start trying to figure out where the documentation is
[mussa.git] / qui / MussaWindow.cpp
1 #include "py/python.hpp"
2 #include "qui/MussaWindow.hpp"
3 #include "mussa_exceptions.hpp"
4
5 #include <QAction>
6 #include <QApplication>
7 #include <QAssistantClient>
8 #include <QDir>
9 #include <QFileDialog>
10 #include <QHBoxLayout>
11 #include <QIcon>
12 #include <QMenuBar>
13 #include <QMessageBox>
14 #include <QScrollBar>
15 #include <QStatusBar>
16 #include <QString>
17 #include <QStringList>
18 #include <QWhatsThis>
19
20 #include <memory>
21 #include <iterator>
22 #include <iostream>
23
24 #include <boost/filesystem/path.hpp>
25 namespace fs = boost::filesystem;
26 #include <boost/bind.hpp>
27
28 using namespace std;
29
30 MussaWindow::MussaWindow(Mussa *analysis_, QWidget *parent) :
31   QMainWindow(parent),
32   analysis(analysis_),
33   motif_editor(0),
34   setup_analysis_dialog(this),
35   browser(this),
36   mussaViewTB("Path Views"),
37   zoom(),
38   threshold(),
39   progress_dialog(0),
40   aboutAction(0),
41   closeAction(0),
42   createNewAnalysisAction(0),
43   createSubAnalysisAction(0),
44   editMotifsAction(0),
45   loadMotifListAction(0),
46   loadMupaAction(0),
47   loadSavedAnalysisAction(0),
48   mussaManualAssistantAction(0),
49   newMussaWindowAction(0),
50   saveMotifListAction(0),
51   showMussaViewToolbarAction(0),
52   toggleMotifsAction(0),
53   saveBrowserPixmapAction(0),
54   whatsThisAction(0),
55   viewMussaAlignmentAction(0),
56   manualAssistant(0)
57 {
58   setupActions();
59   setupMainMenu();
60   setupAssistant();
61
62   //This next setWhatsThis function prevents
63   // a segfault when using WhatsThis feature with 
64   // opengl widget.
65   //scene->setWhatsThis(tr("Mussa in OpenGL!"));
66   setCentralWidget(&browser);
67   // well updatePosition isn't quite right as we really just need
68   // to call update()
69   connect(this, SIGNAL(changedAnnotations()), &browser, SLOT(update()));
70
71   //mussaViewTB.addAction(toggleMotifsAction);
72   mussaViewTB.addWidget(&zoom);
73   
74   connect(&zoom, SIGNAL(valueChanged(double)), 
75           &browser, SLOT(setZoom(double)));
76   
77   // threshold range is set in updateAnalysis
78   
79   //scene->setClipPlane(20);
80   // FIXME: for when we get the paths drawn at the appropriate depth
81   //connect(&threshold, SIGNAL(thresholdChanged(int)),
82   //        this, SLOT(setClipPlane(int)));
83   connect(&threshold, SIGNAL(thresholdChanged(int)),
84           this, SLOT(setSoftThreshold(int)));
85   mussaViewTB.addWidget(&threshold);
86
87   addToolBar(&mussaViewTB);
88
89   statusBar()->showMessage("Welcome to mussa", 2000);
90   connect(analysis, SIGNAL(progress(const std::string&, int, int)),
91           this, SLOT(updateProgress(const std::string&, int, int)));
92   updateAnalysis();
93 }
94
95 void MussaWindow::setAnalysis(Mussa *new_analysis)
96 {
97   if (new_analysis != 0) {
98     // only switch mussas if we loaded without error
99     clear();
100     delete analysis;
101     analysis = new_analysis;
102     setWindowTitle(analysis->get_name().c_str());
103     updateAnalysis();
104   }
105 }
106
107 void MussaWindow::setupActions()
108 {
109   // we really don't want to run this more than once.
110   assert (closeAction == 0);
111
112   // the ever popular about box
113   aboutAction = new QAction(tr("&About"), this);
114   connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
115   aboutAction->setIcon(QIcon(":/icons/info.png"));
116
117   // add exit
118   closeAction = new QAction(tr("&Close"), this);
119   closeAction->setStatusTip(tr("Close this window"));
120   connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
121   closeAction->setIcon(QIcon(":/icons/exit.png"));
122   
123   createNewAnalysisAction = new QAction(tr("Create Analysis"), this);
124   connect(createNewAnalysisAction, SIGNAL(triggered()), 
125           this, SLOT(createNewAnalysis()));
126   createNewAnalysisAction->setIcon(QIcon(":/icons/filenew.png"));
127   
128   createSubAnalysisAction = new QAction(tr("Add to Subanalysis"), this);
129   connect(createSubAnalysisAction, SIGNAL(triggered()), 
130           this, SLOT(createSubAnalysis()));
131
132   editMotifsAction = new QAction(tr("Edit Motifs"), this);;
133   connect(editMotifsAction, SIGNAL(triggered()), this, SLOT(editMotifs()));
134   
135   loadMotifListAction = new QAction(tr("Load Motif List"), this);
136   connect(loadMotifListAction, SIGNAL(triggered()), 
137           this, SLOT(loadMotifList()));
138   loadMotifListAction->setIcon(QIcon(":/icons/fileopen.png"));
139   
140   loadMupaAction = new QAction(tr("Create Analysis from File"), this);
141   connect(loadMupaAction, SIGNAL(triggered()), 
142           this, SLOT(loadMupa()));
143   loadMupaAction->setIcon(QIcon(":/icons/fileopen.png"));
144
145   loadSavedAnalysisAction = new QAction(tr("Load Existing &Analysis"), this);
146   connect(loadSavedAnalysisAction, SIGNAL(triggered()), 
147           this, SLOT(loadSavedAnalysis()));
148   loadSavedAnalysisAction->setIcon(QIcon(":/icons/fileopen.png"));
149
150   mussaManualAssistantAction = new QAction(tr("Mussagl Manual..."), this);
151   mussaManualAssistantAction->setIcon(QIcon(":/icons/contents.png"));
152   connect(mussaManualAssistantAction, SIGNAL(triggered()),
153           this, SLOT(showManual()));
154
155   newMussaWindowAction = new QAction(tr("&New Mussa Window"), this);
156   newMussaWindowAction->setStatusTip("open another mussa window to allow comparing results");
157   connect(newMussaWindowAction, SIGNAL(triggered()), 
158           this, SLOT(newMussaWindow()));
159   newMussaWindowAction->setIcon(QIcon(":/icons/window_new.png"));
160
161   saveMotifListAction = new QAction(tr("Save Motifs"), this);
162   connect(saveMotifListAction, SIGNAL(triggered()), 
163           this, SLOT(saveMotifList()));
164   saveMotifListAction->setIcon(QIcon(":/icons/filesave.png"));
165
166   showMussaViewToolbarAction = new QAction(tr("Show Toolbar"), this);
167   connect(showMussaViewToolbarAction, SIGNAL(triggered()), 
168           this, SLOT(showMussaToolbar()));
169   showMussaViewToolbarAction->setCheckable(true);
170   showMussaViewToolbarAction->setChecked(true);
171
172   toggleMotifsAction = new QAction(tr("Toggle Motifs"), this);
173   connect(toggleMotifsAction, SIGNAL(triggered()), 
174           this, SLOT(toggleMotifs()));
175   toggleMotifsAction->setCheckable(true);
176   toggleMotifsAction->setIcon(QIcon(":/icons/motif_icon.png"));
177   toggleMotifsAction->setWhatsThis(tr("Toggle motif annotations on/off\n\n"
178                                    "You can load motif annotations via "
179                                    "'File->Load Motif List' menu option."));
180
181   //Save pixel map action
182   saveBrowserPixmapAction = new QAction(tr("Save to image..."), this);
183   connect(saveBrowserPixmapAction, (SIGNAL(triggered())),
184           &browser, SLOT(promptSaveBrowserPixmap()));
185   saveBrowserPixmapAction->setIcon(QIcon(":/icons/image2.png"));
186
187   viewMussaAlignmentAction = new QAction(tr("View sequence alignment"), this);
188   connect(viewMussaAlignmentAction, SIGNAL(triggered()),
189           this, SLOT(viewMussaAlignment() ));
190   viewMussaAlignmentAction->setWhatsThis(tr("Create a zoomed in window "
191                                             "showing alignment of the seqcomp "
192                                             "defined paths"));
193
194   whatsThisAction = QWhatsThis::createAction(this);
195   whatsThisAction->setIcon(QIcon(":/icons/help.png"));
196
197
198 }
199
200 void MussaWindow::setupMainMenu()
201 {
202   // we need to run setupActions first
203   assert (closeAction != 0);
204
205   QMenu *newMenu = menuBar()->addMenu(tr("&File"));
206   
207   newMenu->addAction(newMussaWindowAction);
208   newMenu->addAction(createNewAnalysisAction);
209   newMenu->addAction(loadMupaAction);
210   newMenu->addAction(loadSavedAnalysisAction);
211   newMenu->addSeparator();
212   newMenu->addAction(loadMotifListAction);
213   newMenu->addAction(saveMotifListAction);
214   newMenu->addSeparator();
215   newMenu->addAction(saveBrowserPixmapAction);
216   newMenu->addSeparator();
217   newMenu->addAction(closeAction);
218
219   newMenu = menuBar()->addMenu(tr("&Edit"));
220   newMenu->addAction(editMotifsAction);
221   newMenu->addAction(&browser.getCopySelectedSequenceAsFastaAction());
222   newMenu->addAction(createSubAnalysisAction);
223  
224   newMenu = menuBar()->addMenu(tr("&View"));
225   newMenu->addAction(viewMussaAlignmentAction);
226   newMenu->addAction(showMussaViewToolbarAction);
227
228   newMenu = menuBar()->addMenu(tr("&Help"));
229   newMenu->addAction(mussaManualAssistantAction);
230   newMenu->addAction(whatsThisAction);
231   newMenu->addSeparator();
232   newMenu->addAction(aboutAction);
233
234   // add some extra features to the context menu
235   QMenu& popupMenu = browser.getPopupMenu();
236   popupMenu.addAction(viewMussaAlignmentAction);
237   popupMenu.addAction(createSubAnalysisAction);
238 }
239
240 void MussaWindow::setupAssistant()
241 {
242 #if defined(QT_ASSISTANT_FOUND)
243   QStringList manualAssistantArgs;
244   manualAssistantArgs = QStringList();
245   manualAssistantArgs << "-profile" << "./doc/manual/mussagl_manual.adp";
246   manualAssistant = new QAssistantClient("assistant", this);
247   manualAssistant->setArguments(manualAssistantArgs);
248   connect(manualAssistant, SIGNAL(error(QString)),
249           this, SLOT(assistantError(QString)));
250 #endif
251 }
252   
253 void MussaWindow::about()
254 {
255   QString msg("Welcome to Multiple Species Sequence Analysis\n"
256               "(c) 2005-2006 California Institute of Technology\n"
257               "Tristan De Buysscher, Diane Trout\n");
258    msg += "\n\r";
259    msg += "Path: ";
260    msg += QDir::currentPath();
261    msg += "\n";
262    msg += "OpenGL: ";
263    msg += (char *)glGetString(GL_VERSION);
264    msg += "\n";
265    QMessageBox::about(this, tr("About mussa"), msg);
266 }
267
268 void MussaWindow::clear()
269 {
270   aligned_windows.clear();
271   browser.clear();
272 }
273
274 void MussaWindow::createNewAnalysis()
275 {
276   try {
277     if (setup_analysis_dialog.exec()) {
278       Mussa *m = 0;
279       m = setup_analysis_dialog.getMussa();
280       setAnalysis(m);
281     } else {
282       std::cout << "New mussa exp. aborted!\n";
283     }
284   } catch(mussa_error e) {
285     QString msg(e.what());
286     QMessageBox::warning(this, tr("Create New Analysis"), msg);
287   }
288 }
289
290 void MussaWindow::createSubAnalysis()
291 {
292   list<SequenceLocation> result;
293   SequenceLocationModel& model = subanalysis_window.getModel();
294   browser.copySelectedTracksAsSeqLocation(result);
295   for(list<SequenceLocation>::iterator result_itor = result.begin();
296       result_itor != result.end();
297       ++result_itor)
298   {
299     model.push_back(*result_itor);
300   }
301
302   if (not subanalysis_window.isVisible()) {
303     subanalysis_window.show();
304   }
305 }
306
307 void MussaWindow::editMotifs()
308 {
309   if (motif_editor != 0) {
310     motif_editor->hide();
311     delete motif_editor;
312   }
313   motif_editor = new MotifEditor(analysis);
314   connect(motif_editor, SIGNAL(changedMotifs()), 
315           this, SLOT(updateAnnotations()));
316   motif_editor->show();
317 }
318
319 void MussaWindow::loadMotifList()
320 {
321   QString caption("Load a motif list");
322   QString filter("Motif list(*.txt *.mtl)");
323   QString path = QFileDialog::getOpenFileName(this,
324                                                    caption, 
325                                                    QDir::currentPath(),
326                                                    filter);
327   // user hit cancel?
328   if (path.isNull()) 
329     return;
330   // try to load safely
331   try {
332     fs::path converted_path(path.toStdString(), fs::native);
333     analysis->load_motifs(converted_path);
334   } catch (runtime_error e) {
335     QString msg("Unable to load ");
336     msg += path;
337     msg += "\n";
338     msg += e.what();
339     QMessageBox::warning(this, "Load Motifs", msg);
340   }
341   assert (analysis != 0);
342 }
343
344 void MussaWindow::saveMotifList()
345 {
346   NotImplementedBox();
347 }
348
349 void MussaWindow::loadMupa()
350 {
351   QString caption("Load a mussa parameter file");
352   QString filter("Mussa Parameters (*.mupa)");
353   QString mupa_path = QFileDialog::getOpenFileName(this,
354                                                    caption, 
355                                                    QDir::currentPath(),
356                                                    filter);
357   // user hit cancel?
358   if (mupa_path.isNull()) 
359     return;
360   // try to load safely
361   try {
362     Mussa *m = new Mussa;
363     fs::path converted_path(mupa_path.toStdString(), fs::native);
364     connect(m, SIGNAL(progress(const std::string&, int, int)),
365             this, SLOT(updateProgress(const std::string&, int, int)));
366     m->load_mupa_file(converted_path);
367     m->analyze();
368     setAnalysis(m);
369     setWindowTitle(converted_path.native_file_string().c_str());
370   } catch (mussa_load_error e) {
371     QString msg("Unable to load ");
372     msg += mupa_path;
373     msg += "\n";
374     msg += e.what();
375     QMessageBox::warning(this, "Load Parameter", msg);
376   }
377   assert (analysis != 0);
378 }
379
380 void MussaWindow::loadSavedAnalysis()
381 {
382   QString caption("Load a previously run analysis");
383   QString muway_dir = QFileDialog::getExistingDirectory(this,
384                                                         caption, 
385                                                         QDir::currentPath());
386   // user hit cancel?
387   if (muway_dir.isNull()) 
388     return;
389   // try to safely load
390   try {
391     Mussa *m = new Mussa;
392     fs::path converted_path(muway_dir.toStdString(), fs::native);
393     connect(m, SIGNAL(progress(const std::string&, int, int)),
394             this, SLOT(updateProgress(const std::string&, int, int)));
395     m->load(converted_path);
396     // only switch mussas if we loaded without error
397     setAnalysis(m);
398     setWindowTitle(converted_path.native_file_string().c_str());
399   } catch (mussa_load_error e) {
400     QString msg("Unable to load ");
401     msg += muway_dir;
402     msg += "\n";
403     msg += e.what();
404     QMessageBox::warning(this, "Load Parameter", msg);
405   }
406   assert (analysis != 0);
407 }
408
409 void MussaWindow::newMussaWindow()
410 {
411   Mussa *a = new Mussa();
412   MussaWindow *win = new MussaWindow(a);
413   win->show();
414 }
415
416 void MussaWindow::setSoftThreshold(int threshold)
417 {
418   if (analysis->get_soft_threshold() != threshold) {
419     analysis->set_soft_threshold(threshold);
420     analysis->nway();
421     updateLinks();
422     update();
423   }
424 }
425
426 void MussaWindow::showMussaToolbar()
427 {
428   if (mussaViewTB.isVisible())
429     mussaViewTB.hide();
430   else
431     mussaViewTB.show();
432 }
433
434 void MussaWindow::toggleMotifs()
435 {
436   NotImplementedBox();
437 }
438
439 void MussaWindow::showManual()
440 {
441 #if QT_QTASSISTANT_FOUND
442   manualAssistant->openAssistant();
443 #else
444   try {
445     boost::python::object webopen = get_py()["webbrowser.open"];
446     webopen("http://woldlab.caltech.edu/~king/mussagl_manual/");
447   } catch( boost::python::error_already_set ) {
448     PyErr_Print();
449   }
450 #endif //QT_QTASSISTANT_FOUND
451 }
452
453 void MussaWindow::assistantError(QString message)
454 {
455   //std::cout << "QAssistantError: " << message.toStdString() << "\n";
456   QMessageBox::warning ( this, "Warning: Mussagl Manual", message, 
457                          QMessageBox::Ok, 
458                          QMessageBox::NoButton);
459 }
460
461 void MussaWindow::NotImplementedBox()
462 {
463   QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
464 }      
465
466 void MussaWindow::viewMussaAlignment()
467 {
468   const set<int>& selected_paths = browser.selectedPaths();
469   if (selected_paths.size() == 0 ) {
470     QMessageBox::warning(this, 
471                          QObject::tr("mussa"),
472                          QObject::tr("you should probably select some paths "
473                                      "first"));
474   } else {
475     boost::shared_ptr<MussaAlignedWindow> ma_win( 
476       new MussaAlignedWindow(*analysis, selected_paths)
477     );
478
479     aligned_windows.push_back(ma_win);
480     connect(this, SIGNAL(changedAnnotations()), 
481             aligned_windows.back().get(), SLOT(update()));
482     aligned_windows.back()->show();
483   }
484 }
485                         
486 void MussaWindow::updateAnalysis()
487 {
488   threshold.setRange(analysis->get_threshold(),analysis->get_window());
489
490   const Mussa::vector_sequence_type& seqs = analysis->sequences();
491   browser.setSequences(seqs, analysis->colorMapper());
492   updateLinks();
493   browser.zoomOut();
494   zoom.setValue(browser.zoom());
495 }
496
497 void MussaWindow::updateAnnotations()
498 {
499   // motifs were changed in the sequences by 
500   // Mussa::update_sequences_motifs
501   emit changedAnnotations();
502   browser.update();
503 }
504
505 void MussaWindow::updateLinks()
506 {
507   browser.clear_links();
508   bool reversed = false;
509   const NwayPaths& nway = analysis->paths();
510
511   typedef list<ConservedPath> conserved_paths;
512   typedef conserved_paths::const_iterator const_conserved_paths_itor;
513   for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
514       path_itor != nway.refined_pathz.end();
515       ++path_itor)
516   {
517     // since we were drawing to the start of a window, and opengl lines
518     // are centered around the two connecting points our lines were slightly
519     // offset. the idea of window_offset is to adjust them to the
520     // right for forward compliment or left for reverse compliment
521     // FIXME: figure out how to unit test these computations
522     //GLfloat window_offset = (path_itor->window_size)/2.0;
523
524     size_t track_len = path_itor->track_indexes.size();
525     vector<int> normalized_path;
526     normalized_path.reserve(track_len);
527     vector<bool> rc_flags(false, track_len);
528     for (size_t track_i=0; track_i != track_len; ++track_i)
529     {
530       int x = path_itor->track_indexes[track_i];
531       // at some point when we modify the pathz data structure to keep
532       // track of the score we can put grab the depth here.
533       //
534       // are we reverse complimented?
535       if ( x>=0) {
536         reversed = false;
537       } else {
538         reversed = true;
539         // make positive 
540         x = -x;
541       }
542       normalized_path.push_back(x);
543       rc_flags.push_back(reversed);
544     }
545     browser.link(normalized_path, rc_flags, path_itor->window_size);
546   }
547   browser.update();
548 }
549
550 void 
551 MussaWindow::updateProgress(const string& description, int current, int max)
552 {  
553   // if we're done  
554   if (current == max) {
555     if (progress_dialog != 0) {
556       progress_dialog->hide();
557       delete progress_dialog;
558       progress_dialog = 0;
559     }
560   } else {
561     // if we're starting, create the dialog
562     if (progress_dialog == 0) {
563       QString desc(description.c_str());
564       QString cancel("Cancel");
565       progress_dialog = new QProgressDialog(desc, cancel, current, max, this);
566       progress_dialog->show();
567     } else {
568       // just update the dialog
569       progress_dialog->setValue(current);
570     }
571   }
572   qApp->processEvents();
573 }