PathScene rare mousemove without mouseclick event bug fix.
[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   if (drawingBand)
311     rubberBand->setGeometry(QRect(bandOrigin, e->pos()).normalized());
312 }
313
314 void dumpRect(const QRect &r)
315 {
316   std::cout << "x=" << r.x() << " y=" << r.y() 
317             << " w=" << r.width() << " h=" << r.height() << std::endl;
318 }
319
320 void PathScene::mouseReleaseEvent( QMouseEvent *e)
321 {
322   drawingBand = false;
323   rubberBand->hide();
324   QRect r = QRect(bandOrigin, e->pos()).normalized();
325   bandOrigin = r.topLeft();
326   std::cout << "band ";
327   dumpRect(r);
328
329   std::cout << "window ";
330   dumpRect(geometry());
331
332   GLfloat x_scale = curOrtho2d.width()/((float)geometry().width());
333   GLfloat y_scale = curOrtho2d.height()/((float)geometry().height());
334   GLfloat x_left = curOrtho2d.left() + (r.x()*x_scale);
335   GLfloat x_right = x_left + r.width() * x_scale;
336   // using QRectF with Y axis swapped
337   GLfloat y_top = curOrtho2d.bottom()-(r.y()*y_scale);
338   GLfloat y_bottom = y_top - r.height() * y_scale;
339   previousBand.setCoords(x_left, y_top, x_right, y_bottom);
340   std::cout << x_left << " " << x_right << " " << y_top << " " << y_bottom 
341             << std::endl;
342
343   // hopefully this will make a buffer big enough to receive 
344   // everything being selected
345   const size_t pathz_count = mussaAnalysis->paths().refined_pathz.size();
346   const GLuint select_buf_size = 1 + 5 * (pathz_count + tracks.size()) ;
347   GLuint selectBuf[select_buf_size];
348   glSelectBuffer(select_buf_size, selectBuf);
349   GLint hits;
350
351   (void)glRenderMode(GL_SELECT);
352   glPushMatrix();
353   glMatrixMode(GL_PROJECTION);
354   glLoadIdentity();
355   glOrtho(x_left, x_right, y_top, y_bottom, -50.0, 50.0);
356   glMatrixMode(GL_MODELVIEW);
357   glLoadIdentity();
358  
359   mussaesque();
360   glFlush();
361
362   glPopMatrix();
363   hits = glRenderMode(GL_RENDER);
364   processSelection(hits, selectBuf, select_buf_size);
365
366   resizeGL(geometry().width(), geometry().height());
367 }
368
369
370 //////
371 // openGl rendering code
372
373 void PathScene::draw_tracks() const
374 {
375   glPushMatrix();
376   glPushName(0);
377   for (vector<GlSequence>::size_type i = 0; i != tracks.size(); ++i )
378   {
379     glLoadName(i);
380     tracks[i].draw(curOrtho2d.left(), curOrtho2d.right());
381   }
382   glPopName();
383   glPopMatrix();
384 }
385
386 void PathScene::draw_lines() const
387 {
388   GLfloat x;
389   GLfloat y;
390   GLuint pathid=0;
391   GLint objid=0;
392   bool reversed = false;
393   bool prevReversed = false;
394   const NwayPaths& nway = mussaAnalysis->paths();
395
396   glPushName(pathid);
397   glLineWidth(2);
398   vector<GlSequence>::const_iterator track_itor;
399   
400   typedef list<ExtendedConservedPath> conserved_paths;
401   typedef conserved_paths::const_iterator const_conserved_paths_itor;
402   for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
403       path_itor != nway.refined_pathz.end();
404       ++path_itor, ++objid)
405   {
406     track_itor = tracks.begin();
407     // since we were drawing to the start of a window, and opengl lines
408     // are centered around the two connecting points our lines were slightly
409     // offset. the idea of window_offset is to adjust them to the
410     // right for forward compliment or left for reverse compliment
411     // FIXME: figure out how to unit test these computations
412     GLfloat window_offset = (path_itor->window_size)/2.0;
413     
414     glBegin(GL_LINE_STRIP);
415     for (vector<int>::const_iterator sp_itor = path_itor->begin();
416          sp_itor != path_itor->end();
417          ++sp_itor, ++track_itor)
418     {
419       x = *sp_itor;
420       y = track_itor->y();
421       // at some point when we modify the pathz data structure to keep
422       // track of the score we can put grab the depth here.
423       //
424       // are we reverse complimented? 
425       if ( x>=0) {
426         reversed = false;
427       } else {
428         reversed = true;
429         x = -x; // make positive
430       }
431       if (!reversed)
432         x += window_offset; // move right for forward compliment
433       else
434         x -= window_offset; // move left for reverse compliment
435       // the following boolean implements logical xor
436       if ( (reversed || prevReversed) && (!reversed || !prevReversed)) { 
437         // we have a different orientation
438         if (not selectedMode or selectedPaths[objid] == true) {
439           // if we have nothing selected, or we're the highlight, be bright
440           glColor3f(0.0, 0.0, 1.0);
441         } else {
442           // else be dim
443           glColor3f(0.5, 0.5, 1.0);
444         }
445       } else {
446         // both current and previous path have the same orientation
447         if (not selectedMode or selectedPaths[objid] == true) {
448           glColor3f(1.0, 0.0, 0.2);
449         } else {
450           glColor3f(1.0, 0.5, 0.5);
451         }
452       }
453       prevReversed = reversed;
454       glVertex3f(x, y, 0.0);
455     }
456     glEnd();
457     glLoadName(++pathid);
458   }
459   glPopName();
460 }
461
462 GLuint PathScene::make_line_list()
463 {
464   GLuint line_list = glGenLists(1);
465   glNewList(line_list, GL_COMPILE);
466
467   draw_lines();
468
469   glEndList();
470   return line_list;
471
472 }
473
474 void PathScene::mussaesque()
475 {
476   //static GLuint theLines = make_line_list();
477
478   glInitNames();
479   glPushName(MussaPaths);
480   //glCallList(theLines);
481   draw_lines();
482   glLoadName(MussaTracks);
483   draw_tracks();
484   glPopName();
485 }
486