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