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