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