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