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