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