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