e97429cef7d0285f8551d89ba8dbc7d83f4fcdf4
[mussa.git] / qui / PathScene.cxx
1 #include "PathScene.h"
2
3 #include <QDir>
4 #include <QFileDialog>
5 #include <QMessageBox>
6 #include <QMouseEvent>
7 #include <QRubberBand>
8 #include <QRect>
9 #include <QString>
10 #include <iostream>
11 #include <set>
12
13 #include <GL/gl.h>
14 #include <math.h>
15
16 #include "qui/GlSequence.h"
17
18 using namespace std;
19
20 PathScene::PathScene(Mussa* analysis, QWidget *parent) : 
21   QGLWidget(parent),
22   viewport_height(0),
23   viewport_width(0),
24   clipZ(30.0),
25   zoom(0),
26   maxOrtho2d(-50.0, -50, 3000000.0, 300.0),
27   curOrtho2d(maxOrtho2d),
28   selectedMode(false),
29   rubberBand(0),
30   drawingBand(false)
31
32   if (analysis == 0)
33   {
34     mussaAnalysis = new Mussa;
35   }
36   else
37   {
38     mussaAnalysis = analysis;
39   }
40   updateScene();
41 }
42
43 QSize PathScene::sizeHint() const
44 {
45   return QSize(400, 400);
46 }
47
48 static float max(float a, float b)
49 {
50   if ( a < b) 
51     return b;
52   else
53     return a;
54 }
55
56 static float min(float a, float b)
57 {
58   if ( a < b) 
59     return a;
60   else
61     return b;
62 }
63
64 void PathScene::setZoom(int newZoom)
65 {
66   std::cout << newZoom << " " << zoom << std::endl;
67   if (zoom != newZoom) {
68     // try to figure out where we should be now?
69
70     float width = maxOrtho2d.width();
71     std::cout << "max width " << width 
72               << " max left " << maxOrtho2d.left()
73               << " max right " << maxOrtho2d.right() << std::endl;
74     float newWidth = width / newZoom;
75     std::cout << "new width" << newWidth<< std::endl;
76     float center = curOrtho2d.width()/2 + curOrtho2d.left();
77     std::cout << "l: " << curOrtho2d.left() << " r: " << curOrtho2d.right() 
78               << " ctr: " << center << std::endl;
79     curOrtho2d.setLeft(max(center-newWidth, maxOrtho2d.left()));
80     curOrtho2d.setRight(min(center+newWidth, maxOrtho2d.right()));
81     std::cout << "nl: " << curOrtho2d.left() << " nr: " << curOrtho2d.right()
82               << std::endl;
83     zoom = newZoom;
84     update();
85   }
86 }
87
88 void PathScene::setClipPlane(int newZ)
89 {
90   if (clipZ != (double) newZ){
91     clipZ = (double) newZ;
92     update();
93   }
94 }
95
96 void PathScene::loadMupa()
97 {
98   QString caption("Load a mussa parameter file");
99   QString filter("Mussa Parameters (*.mupa)");
100   QString mupa_path = QFileDialog::getOpenFileName(this,
101                                                    caption, 
102                                                    QDir::currentPath(),
103                                                    filter);
104   // user hit cancel?
105   if (mupa_path.isNull()) 
106     return;
107   // try to load safely
108   try {
109     Mussa *m = new Mussa;
110     m->load_mupa_file(mupa_path.toStdString());
111     m->analyze(0, 0, Mussa::TransitiveNway, 0.0);
112     // only switch mussas if we loaded without error
113     delete mussaAnalysis;
114     mussaAnalysis = m;
115     updateScene();
116   } catch (mussa_load_error e) {
117     QString msg("Unable to load ");
118     msg += mupa_path;
119     msg += "\n";
120     msg += e.what();
121     QMessageBox::warning(this, "Load Parameter", msg);
122   }
123   assert (mussaAnalysis != 0);
124 }
125
126 void PathScene::loadSavedAnalysis()
127 {
128   QString caption("Load a previously run analysis");
129   QString muway_dir = QFileDialog::getExistingDirectory(this,
130                                                         caption, 
131                                                         QDir::currentPath());
132   // user hit cancel?
133   if (muway_dir.isNull()) 
134     return;
135   // try to safely load
136   try {
137     Mussa *m = new Mussa;
138     m->load(muway_dir.toStdString());
139     // only switch mussas if we loaded without error
140     delete mussaAnalysis;
141     mussaAnalysis = m;
142     updateScene();
143   } catch (mussa_load_error e) {
144     QString msg("Unable to load ");
145     msg += muway_dir;
146     msg += "\n";
147     msg += e.what();
148     QMessageBox::warning(this, "Load Parameter", msg);
149   }
150   assert (mussaAnalysis != 0);
151 }
152
153 void PathScene::setSoftThreshold(int threshold)
154 {
155   if (mussaAnalysis->get_threshold() != threshold) {
156     mussaAnalysis->set_soft_thres(threshold);
157     mussaAnalysis->nway();
158     update();
159   }
160 }
161
162 ////////////////////
163 // Rendering code
164 void PathScene::initializeGL()
165 {
166   glEnable(GL_DEPTH_TEST);
167   glClearColor(1.0, 1.0, 1.0, 0.0);
168   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
169   glShadeModel(GL_FLAT);
170 }
171
172 void PathScene::resizeGL(int width, int height)
173 {
174   viewport_width = width;
175   viewport_height = height;
176   assert (geometry().width() == width);
177   assert (geometry().height() == height);
178   glViewport(0, 0, (GLsizei)width, (GLsizei)height);
179   glMatrixMode(GL_PROJECTION);
180   glLoadIdentity();
181   // I'm abusing this as Qt and OpenGL disagree about the direction of the
182   // y axis
183   glOrtho(curOrtho2d.left(), curOrtho2d.right(), 
184           curOrtho2d.top(), curOrtho2d.bottom(), 
185           -50.0, clipZ);
186 }
187
188 void PathScene::paintGL()
189 {
190   glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
191
192   glPushMatrix();
193   glMatrixMode(GL_PROJECTION);
194   glLoadIdentity();
195   glOrtho(curOrtho2d.left(), curOrtho2d.right(), 
196           curOrtho2d.top(), curOrtho2d.bottom(), 
197           -50.0, clipZ);
198   glMatrixMode(GL_MODELVIEW);
199   mussaesque();
200   glEnable(GL_BLEND);
201   glDepthMask(GL_FALSE);
202   if (selectedMode && !drawingBand) {
203     glColor4f(0.4, 0.4, 0.4, 0.9);
204     glRectf(previousBand.x(), previousBand.y(),
205             previousBand.right(), previousBand.bottom());
206     cout << "x: " << previousBand.x() << " r:" << previousBand.right() << endl;
207   }
208   glDepthMask(GL_TRUE);
209   glDisable(GL_BLEND);
210   glPopMatrix();
211   glFlush();
212 }
213
214 void PathScene::updateScene()
215 {
216   // Delete old glsequences
217   // FIXME: does this actually free the memory from the new'ed GlSequences?
218   tracks.clear();
219
220   // save a reference to our GlSequences
221   GlSequence *gl_seq;
222   float y = mussaAnalysis->sequences().size() * 100 ;
223   float max_base_pairs = 0;
224   typedef vector<Sequence>::const_iterator seqs_itor_t;
225   for(seqs_itor_t seq_itor = mussaAnalysis->sequences().begin();
226       seq_itor != mussaAnalysis->sequences().end();
227       ++seq_itor)
228   {
229     y = y - 100;
230     gl_seq = new GlSequence(*seq_itor);
231     gl_seq->setX(0);
232     gl_seq->setY(y);
233     if (gl_seq->width() > max_base_pairs ) max_base_pairs = gl_seq->width();
234     tracks.push_back( *gl_seq );
235   }
236   maxOrtho2d.setWidth(max_base_pairs + 100.0);
237   maxOrtho2d.setHeight((mussaAnalysis->sequences().size()) * 100 );
238   curOrtho2d = maxOrtho2d;
239 }
240
241 void PathScene::processSelection(GLuint hits, GLuint buffer[], GLuint bufsize)
242 {
243   const size_t pathz_count = mussaAnalysis->paths().refined_pathz.size();
244   GLuint *ptr;
245   GLuint names;
246   float z1;
247   float z2;
248   GLuint objtype;
249   GLuint objid;
250
251   selectedPaths.clear();
252   selectedPaths.reserve(pathz_count);
253   selectedPaths.insert(selectedPaths.begin(), pathz_count, false);
254   selectedTrack = 0x7fffffff;
255   
256   std::cout << "hits = " << hits << " " << pathz_count << std::endl;
257   ptr = (GLuint *) buffer;
258   if (hits > 0)
259     selectedMode = true;
260   for (GLuint i=0; i < hits; ++i)
261   {
262     if ((i + 5) > bufsize) {
263       std::clog << "*** selection overflow***" << std::endl;
264     } else {
265       names = *ptr++;
266       z1 = ((float)*ptr++)/0x7fffffff;
267       z2 = ((float)*ptr++)/0x7fffffff;
268       objtype = *ptr++;
269       objid = *ptr++;
270       if (names != 2) {
271         std::clog << "wrong number of glNames, selection is having trouble ";
272         std::clog << " got " << names << std::endl;
273         std::clog << " names: " ;
274         for (GLuint j = 0 ; j < names-2; ++j) {
275           std::clog << *ptr++ << " ";
276         }
277         std::clog << endl;
278         return;
279       } 
280       switch (objtype) {
281         case MussaPaths:
282           selectedPaths[objid] = true;
283         break;
284         case MussaTracks:
285           if (objid < selectedTrack) {
286             selectedTrack = objid; 
287           }
288         break;
289       }
290     }
291   }
292 }
293
294 void PathScene::mousePressEvent( QMouseEvent *e)
295 {
296   drawingBand = true;
297   std::cout << "x=" << e->x() << " y=" << e->y() << std::endl;
298
299   selectedMode = false;
300   bandOrigin = e->pos();
301   if (!rubberBand)
302     rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
303   
304   rubberBand->setGeometry(QRect(bandOrigin, QSize()));
305   rubberBand->show();
306 }
307
308 void PathScene::mouseMoveEvent( QMouseEvent *e)
309 {
310   rubberBand->setGeometry(QRect(bandOrigin, e->pos()).normalized());
311 }
312
313 void dumpRect(const QRect &r)
314 {
315   std::cout << "x=" << r.x() << " y=" << r.y() 
316             << " w=" << r.width() << " h=" << r.height() << std::endl;
317 }
318
319 void PathScene::mouseReleaseEvent( QMouseEvent *e)
320 {
321   drawingBand = false;
322   rubberBand->hide();
323   QRect r = QRect(bandOrigin, e->pos()).normalized();
324   bandOrigin = r.topLeft();
325   std::cout << "band ";
326   dumpRect(r);
327
328   std::cout << "window ";
329   dumpRect(geometry());
330
331   GLfloat x_scale = curOrtho2d.width()/((float)geometry().width());
332   GLfloat y_scale = curOrtho2d.height()/((float)geometry().height());
333   GLfloat x_left = curOrtho2d.left() + (r.x()*x_scale);
334   GLfloat x_right = x_left + r.width() * x_scale;
335   // using QRectF with Y axis swapped
336   GLfloat y_top = curOrtho2d.bottom()-(r.y()*y_scale);
337   GLfloat y_bottom = y_top - r.height() * y_scale;
338   previousBand.setCoords(x_left, y_top, x_right, y_bottom);
339   std::cout << x_left << " " << x_right << " " << y_top << " " << y_bottom 
340             << std::endl;
341
342   // hopefully this will make a buffer big enough to receive 
343   // everything being selected
344   const size_t pathz_count = mussaAnalysis->paths().refined_pathz.size();
345   const GLuint select_buf_size = 1 + 5 * (pathz_count + tracks.size()) ;
346   GLuint selectBuf[select_buf_size];
347   glSelectBuffer(select_buf_size, selectBuf);
348   GLint hits;
349
350   (void)glRenderMode(GL_SELECT);
351   glPushMatrix();
352   glMatrixMode(GL_PROJECTION);
353   glLoadIdentity();
354   glOrtho(x_left, x_right, y_top, y_bottom, -50.0, 50.0);
355   glMatrixMode(GL_MODELVIEW);
356   glLoadIdentity();
357  
358   mussaesque();
359   glFlush();
360
361   glPopMatrix();
362   hits = glRenderMode(GL_RENDER);
363   processSelection(hits, selectBuf, select_buf_size);
364
365   resizeGL(geometry().width(), geometry().height());
366 }
367
368
369 //////
370 // openGl rendering code
371
372 void PathScene::draw_tracks() const
373 {
374   glPushMatrix();
375   glPushName(0);
376   for (vector<GlSequence>::size_type i = 0; i != tracks.size(); ++i )
377   {
378     glLoadName(i);
379     tracks[i].draw(curOrtho2d.left(), curOrtho2d.right());
380   }
381   glPopName();
382   glPopMatrix();
383 }
384
385 void PathScene::draw_lines() const
386 {
387   GLfloat x;
388   GLfloat y;
389   GLuint pathid=0;
390   GLint objid=0;
391   bool reversed = false;
392   bool prevReversed = false;
393   const NwayPaths& nway = mussaAnalysis->paths();
394
395   glPushName(pathid);
396   glLineWidth(2);
397   vector<GlSequence>::const_iterator track_itor;
398   
399   typedef list<ExtendedConservedPath> conserved_paths;
400   typedef conserved_paths::const_iterator const_conserved_paths_itor;
401   for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
402       path_itor != nway.refined_pathz.end();
403       ++path_itor, ++objid)
404   {
405     track_itor = tracks.begin();
406     // since we were drawing to the start of a window, and opengl lines
407     // are centered around the two connecting points our lines were slightly
408     // offset. the idea of window_offset is to adjust them to the
409     // right for forward compliment or left for reverse compliment
410     // FIXME: figure out how to unit test these computations
411     GLfloat window_offset = (path_itor->window_size)/2.0;
412     
413     glBegin(GL_LINE_STRIP);
414     for (vector<int>::const_iterator sp_itor = path_itor->begin();
415          sp_itor != path_itor->end();
416          ++sp_itor, ++track_itor)
417     {
418       x = *sp_itor;
419       y = track_itor->y();
420       // at some point when we modify the pathz data structure to keep
421       // track of the score we can put grab the depth here.
422       //
423       // are we reverse complimented? 
424       if ( x>=0) {
425         reversed = false;
426       } else {
427         reversed = true;
428         x = -x; // make positive
429       }
430       if (!reversed)
431         x += window_offset; // move right for forward compliment
432       else
433         x -= window_offset; // move left for reverse compliment
434       // the following boolean implements logical xor
435       if ( (reversed || prevReversed) && (!reversed || !prevReversed)) { 
436         // we have a different orientation
437         if (not selectedMode or selectedPaths[objid] == true) {
438           // if we have nothing selected, or we're the highlight, be bright
439           glColor3f(0.0, 0.0, 1.0);
440         } else {
441           // else be dim
442           glColor3f(0.5, 0.5, 1.0);
443         }
444       } else {
445         // both current and previous path have the same orientation
446         if (not selectedMode or selectedPaths[objid] == true) {
447           glColor3f(1.0, 0.0, 0.2);
448         } else {
449           glColor3f(1.0, 0.5, 0.5);
450         }
451       }
452       prevReversed = reversed;
453       glVertex3f(x, y, 0.0);
454     }
455     glEnd();
456     glLoadName(++pathid);
457   }
458   glPopName();
459 }
460
461 GLuint PathScene::make_line_list()
462 {
463   GLuint line_list = glGenLists(1);
464   glNewList(line_list, GL_COMPILE);
465
466   draw_lines();
467
468   glEndList();
469   return line_list;
470
471 }
472
473 void PathScene::mussaesque()
474 {
475   //static GLuint theLines = make_line_list();
476
477   glInitNames();
478   glPushName(MussaPaths);
479   //glCallList(theLines);
480   draw_lines();
481   glLoadName(MussaTracks);
482   draw_tracks();
483   glPopName();
484 }
485