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