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