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