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