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