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