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