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