partial implementation of sequence track copy
[mussa.git] / qui / MussaWindow.cpp
1 #include <QAction>
2 #include <QApplication>
3 #include <QAssistantClient>
4 #include <QDir>
5 #include <QFileDialog>
6 #include <QHBoxLayout>
7 #include <QIcon>
8 #include <QMenuBar>
9 #include <QMessageBox>
10 #include <QScrollBar>
11 #include <QStatusBar>
12 #include <QString>
13 #include <QStringList>
14 #include <QWhatsThis>
15
16 #include "qui/MussaWindow.hpp"
17 #include "mussa_exceptions.hpp"
18
19 #include <iostream>
20
21 #include <boost/filesystem/path.hpp>
22 namespace fs = boost::filesystem;
23
24 using namespace std;
25
26 MussaWindow::MussaWindow(Mussa *analysis_, QWidget *parent) :
27   QMainWindow(parent),
28   analysis(analysis_),
29   motif_editor(0),
30   browser(this),
31   mussaViewTB("Path Views"),
32   zoom(),
33   threshold(),
34   progress_dialog(0),
35   aboutAction(0),
36   copySelectedSequenceAsFastaAction(0),
37   closeAction(0),
38   createNewAnalysisAction(0),
39   createSubAnalysisAction(0),
40   editMotifsAction(0),
41   loadMotifListAction(0),
42   loadMupaAction(0),
43   loadSavedAnalysisAction(0),
44   mussaManualAssistantAction(0),
45   newMussaWindowAction(0),
46   saveMotifListAction(0),
47   showMussaViewToolbarAction(0),
48   toggleMotifsAction(0),
49   saveBrowserPixmapAction(0),
50   whatsThisAction(0),
51   viewMussaAlignmentAction(0),
52   manualAssistant(0)
53 {
54
55   setupActions();
56   setupMainMenu();
57   setupAssistant();
58
59   //This next setWhatsThis function prevents
60   // a segfault when using WhatsThis feature with 
61   // opengl widget.
62   //scene->setWhatsThis(tr("Mussa in OpenGL!"));
63   setCentralWidget(&browser);
64   // well updatePosition isn't quite right as we really just need
65   // to call update()
66   connect(this, SIGNAL(changedAnnotations()), &browser, SLOT(update()));
67
68   mussaViewTB.addAction(toggleMotifsAction);
69   mussaViewTB.addWidget(&zoom);
70   
71   connect(&zoom, SIGNAL(valueChanged(double)), 
72           &browser, SLOT(setZoom(double)));
73   
74   // threshold range is set in updateAnalysis
75   
76   //scene->setClipPlane(20);
77   // FIXME: for when we get the paths drawn at the appropriate depth
78   //connect(&threshold, SIGNAL(thresholdChanged(int)),
79   //        this, SLOT(setClipPlane(int)));
80   connect(&threshold, SIGNAL(thresholdChanged(int)),
81           this, SLOT(setSoftThreshold(int)));
82   mussaViewTB.addWidget(&threshold);
83
84   addToolBar(&mussaViewTB);
85
86   statusBar()->showMessage("Welcome to mussa", 2000);
87   connect(analysis, SIGNAL(progress(const std::string&, int, int)),
88           this, SLOT(updateProgress(const std::string&, int, int)));
89   updateAnalysis();
90 }
91
92 MussaWindow::~MussaWindow()
93 {
94   if (analysis != 0) delete analysis;
95   aligned_windows.clear();
96   if (motif_editor != 0) delete motif_editor;
97   if (progress_dialog != 0) delete progress_dialog;
98
99   if (aboutAction != 0) delete aboutAction;
100   if (copySelectedSequenceAsFastaAction != 0) 
101     delete copySelectedSequenceAsFastaAction;
102   if (closeAction != 0) delete closeAction;
103   if (createNewAnalysisAction != 0) delete createNewAnalysisAction;
104   if (createSubAnalysisAction != 0) delete createSubAnalysisAction;
105   if (editMotifsAction != 0) delete editMotifsAction;
106   if (loadMotifListAction != 0) delete loadMotifListAction;
107   if (loadMupaAction != 0) delete loadMupaAction;
108   if (loadSavedAnalysisAction != 0) delete loadSavedAnalysisAction;
109   if (mussaManualAssistantAction != 0) delete mussaManualAssistantAction;
110   if (newMussaWindowAction != 0) delete newMussaWindowAction;
111   if (saveBrowserPixmapAction != 0) delete saveBrowserPixmapAction;
112   if (saveMotifListAction != 0) delete saveMotifListAction;
113   if (showMussaViewToolbarAction != 0) delete showMussaViewToolbarAction;
114   if (toggleMotifsAction != 0) delete toggleMotifsAction;
115   if (whatsThisAction != 0) delete whatsThisAction;
116   if (viewMussaAlignmentAction != 0) delete viewMussaAlignmentAction;
117   
118   if (manualAssistant != 0) delete manualAssistant;
119   
120 }
121
122 void MussaWindow::setAnalysis(Mussa *new_analysis)
123 {
124   if (new_analysis != 0) {
125     // only switch mussas if we loaded without error
126     delete analysis;
127     analysis = new_analysis;
128     setWindowTitle(analysis->get_name().c_str());
129     updateAnalysis();
130   }
131 }
132
133 void MussaWindow::setupActions()
134 {
135   // we really don't want to run this more than once.
136   assert (closeAction == 0);
137
138   // the ever popular about box
139   aboutAction = new QAction(tr("&About"), this);
140   connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
141   aboutAction->setIcon(QIcon(":/icons/info.png"));
142
143   // copy sequence
144   copySelectedSequenceAsFastaAction = new QAction(tr("&Copy As Fasta"), this);
145   connect(copySelectedSequenceAsFastaAction, SIGNAL(triggered()), 
146           &browser, SLOT(copySelectedSequenceAsFasta()));
147
148   // add exit
149   closeAction = new QAction(tr("&Close"), this);
150   closeAction->setStatusTip(tr("Close this window"));
151   connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
152   closeAction->setIcon(QIcon(":/icons/exit.png"));
153   
154   createNewAnalysisAction = new QAction(tr("Create Analysis"), this);
155   connect(createNewAnalysisAction, SIGNAL(triggered()), 
156           this, SLOT(createNewAnalysis()));
157   createNewAnalysisAction->setIcon(QIcon(":/icons/filenew.png"));
158   
159   createSubAnalysisAction = new QAction(tr("Define SubAnalysis"), this);
160   connect(createSubAnalysisAction, SIGNAL(triggered()), 
161           this, SLOT(createSubAnalysis()));
162
163   editMotifsAction = new QAction(tr("Edit Motifs"), this);;
164   connect(editMotifsAction, SIGNAL(triggered()), this, SLOT(editMotifs()));
165   
166   loadMotifListAction = new QAction(tr("Load Motif List"), this);
167   connect(loadMotifListAction, SIGNAL(triggered()), 
168           this, SLOT(loadMotifList()));
169   loadMotifListAction->setIcon(QIcon(":/icons/fileopen.png"));
170   
171   loadMupaAction = new QAction(tr("Create Analysis from File"), this);
172   connect(loadMupaAction, SIGNAL(triggered()), 
173           this, SLOT(loadMupa()));
174   loadMupaAction->setIcon(QIcon(":/icons/fileopen.png"));
175
176   loadSavedAnalysisAction = new QAction(tr("Load Existing &Analysis"), this);
177   connect(loadSavedAnalysisAction, SIGNAL(triggered()), 
178           this, SLOT(loadSavedAnalysis()));
179   loadSavedAnalysisAction->setIcon(QIcon(":/icons/fileopen.png"));
180
181   mussaManualAssistantAction = new QAction(tr("Mussagl Manual..."), this);
182   mussaManualAssistantAction->setIcon(QIcon(":/icons/contents.png"));
183   connect(mussaManualAssistantAction, SIGNAL(triggered()),
184           this, SLOT(showManual()));
185
186   newMussaWindowAction = new QAction(tr("&New Mussa Window"), this);
187   newMussaWindowAction->setStatusTip("open another mussa window to allow comparing results");
188   connect(newMussaWindowAction, SIGNAL(triggered()), 
189           this, SLOT(newMussaWindow()));
190   newMussaWindowAction->setIcon(QIcon(":/icons/window_new.png"));
191
192   saveMotifListAction = new QAction(tr("Save Motifs"), this);
193   connect(saveMotifListAction, SIGNAL(triggered()), 
194           this, SLOT(saveMotifList()));
195   saveMotifListAction->setIcon(QIcon(":/icons/filesave.png"));
196
197   showMussaViewToolbarAction = new QAction(tr("Show Toolbar"), this);
198   connect(showMussaViewToolbarAction, SIGNAL(triggered()), 
199           this, SLOT(showMussaToolbar()));
200   showMussaViewToolbarAction->setCheckable(true);
201   showMussaViewToolbarAction->setChecked(true);
202
203   toggleMotifsAction = new QAction(tr("Toggle Motifs"), this);
204   connect(toggleMotifsAction, SIGNAL(triggered()), 
205           this, SLOT(toggleMotifs()));
206   toggleMotifsAction->setCheckable(true);
207   toggleMotifsAction->setIcon(QIcon(":/icons/motif_icon.png"));
208   toggleMotifsAction->setWhatsThis(tr("Toggle motif annotations on/off\n\n"
209                                    "You can load motif annotations via "
210                                    "'File->Load Motif List' menu option."));
211
212   //Save pixel map action
213   saveBrowserPixmapAction = new QAction(tr("Save to image..."), this);
214   connect(saveBrowserPixmapAction, (SIGNAL(triggered())),
215           &browser, SLOT(promptSaveBrowserPixmap()));
216   saveBrowserPixmapAction->setIcon(QIcon(":/icons/image2.png"));
217
218   viewMussaAlignmentAction = new QAction(tr("View mussa alignment"), this);
219   connect(viewMussaAlignmentAction, SIGNAL(triggered()),
220           this, SLOT(viewMussaAlignment() ));
221   viewMussaAlignmentAction->setWhatsThis(tr("Create a zoomed in window "
222                                             "showing alignment of the seqcomp "
223                                             "defined paths"));
224
225   whatsThisAction = QWhatsThis::createAction(this);
226   whatsThisAction->setIcon(QIcon(":/icons/help.png"));
227
228
229 }
230
231 void MussaWindow::setupMainMenu()
232 {
233   // we need to run setupActions first
234   assert (closeAction != 0);
235
236   QMenu *newMenu = menuBar()->addMenu(tr("&File"));
237   
238   newMenu->addAction(newMussaWindowAction);
239   newMenu->addAction(createNewAnalysisAction);
240   newMenu->addAction(loadMupaAction);
241   newMenu->addAction(loadSavedAnalysisAction);
242   //newMenu->addAction(createSubAnalysisAction);
243   newMenu->addSeparator();
244   newMenu->addAction(loadMotifListAction);
245   newMenu->addAction(saveMotifListAction);
246   newMenu->addSeparator();
247   newMenu->addAction(saveBrowserPixmapAction);
248   newMenu->addSeparator();
249   newMenu->addAction(closeAction);
250
251   newMenu = menuBar()->addMenu(tr("&Edit"));
252   newMenu->addAction(copySelectedSequenceAsFastaAction);
253  
254   newMenu = menuBar()->addMenu(tr("&View"));
255   newMenu->addAction(editMotifsAction);
256   newMenu->addAction(viewMussaAlignmentAction);
257   newMenu->addAction(showMussaViewToolbarAction);
258
259   newMenu = menuBar()->addMenu(tr("&Help"));
260   newMenu->addAction(mussaManualAssistantAction);
261   newMenu->addAction(whatsThisAction);
262   newMenu->addSeparator();
263   newMenu->addAction(aboutAction);
264 }
265
266 void MussaWindow::setupAssistant()
267 {
268   QStringList manualAssistantArgs;
269   manualAssistantArgs = QStringList();
270   manualAssistantArgs << "-profile" << "./doc/manual/mussagl_manual.adp";
271   manualAssistant = new QAssistantClient("assistant", this);
272   manualAssistant->setArguments(manualAssistantArgs);
273   connect(manualAssistant, SIGNAL(error(QString)),
274           this, SLOT(assistantError(QString)));
275 }
276   
277 void MussaWindow::about()
278 {
279   QString msg("Welcome to Multiple Species Sequence Analysis\n"
280               "(c) 2005-2006 California Institute of Technology\n"
281               "Tristan De Buysscher, Diane Trout\n");
282    msg += "\n\r";
283    msg += "Path: ";
284    msg += QDir::currentPath();
285    msg += "\n";
286    msg += "OpenGL: ";
287    msg += (char *)glGetString(GL_VERSION);
288    msg += "\n";
289    QMessageBox::about(this, tr("About mussa"), msg);
290 }
291
292 void MussaWindow::createNewAnalysis()
293 {
294   try {
295     if (setup_analysis_dialog.exec()) {
296       Mussa *m = 0;
297       m = setup_analysis_dialog.getMussa();
298       setAnalysis(m);
299     } else {
300       std::cout << "New mussa exp. aborted!\n";
301     }
302   } catch(mussa_error e) {
303     QString msg(e.what());
304     QMessageBox::warning(this, tr("Create New Analysis"), msg);
305   }
306 }
307
308 void MussaWindow::createSubAnalysis()
309 {
310   NotImplementedBox();
311 }
312
313 void MussaWindow::editMotifs()
314 {
315   if (motif_editor != 0) {
316     motif_editor->hide();
317     delete motif_editor;
318   }
319   motif_editor = new MotifEditor(analysis);
320   connect(motif_editor, SIGNAL(changedMotifs()), 
321           this, SLOT(updateAnnotations()));
322   motif_editor->show();
323 }
324
325 void MussaWindow::loadMotifList()
326 {
327   QString caption("Load a motif list");
328   QString filter("Motif list(*.txt *.mtl)");
329   QString path = QFileDialog::getOpenFileName(this,
330                                                    caption, 
331                                                    QDir::currentPath(),
332                                                    filter);
333   // user hit cancel?
334   if (path.isNull()) 
335     return;
336   // try to load safely
337   try {
338     fs::path converted_path(path.toStdString(), fs::native);
339     analysis->load_motifs(converted_path);
340   } catch (runtime_error e) {
341     QString msg("Unable to load ");
342     msg += path;
343     msg += "\n";
344     msg += e.what();
345     QMessageBox::warning(this, "Load Motifs", msg);
346   }
347   assert (analysis != 0);
348 }
349
350 void MussaWindow::saveMotifList()
351 {
352   NotImplementedBox();
353 }
354
355 void MussaWindow::loadMupa()
356 {
357   QString caption("Load a mussa parameter file");
358   QString filter("Mussa Parameters (*.mupa)");
359   QString mupa_path = QFileDialog::getOpenFileName(this,
360                                                    caption, 
361                                                    QDir::currentPath(),
362                                                    filter);
363   // user hit cancel?
364   if (mupa_path.isNull()) 
365     return;
366   // try to load safely
367   try {
368     Mussa *m = new Mussa;
369     fs::path converted_path(mupa_path.toStdString(), fs::native);
370     connect(m, SIGNAL(progress(const std::string&, int, int)),
371             this, SLOT(updateProgress(const std::string&, int, int)));
372     m->load_mupa_file(converted_path);
373     m->analyze();
374     setAnalysis(m);
375     setWindowTitle(converted_path.native_file_string().c_str());
376   } catch (mussa_load_error e) {
377     QString msg("Unable to load ");
378     msg += mupa_path;
379     msg += "\n";
380     msg += e.what();
381     QMessageBox::warning(this, "Load Parameter", msg);
382   }
383   assert (analysis != 0);
384 }
385
386 void MussaWindow::loadSavedAnalysis()
387 {
388   QString caption("Load a previously run analysis");
389   QString muway_dir = QFileDialog::getExistingDirectory(this,
390                                                         caption, 
391                                                         QDir::currentPath());
392   // user hit cancel?
393   if (muway_dir.isNull()) 
394     return;
395   // try to safely load
396   try {
397     Mussa *m = new Mussa;
398     fs::path converted_path(muway_dir.toStdString(), fs::native);
399     connect(m, SIGNAL(progress(const std::string&, int, int)),
400             this, SLOT(updateProgress(const std::string&, int, int)));
401     m->load(converted_path);
402     // only switch mussas if we loaded without error
403     setAnalysis(m);
404     setWindowTitle(converted_path.native_file_string().c_str());
405   } catch (mussa_load_error e) {
406     QString msg("Unable to load ");
407     msg += muway_dir;
408     msg += "\n";
409     msg += e.what();
410     QMessageBox::warning(this, "Load Parameter", msg);
411   }
412   assert (analysis != 0);
413 }
414
415 void MussaWindow::newMussaWindow()
416 {
417   Mussa *a = new Mussa();
418   MussaWindow *win = new MussaWindow(a);
419   win->show();
420 }
421
422 void MussaWindow::setSoftThreshold(int threshold)
423 {
424   if (analysis->get_soft_threshold() != threshold) {
425     analysis->set_soft_threshold(threshold);
426     analysis->nway();
427     updateLinks();
428     update();
429   }
430 }
431
432 void MussaWindow::showMussaToolbar()
433 {
434   if (mussaViewTB.isVisible())
435     mussaViewTB.hide();
436   else
437     mussaViewTB.show();
438 }
439
440 void MussaWindow::toggleMotifs()
441 {
442   NotImplementedBox();
443 }
444
445 void MussaWindow::showManual()
446 {
447   manualAssistant->openAssistant();
448 }
449
450 void MussaWindow::assistantError(QString message)
451 {
452   //std::cout << "QAssistantError: " << message.toStdString() << "\n";
453   QMessageBox::warning ( this, "Warning: Mussagl Manual", message, 
454                          QMessageBox::Ok, 
455                          QMessageBox::NoButton);
456 }
457
458 void MussaWindow::NotImplementedBox()
459 {
460   QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
461 }      
462
463 void MussaWindow::viewMussaAlignment()
464 {
465   const set<int>& selected_paths = browser.selectedPaths();
466   if (selected_paths.size() == 0 ) {
467     QMessageBox::warning(this, 
468                          QObject::tr("mussa"),
469                          QObject::tr("you should probably select some paths "
470                                      "first"));
471   } else {
472     MussaAlignedWindow *ma_win = new MussaAlignedWindow(*analysis, 
473                                                         selected_paths);
474     connect(this, SIGNAL(changedAnnotations()), 
475             ma_win, SLOT(update()));
476     aligned_windows.push_back(ma_win);
477     ma_win->show();
478   }
479 }
480                         
481 void MussaWindow::updateAnalysis()
482 {
483   threshold.setRange(analysis->get_threshold(),analysis->get_window());
484   for (list<MussaAlignedWindow *>::iterator maw_i = aligned_windows.begin();
485        maw_i != aligned_windows.end();
486        ++maw_i)
487   {
488     (*maw_i)->hide();
489     delete *maw_i;
490   }
491
492   browser.clear();
493   const vector<Sequence>& seqs = analysis->sequences();
494   browser.setSequences(seqs, analysis->colorMapper());
495   updateLinks();
496   browser.zoomOut();
497   zoom.setValue(browser.zoom());
498 }
499
500 void MussaWindow::updateAnnotations()
501 {
502   // motifs were changed in the sequences by 
503   // Mussa::update_sequences_motifs
504   emit changedAnnotations();
505   browser.update();
506 }
507
508 void MussaWindow::updateLinks()
509 {
510   browser.clear_links();
511   bool reversed = false;
512   const NwayPaths& nway = analysis->paths();
513
514   typedef list<ConservedPath> conserved_paths;
515   typedef conserved_paths::const_iterator const_conserved_paths_itor;
516   for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
517       path_itor != nway.refined_pathz.end();
518       ++path_itor)
519   {
520     // since we were drawing to the start of a window, and opengl lines
521     // are centered around the two connecting points our lines were slightly
522     // offset. the idea of window_offset is to adjust them to the
523     // right for forward compliment or left for reverse compliment
524     // FIXME: figure out how to unit test these computations
525     //GLfloat window_offset = (path_itor->window_size)/2.0;
526
527     size_t track_len = path_itor->track_indexes.size();
528     vector<int> normalized_path;
529     normalized_path.reserve(track_len);
530     vector<bool> rc_flags(false, track_len);
531     for (size_t track_i=0; track_i != track_len; ++track_i)
532     {
533       int x = path_itor->track_indexes[track_i];
534       // at some point when we modify the pathz data structure to keep
535       // track of the score we can put grab the depth here.
536       //
537       // are we reverse complimented?
538       if ( x>=0) {
539         reversed = false;
540       } else {
541         reversed = true;
542         // make positive 
543         x = -x;
544       }
545       normalized_path.push_back(x);
546       rc_flags.push_back(reversed);
547     }
548     browser.link(normalized_path, rc_flags, path_itor->window_size);
549   }
550   browser.update();
551 }
552
553 void 
554 MussaWindow::updateProgress(const string& description, int current, int max)
555 {  
556   // if we're done  
557   if (current == max) {
558     if (progress_dialog != 0) {
559       progress_dialog->hide();
560       delete progress_dialog;
561       progress_dialog = 0;
562     }
563   } else {
564     // if we're starting, create the dialog
565     if (progress_dialog == 0) {
566       QString desc(description.c_str());
567       QString cancel("Cancel");
568       progress_dialog = new QProgressDialog(desc, cancel, current, max, this);
569       progress_dialog->show();
570     } else {
571       // just update the dialog
572       progress_dialog->setValue(current);
573     }
574   }
575   qApp->processEvents();
576 }