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