Mussagl Manual QAssistant Patch
[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(0, 0, Mussa::TransitiveNway, 0.0);
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_thres() != threshold) {
405     analysis->set_soft_thres(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 << "Error922: " << message.toStdString() << "\n";
433 }
434
435 void MussaWindow::NotImplementedBox()
436 {
437   QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
438 }      
439
440 void MussaWindow::viewMussaAlignment()
441 {
442   const set<int>& selected_paths = browser.selectedPaths();
443   if (selected_paths.size() == 0 ) {
444     QMessageBox::warning(this, 
445                          QObject::tr("mussa"),
446                          QObject::tr("you should probably select some paths "
447                                      "first"));
448   } else {
449     MussaAlignedWindow *ma_win = new MussaAlignedWindow(*analysis, 
450                                                         selected_paths);
451     connect(this, SIGNAL(changedAnnotations()), 
452             ma_win, SLOT(update()));
453     aligned_windows.push_back(ma_win);
454     ma_win->show();
455   }
456 }
457                         
458 void MussaWindow::updateAnalysis()
459 {
460   threshold.setRange(analysis->get_threshold(),analysis->get_window());
461   for (list<MussaAlignedWindow *>::iterator maw_i = aligned_windows.begin();
462        maw_i != aligned_windows.end();
463        ++maw_i)
464   {
465     (*maw_i)->hide();
466     delete *maw_i;
467   }
468
469   browser.clear();
470   const vector<Sequence>& seqs = analysis->sequences();
471   browser.setSequences(seqs, analysis->colorMapper());
472   updateLinks();
473   browser.zoomOut();
474   zoom.setValue(browser.zoom());
475 }
476
477 void MussaWindow::updateAnnotations()
478 {
479   // motifs were changed in the sequences by 
480   // Mussa::update_sequences_motifs
481   emit changedAnnotations();
482   browser.update();
483 }
484
485 void MussaWindow::updateLinks()
486 {
487   browser.clear_links();
488   bool reversed = false;
489   const NwayPaths& nway = analysis->paths();
490
491   typedef list<ConservedPath> conserved_paths;
492   typedef conserved_paths::const_iterator const_conserved_paths_itor;
493   for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
494       path_itor != nway.refined_pathz.end();
495       ++path_itor)
496   {
497     // since we were drawing to the start of a window, and opengl lines
498     // are centered around the two connecting points our lines were slightly
499     // offset. the idea of window_offset is to adjust them to the
500     // right for forward compliment or left for reverse compliment
501     // FIXME: figure out how to unit test these computations
502     //GLfloat window_offset = (path_itor->window_size)/2.0;
503
504     size_t track_len = path_itor->track_indexes.size();
505     vector<int> normalized_path;
506     normalized_path.reserve(track_len);
507     vector<bool> rc_flags(false, track_len);
508     for (size_t track_i=0; track_i != track_len; ++track_i)
509     {
510       int x = path_itor->track_indexes[track_i];
511       // at some point when we modify the pathz data structure to keep
512       // track of the score we can put grab the depth here.
513       //
514       // are we reverse complimented?
515       if ( x>=0) {
516         reversed = false;
517       } else {
518         reversed = true;
519         // make positive 
520         x = -x;
521       }
522       normalized_path.push_back(x);
523       rc_flags.push_back(reversed);
524     }
525     browser.link(normalized_path, rc_flags, path_itor->window_size);
526   }
527   browser.update();
528 }
529