threshold widget shows both base pair and percents
[mussa.git] / qui / MussaWindow.cpp
1 #include <QAction>
2 #include <QDir>
3 #include <QFileDialog>
4 #include <QHBoxLayout>
5 #include <QIcon>
6 #include <QMenuBar>
7 #include <QMessageBox>
8 #include <QScrollBar>
9 #include <QStatusBar>
10 #include <QString>
11 #include <QWhatsThis>
12
13 #include "qui/MussaWindow.hpp"
14 #include "mussa_exceptions.hpp"
15
16 #include <iostream>
17
18 using namespace std;
19
20 MussaWindow::MussaWindow(Mussa *analysis_, QWidget *parent) :
21   QMainWindow(parent),
22   analysis(analysis_),
23   motif_editor(0),
24   browser(this),
25   mussaViewTB("Path Views"),
26   zoomBox(),
27   threshold(),
28   zoomLabel(),
29   closeAction(0) // initialize one of the pointers to null as a saftey flag
30 {
31   setupActions();
32   setupMainMenu();
33
34   //This next setWhatsThis function prevents
35   // a segfault when using WhatsThis feature with 
36   // opengl widget.
37   //scene->setWhatsThis(tr("Mussa in OpenGL!"));
38   setCentralWidget(&browser);
39   // well updatePosition isn't quite right as we really just need
40   // to call update()
41   connect(this, SIGNAL(changedAnnotations()), &browser, SLOT(update()));
42
43   mussaViewTB.addAction(toggleMotifsAction);
44   
45   zoomLabel.setPixmap(QIcon(":/icons/viewmag.png").pixmap(16, 16));
46   zoomLabel.setToolTip(tr("Zoom"));
47   zoomLabel.setWhatsThis(tr("Zoom magnification factor"));
48   mussaViewTB.addWidget(&zoomLabel);
49
50   zoomBox.setToolTip(tr("Zoom"));
51   zoomBox.setWhatsThis(tr("Zoom magnification factor"));
52   zoomBox.setRange(1,10000000);
53   mussaViewTB.addWidget(&zoomBox);
54   connect(&zoomBox, SIGNAL(valueChanged(int)), 
55           &browser, SLOT(setZoom(int)));
56   
57   // threshold range is set in updateAnalysis
58   
59   //scene->setClipPlane(20);
60   // FIXME: for when we get the paths drawn at the appropriate depth
61   //connect(&threshold, SIGNAL(thresholdChanged(int)),
62   //        this, SLOT(setClipPlane(int)));
63   connect(&threshold, SIGNAL(thresholdChanged(int)),
64           this, SLOT(setSoftThreshold(int)));
65   mussaViewTB.addWidget(&threshold);
66
67   addToolBar(&mussaViewTB);
68
69   statusBar()->showMessage("Welcome to mussa", 2000);
70   updateAnalysis();
71 }
72
73 void MussaWindow::setAnalysis(Mussa *new_analysis)
74 {
75   if (new_analysis != 0) {
76     // only switch mussas if we loaded without error
77     delete analysis;
78     analysis = new_analysis;
79     updateAnalysis();
80   }
81 }
82
83 void MussaWindow::setupActions()
84 {
85   // we really don't want to run this more than once.
86   assert (closeAction == 0);
87
88   // the ever popular about box
89   aboutAction = new QAction(tr("&About"), this);
90   connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
91   aboutAction->setIcon(QIcon(":/icons/info.png"));
92
93   // add exit
94   closeAction = new QAction(tr("&Close"), this);
95   closeAction->setStatusTip(tr("Close this window"));
96   connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
97   closeAction->setIcon(QIcon(":/icons/exit.png"));
98   
99   createNewAnalysisAction = new QAction(tr("Define Analysis"), this);
100   connect(createNewAnalysisAction, SIGNAL(triggered()), 
101           this, SLOT(createNewAnalysis()));
102   createNewAnalysisAction->setIcon(QIcon(":/icons/filenew.png"));
103   
104   createSubAnalysisAction = new QAction(tr("Define SubAnalysis"), this);
105   connect(createSubAnalysisAction, SIGNAL(triggered()), 
106           this, SLOT(createSubAnalysis()));
107
108   editMotifsAction = new QAction(tr("Edit Motifs"), this);;
109   connect(editMotifsAction, SIGNAL(triggered()), this, SLOT(editMotifs()));
110   
111   loadMotifListAction = new QAction(tr("Load Motif List"), this);
112   connect(loadMotifListAction, SIGNAL(triggered()), 
113           this, SLOT(loadMotifList()));
114   loadMotifListAction->setIcon(QIcon(":/icons/fileopen.png"));
115   
116   loadMupaAction = new QAction(tr("Load Mussa Parameters"), this);
117   connect(loadMupaAction, SIGNAL(triggered()), 
118           this, SLOT(loadMupa()));
119   loadMupaAction->setIcon(QIcon(":/icons/fileopen.png"));
120
121   loadSavedAnalysisAction = new QAction(tr("Load &Analysis"), this);
122   connect(loadSavedAnalysisAction, SIGNAL(triggered()), 
123           this, SLOT(loadSavedAnalysis()));
124   loadSavedAnalysisAction->setIcon(QIcon(":/icons/fileopen.png"));
125
126   saveMotifListAction = new QAction(tr("Save Motifs"), this);
127   connect(saveMotifListAction, SIGNAL(triggered()), 
128           this, SLOT(saveMotifList()));
129   saveMotifListAction->setIcon(QIcon(":/icons/filesave.png"));
130
131   showMussaViewToolbarAction = new QAction(tr("Show Toolbar"), this);
132   connect(showMussaViewToolbarAction, SIGNAL(triggered()), 
133           this, SLOT(showMussaToolbar()));
134   showMussaViewToolbarAction->setCheckable(true);
135   showMussaViewToolbarAction->setChecked(true);
136
137   toggleMotifsAction = new QAction(tr("Toggle Motifs"), this);
138   connect(toggleMotifsAction, SIGNAL(triggered()), 
139           this, SLOT(toggleMotifs()));
140   toggleMotifsAction->setCheckable(true);
141   toggleMotifsAction->setIcon(QIcon(":/icons/motif_icon.png"));
142   toggleMotifsAction->setWhatsThis(tr("Toggle motif annotations on/off\n\n"
143                                    "You can load motif annotations via "
144                                    "'File->Load Motif List' menu option."));
145
146   //Save pixel map action
147   saveBrowserPixmapAction = new QAction(tr("Save to image..."), this);
148   connect(saveBrowserPixmapAction, (SIGNAL(triggered())),
149           &browser, SLOT(promptSaveBrowserPixmap()));
150   saveBrowserPixmapAction->setIcon(QIcon(":/icons/image2.png"));
151
152   viewMussaAlignmentAction = new QAction(tr("View mussa alignment"), this);
153   connect(viewMussaAlignmentAction, SIGNAL(triggered()),
154           this, SLOT(viewMussaAlignment() ));
155   viewMussaAlignmentAction->setWhatsThis(tr("Create a zoomed in window "
156                                             "showing alignment of the seqcomp "
157                                             "defined paths"));
158
159   whatsThisAction = QWhatsThis::createAction(this);
160   whatsThisAction->setIcon(QIcon(":/icons/help.png"));
161
162 }
163
164 void MussaWindow::setupMainMenu()
165 {
166   // we need to run setupActions first
167   assert (closeAction != 0);
168   
169   QMenu *newMenu;
170   newMenu = menuBar()->addMenu(tr("&File"));
171   newMenu->addAction(createNewAnalysisAction);
172   newMenu->addAction(loadMupaAction);
173   newMenu->addAction(loadSavedAnalysisAction);
174   //newMenu->addAction(createSubAnalysisAction);
175   newMenu->addSeparator();
176   newMenu->addAction(loadMotifListAction);
177   newMenu->addAction(saveMotifListAction);
178   newMenu->addSeparator();
179   newMenu->addAction(saveBrowserPixmapAction);
180   newMenu->addSeparator();
181   newMenu->addAction(closeAction);
182
183   newMenu = menuBar()->addMenu(tr("&View"));
184   newMenu->addAction(editMotifsAction);
185   newMenu->addAction(viewMussaAlignmentAction);
186   newMenu->addAction(showMussaViewToolbarAction);
187
188   newMenu = menuBar()->addMenu(tr("&Help"));
189   newMenu->addAction(whatsThisAction);
190   newMenu->addSeparator();
191   newMenu->addAction(aboutAction);
192 }
193   
194 void MussaWindow::about()
195 {
196   QString msg("Welcome to Multiple Species Sequence Analysis\n"
197               "(c) 2005-2006 California Institute of Technology\n"
198               "Tristan De Buysscher, Diane Trout\n");
199    msg += "\n\r";
200    msg += "Path: ";
201    msg += QDir::currentPath();
202    msg += "\n";
203    msg += "OpenGL: ";
204    msg += (char *)glGetString(GL_VERSION);
205    msg += "\n";
206    QMessageBox::about(this, tr("About mussa"), msg);
207 }
208
209 void MussaWindow::createNewAnalysis()
210 {
211   try {
212     if (setup_analysis_dialog.exec()) {
213       Mussa *m = 0;
214       m = setup_analysis_dialog.getMussa();
215       setAnalysis(m);
216     } else {
217       std::cout << "New mussa exp. aborted!\n";
218     }
219   } catch(mussa_error e) {
220     QString msg(e.what());
221     QMessageBox::warning(this, tr("Create New Analysis"), msg);
222   }
223 }
224
225 void MussaWindow::createSubAnalysis()
226 {
227   NotImplementedBox();
228 }
229
230 void MussaWindow::editMotifs()
231 {
232   if (motif_editor != 0) {
233     motif_editor->hide();
234     delete motif_editor;
235   }
236   motif_editor = new MotifEditor(analysis);
237   connect(motif_editor, SIGNAL(changedMotifs()), 
238           this, SLOT(updateAnnotations()));
239   motif_editor->show();
240 }
241
242 void MussaWindow::loadMotifList()
243 {
244   QString caption("Load a motif list");
245   QString filter("Motif list(*.txt *.mtl)");
246   QString path = QFileDialog::getOpenFileName(this,
247                                                    caption, 
248                                                    QDir::currentPath(),
249                                                    filter);
250   // user hit cancel?
251   if (path.isNull()) 
252     return;
253   // try to load safely
254   try {
255     analysis->load_motifs(path.toStdString());
256   } catch (runtime_error e) {
257     QString msg("Unable to load ");
258     msg += path;
259     msg += "\n";
260     msg += e.what();
261     QMessageBox::warning(this, "Load Motifs", msg);
262   }
263   assert (analysis != 0);
264 }
265
266 void MussaWindow::saveMotifList()
267 {
268   NotImplementedBox();
269 }
270
271 void MussaWindow::loadMupa()
272 {
273   QString caption("Load a mussa parameter file");
274   QString filter("Mussa Parameters (*.mupa)");
275   QString mupa_path = QFileDialog::getOpenFileName(this,
276                                                    caption, 
277                                                    QDir::currentPath(),
278                                                    filter);
279   // user hit cancel?
280   if (mupa_path.isNull()) 
281     return;
282   // try to load safely
283   try {
284     Mussa *m = new Mussa;
285     m->load_mupa_file(mupa_path.toStdString());
286     m->analyze(0, 0, Mussa::TransitiveNway, 0.0);
287     setAnalysis(m);
288   } catch (mussa_load_error e) {
289     QString msg("Unable to load ");
290     msg += mupa_path;
291     msg += "\n";
292     msg += e.what();
293     QMessageBox::warning(this, "Load Parameter", msg);
294   }
295   assert (analysis != 0);
296 }
297
298 void MussaWindow::loadSavedAnalysis()
299 {
300   QString caption("Load a previously run analysis");
301   QString muway_dir = QFileDialog::getExistingDirectory(this,
302                                                         caption, 
303                                                         QDir::currentPath());
304   // user hit cancel?
305   if (muway_dir.isNull()) 
306     return;
307   // try to safely load
308   try {
309     Mussa *m = new Mussa;
310     m->load(muway_dir.toStdString());
311     // only switch mussas if we loaded without error
312     setAnalysis(m);
313   } catch (mussa_load_error e) {
314     QString msg("Unable to load ");
315     msg += muway_dir;
316     msg += "\n";
317     msg += e.what();
318     QMessageBox::warning(this, "Load Parameter", msg);
319   }
320   assert (analysis != 0);
321 }
322
323 void MussaWindow::setSoftThreshold(int threshold)
324 {
325   if (analysis->get_soft_thres() != threshold) {
326     analysis->set_soft_thres(threshold);
327     analysis->nway();
328     updateLinks();
329     update();
330   }
331 }
332
333 void MussaWindow::showMussaToolbar()
334 {
335   if (mussaViewTB.isVisible())
336     mussaViewTB.hide();
337   else
338     mussaViewTB.show();
339 }
340
341 void MussaWindow::toggleMotifs()
342 {
343   NotImplementedBox();
344 }
345
346 void MussaWindow::NotImplementedBox()
347 {
348   QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
349 }      
350
351 void MussaWindow::viewMussaAlignment()
352 {
353   const set<int>& selected_paths = browser.selectedPaths();
354   if (selected_paths.size() == 0 ) {
355     QMessageBox::warning(this, 
356                          QObject::tr("mussa"),
357                          QObject::tr("you should probably select some paths "
358                                      "first"));
359   } else {
360     MussaAlignedWindow *ma_win = new MussaAlignedWindow(*analysis, 
361                                                         selected_paths);
362     connect(this, SIGNAL(changedAnnotations()), 
363             ma_win, SLOT(update()));
364     aligned_windows.push_back(ma_win);
365     ma_win->show();
366   }
367 }
368                         
369 void MussaWindow::updateAnalysis()
370 {
371   threshold.setRange(analysis->get_threshold(),analysis->get_window());
372   for (list<MussaAlignedWindow *>::iterator maw_i = aligned_windows.begin();
373        maw_i != aligned_windows.end();
374        ++maw_i)
375   {
376     (*maw_i)->hide();
377     delete *maw_i;
378   }
379
380   browser.clear();
381   const vector<Sequence>& seqs = analysis->sequences();
382   browser.setSequences(seqs, analysis->colorMapper());
383   updateLinks();
384   browser.zoomOut();
385   cout << "browser zoom " <<  browser.zoom() << endl;
386   zoomBox.setValue(browser.zoom());
387 }
388
389 void MussaWindow::updateAnnotations()
390 {
391   // motifs were changed in the sequences by 
392   // Mussa::update_sequences_motifs
393   emit changedAnnotations();
394   browser.update();
395 }
396
397 void MussaWindow::updateLinks()
398 {
399   browser.clear_links();
400   bool reversed = false;
401   const NwayPaths& nway = analysis->paths();
402
403   typedef list<ExtendedConservedPath> conserved_paths;
404   typedef conserved_paths::const_iterator const_conserved_paths_itor;
405   for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
406       path_itor != nway.refined_pathz.end();
407       ++path_itor)
408   {
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     size_t track_len = path_itor->track_indexes.size();
417     vector<int> normalized_path;
418     normalized_path.reserve(track_len);
419     vector<bool> rc_flags(false, track_len);
420     for (size_t track_i=0; track_i != track_len; ++track_i)
421     {
422       int x = path_itor->track_indexes[track_i];
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       normalized_path.push_back(x);
434       rc_flags.push_back(reversed);
435     }
436     browser.link(normalized_path, rc_flags, path_itor->window_size);
437   }
438   browser.update();
439 }
440