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