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