5d934fa36d637536fa33b03ccac033cdaa5c8f72
[mussa.git] / qui / MussaWindow.cpp
1 #include "qui/MussaWindow.hpp"
2 #include "mussa_exceptions.hpp"
3 #include "version.hpp"
4
5 #include <QAction>
6 #include <QApplication>
7 #include <QAssistantClient>
8 #include <QCloseEvent>
9 #include <QDesktopServices>
10 #include <QDir>
11 #include <QFileDialog>
12 #include <QHBoxLayout>
13 #include <QIcon>
14 #include <QMenuBar>
15 #include <QMessageBox>
16 #include <QScrollBar>
17 #include <QStatusBar>
18 #include <QString>
19 #include <QStringList>
20 #include <QUrl>
21 #include <QWhatsThis>
22
23 #include <memory>
24 #include <iterator>
25 #include <iostream>
26
27 #include <boost/filesystem/path.hpp>
28 #include <boost/filesystem/operations.hpp>
29 #include <boost/filesystem/convenience.hpp>
30 namespace fs = boost::filesystem;
31 #include <boost/bind.hpp>
32
33 using namespace std;
34
35 static void init_resources() {
36   static bool resources_loaded = false;
37   if (not resources_loaded) {
38     Q_INIT_RESOURCE(icons);
39     resources_loaded = true;
40   }
41 }
42
43 MussaWindow::MussaWindow(MussaRef analysis_, QWidget *parent) :
44   QMainWindow(parent),
45   analysis(analysis_),
46   motif_editor(0),
47   setup_analysis_dialog(0),
48   browser(0),
49   mussaViewTB(0),
50   zoom(0),
51   threshold(0),
52   progress_dialog(0),
53   aboutAction(0),
54   closeAction(0),
55   createNewAnalysisAction(0),
56   createSubAnalysisAction(0),
57   editMotifsAction(0),
58   loadMotifListAction(0),
59   loadMupaAction(0),
60   loadSavedAnalysisAction(0),
61   mussaManualAssistantAction(0),
62   newMussaWindowAction(0),
63   saveMotifListAction(0),
64   showMussaViewToolbarAction(0),
65   toggleMotifsAction(0),
66   saveBrowserPixmapAction(0),
67   whatsThisAction(0),
68   viewMussaAlignmentAction(0),
69   manualAssistant(0)
70 {
71   init_resources();
72   setupWidgets();
73   setupActions();
74   setupAssistant();
75   setupMainMenu();
76   
77   setWindowIcon(QIcon(":/icons/mussa.png"));
78   default_dir.reset(new QDir(QDir::home().absolutePath()));
79
80   setCentralWidget(browser);
81   // well updatePosition isn't quite right as we really just need
82   // to call update()
83   connect(this, SIGNAL(changedAnnotations()), browser, SLOT(update()));
84   connect(this, SIGNAL(changedMotifs()), this, SLOT(updateAnnotations()));
85   connect(browser, SIGNAL(basepairsCopied(size_t)), 
86           this, SLOT(showBasePairsCopied(size_t)));
87   connect(zoom, SIGNAL(valueChanged(double)), 
88           browser, SLOT(setZoom(double)));
89   mussaViewTB->addWidget(zoom);
90
91   // threshold range is set in updateAnalysis  
92   connect(threshold, SIGNAL(thresholdChanged(int)),
93           this, SLOT(setSoftThreshold(int)));
94   mussaViewTB->addWidget(threshold);
95
96   addToolBar(mussaViewTB);
97
98   statusBar()->showMessage("Welcome to mussa", 2000);
99
100   // FIXME: we should start refactoring the connect call to updateAnalysis or something
101   if (analysis) {
102     connect(analysis.get(), SIGNAL(progress(const QString&, int, int)),
103             this, SLOT(updateProgress(const QString&, int, int)));
104     connect(analysis.get(), SIGNAL(isModified(bool)),
105             this, SLOT(updateAnalysisModified(bool)));        
106   }
107   updateTitle();
108   updateAnalysis();
109 }
110
111 void MussaWindow::setAnalysis(MussaRef new_analysis)
112 {
113   if (new_analysis != 0) {
114     // only switch mussas if we loaded without error
115     clear();
116     analysis.swap(new_analysis);
117     updateTitle();
118     updateAnalysis();
119   }
120 }
121
122 void MussaWindow::setupActions()
123 {
124   // we really don't want to run this more than once.
125   assert (closeAction == 0);
126
127   // the ever popular about box
128   aboutAction = new QAction(tr("&About"), this);
129   connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
130   aboutAction->setIcon(QIcon(":/icons/info.png"));
131
132   // add exit
133   closeAction = new QAction(tr("&Close"), this);
134   closeAction->setStatusTip(tr("Close this window"));
135   connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
136   closeAction->setIcon(QIcon(":/icons/exit.png"));
137   
138   createNewAnalysisAction = new QAction(tr("Create &New Analysis"), this);
139   connect(createNewAnalysisAction, SIGNAL(triggered()), 
140           this, SLOT(createNewAnalysis()));
141   createNewAnalysisAction->setIcon(QIcon(":/icons/filenew.png"));
142   createNewAnalysisAction->setShortcut(Qt::CTRL | Qt::Key_N);
143   
144   createSubAnalysisAction = new QAction(tr("Add to Subanalysis"), this);
145   connect(createSubAnalysisAction, SIGNAL(triggered()), 
146           this, SLOT(createSubAnalysis()));
147
148   saveAnalysisAction = new QAction(tr("&Save Analysis"), this);
149   connect(saveAnalysisAction, SIGNAL(triggered()), 
150           this, SLOT(saveAnalysis()));
151   saveAnalysisAction->setIcon(QIcon(":/icons/filesave.png"));
152   saveAnalysisAction->setShortcut(Qt::CTRL | Qt::Key_S);
153
154   saveAnalysisAsAction = new QAction(tr("Save Analysis &As"), this);
155   connect(saveAnalysisAsAction, SIGNAL(triggered()), 
156           this, SLOT(saveAnalysisAs()));
157   saveAnalysisAsAction->setIcon(QIcon(":/icons/filesave.png"));
158
159   editMotifsAction = new QAction(tr("Edit Motifs"), this);;
160   connect(editMotifsAction, SIGNAL(triggered()), this, SLOT(editMotifs()));
161   
162   loadMotifListAction = new QAction(tr("Open Motif List"), this);
163   connect(loadMotifListAction, SIGNAL(triggered()), 
164           this, SLOT(loadMotifList()));
165   loadMotifListAction->setIcon(QIcon(":/icons/fileopen.png"));
166   
167   loadMupaAction = new QAction(tr("Create Analysis from File"), this);
168   connect(loadMupaAction, SIGNAL(triggered()), 
169           this, SLOT(loadMupa()));
170   loadMupaAction->setIcon(QIcon(":/icons/fileopen.png"));
171
172   loadSavedAnalysisAction = new QAction(tr("&Open Existing &Analysis"), this);
173   connect(loadSavedAnalysisAction, SIGNAL(triggered()), 
174           this, SLOT(loadSavedAnalysis()));
175   loadSavedAnalysisAction->setIcon(QIcon(":/icons/fileopen.png"));
176   loadSavedAnalysisAction->setShortcut(Qt::CTRL | Qt::Key_O);
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   if (browser) {
212     connect(saveBrowserPixmapAction, (SIGNAL(triggered())),
213             browser, SLOT(promptSaveBrowserPixmap()));
214     saveBrowserPixmapAction->setIcon(QIcon(":/icons/image2.png"));
215   }
216
217   viewMussaAlignmentAction = new QAction(tr("View sequence alignment"), this);
218   connect(viewMussaAlignmentAction, SIGNAL(triggered()),
219           this, SLOT(viewMussaAlignment() ));
220   viewMussaAlignmentAction->setWhatsThis(tr("Create a zoomed in window "
221                                             "showing alignment of the seqcomp "
222                                             "defined paths"));
223
224   whatsThisAction = QWhatsThis::createAction(this);
225   whatsThisAction->setIcon(QIcon(":/icons/help.png"));
226
227
228 }
229
230 void MussaWindow::closeEvent(QCloseEvent *event)
231 {
232   if(isClearingAnalysisSafe()) {
233     event->accept();
234   } else {
235     event->ignore();
236   }
237 }
238
239 void MussaWindow::setupMainMenu()
240 {
241   // we need to run setupActions first
242   assert (closeAction != 0);
243
244   QMenu *newMenu = menuBar()->addMenu(tr("&File"));
245   
246   newMenu->addAction(newMussaWindowAction);
247   newMenu->addAction(createNewAnalysisAction);
248   newMenu->addAction(loadMupaAction);
249   newMenu->addAction(loadSavedAnalysisAction);
250   newMenu->addAction(saveAnalysisAction);
251   newMenu->addAction(saveAnalysisAsAction);
252   newMenu->addSeparator();
253   newMenu->addAction(loadMotifListAction);
254   newMenu->addAction(saveMotifListAction);
255   newMenu->addSeparator();
256   newMenu->addAction(saveBrowserPixmapAction);
257   newMenu->addSeparator();
258   newMenu->addAction(closeAction);
259
260   newMenu = menuBar()->addMenu(tr("&Edit"));
261   newMenu->addAction(editMotifsAction);
262   if (browser) newMenu->addAction(browser->getCopySelectedSequenceAsStringAction());
263   if (browser) newMenu->addAction(browser->getCopySelectedSequenceAsFastaAction());
264   newMenu->addAction(createSubAnalysisAction);
265   if (browser) newMenu->addAction(browser->getEditSequencePropertiesAction());
266  
267   newMenu = menuBar()->addMenu(tr("&View"));
268   newMenu->addAction(viewMussaAlignmentAction);
269   newMenu->addAction(showMussaViewToolbarAction);
270
271   newMenu = menuBar()->addMenu(tr("&Help"));
272   newMenu->addAction(mussaManualAssistantAction);
273   newMenu->addAction(whatsThisAction);
274   newMenu->addSeparator();
275   newMenu->addAction(aboutAction);
276
277   // add some extra features to the context menu
278   if (browser) {
279     QMenu *popupMenu = browser->getPopupMenu();
280     if (popupMenu) {
281       popupMenu->addAction(viewMussaAlignmentAction);
282       popupMenu->addAction(createSubAnalysisAction);
283     }
284   }
285 }
286
287 void MussaWindow::setupWidgets()
288 {
289   setup_analysis_dialog = new MussaSetupDialog;
290   subanalysis_window.reset(new SubanalysisWindow(analysis));
291   browser = new SequenceBrowserWidget(default_dir);
292   mussaViewTB = new QToolBar("Path Views", this);
293   zoom = new ZoomWidget(mussaViewTB);
294   threshold = new ThresholdWidget(mussaViewTB);
295 }
296
297 void MussaWindow::setupAssistant()
298 {
299 #if defined(QT_QTASSISTANT_FOUND)
300   QStringList manualAssistantArgs;
301   manualAssistantArgs = QStringList();
302   manualAssistantArgs << "-profile" << "./doc/manual/mussagl_manual.adp";
303   manualAssistant = new QAssistantClient("assistant", this);
304   manualAssistant->setArguments(manualAssistantArgs);
305   connect(manualAssistant, SIGNAL(error(QString)),
306           this, SLOT(assistantError(QString)));
307 #endif
308 }
309   
310 void MussaWindow::about()
311 {
312   QString msg;
313   msg += "Welcome to Multiple Species Sequence Analysis\n";
314   msg += "(c) 2005-2006 California Institute of Technology\n";
315   msg += "Diane Trout, Tristan De Buysscher, Brandon King\n";
316   msg += "Version: ";
317   msg += mussa_version;
318   msg += "\n";
319   msg += "OpenGL: ";
320   msg += (char *)glGetString(GL_VERSION);
321   msg += "\n";
322   QMessageBox::about(this, tr("About mussa"), msg);
323 }
324
325 void MussaWindow::clear()
326 {
327   if (motif_editor != 0) {
328     motif_editor->hide();
329     delete motif_editor;
330   }
331   
332   aligned_windows.clear();
333   browser->clear();
334 }
335
336 void MussaWindow::createNewAnalysis()
337 {
338   try {
339     // ideally we should open a new window if there's an analysis
340     // but this should work for the moment.
341     if (not isClearingAnalysisSafe()) return;
342
343     if (setup_analysis_dialog->exec()) {
344       setAnalysis(setup_analysis_dialog->getMussa());
345     }
346   } catch(mussa_error e) {
347     QString msg(e.what());
348     QMessageBox::warning(this, tr("Create New Analysis"), msg);
349   }
350 }
351
352 void MussaWindow::createSubAnalysis()
353 {
354   list<SequenceLocation> result;
355   SequenceLocationModel& model = subanalysis_window->getModel();
356   browser->copySelectedTracksAsSeqLocation(result);
357   for(list<SequenceLocation>::iterator result_itor = result.begin();
358       result_itor != result.end();
359       ++result_itor)
360   {
361     model.push_back(*result_itor);
362   }
363
364   if (not subanalysis_window->isVisible()) {
365     subanalysis_window->show();
366   }
367 }
368
369 void MussaWindow::saveAnalysis()
370 {
371   // if we've got an analysis
372   if (analysis and not analysis->empty()) {
373     // if it doesn't have a name we need to pick one
374     if (analysis->get_analysis_path().empty()) {
375       saveAnalysisAs();
376     } else {     
377       // if we've got a name, has it changed any?
378       // this doesn't work when a sequence changes something (like its
379       // name)
380       //else if (analysis->is_dirty())
381       try { 
382         analysis->save();
383       } catch (std::exception e) {
384         QMessageBox::critical(this, 
385                               tr("Mussa Save Error"),
386                               tr(e.what()),
387                               QMessageBox::Ok, 0, 0);
388   }
389       
390     }
391   }
392 }
393
394 void MussaWindow::saveAnalysisAs()
395 {
396   std::auto_ptr<QFileDialog> dialog(new QFileDialog(this));
397   dialog->setAcceptMode(QFileDialog::AcceptSave);
398   dialog->setFileMode(QFileDialog::AnyFile);
399   dialog->setDirectory(*default_dir);
400
401   QStringList fileNames;
402   if (not dialog->exec()) {
403     return;
404   }
405   fileNames = dialog->selectedFiles();
406    
407   if (fileNames.size() != 1) {
408     return;
409   }
410   try {
411     fs::path save_path(fileNames[0].toStdString(), fs::native);
412     // do you want to overwrite?
413     if (fs::exists(save_path) and 
414         QMessageBox::question(
415           this,
416           tr("Overwrite File? -- Mussa"),
417           tr("A file called %1 already exists"
418              "do you want to overwrite it?")
419              .arg(fileNames[0]),
420           tr("&Yes"), tr("&No"),
421           QString(), 0, 1)
422         ) {
423       return;
424     }
425     analysis->save(save_path);
426     fs::path normalized_path = (save_path / "..").normalize();   
427     default_dir->setPath(normalized_path.native_directory_string().c_str());
428   } catch (std::exception e) {
429     QMessageBox::critical(this, 
430                           tr("Mussa Save Error"),
431                           tr(e.what()),
432                           QMessageBox::Ok, 0, 0);
433   }
434 }
435
436 bool MussaWindow::isClearingAnalysisSafe()
437 {
438   if (analysis and not analysis->empty() and analysis->is_dirty()) {
439     switch (QMessageBox::question(
440       this,
441       tr("Save Unsaved Changes -- Mussa"),
442       tr("There are unsaved changes,\ndo you want to save?"),
443       tr("&Yes"), tr("&No"), tr("&Cancel"),
444       0, 2)) {
445     case 0:
446       // save
447       saveAnalysis();
448       break;
449     case 1:
450       // don't save
451       break;
452     case 2:
453       // don't replace 
454       return false;
455       break;
456     default:
457       // error
458       throw runtime_error("isClearingAnalysis QMesageBox failure");
459     }
460   } 
461   // if we're here we've been saved and can replace
462   return true;
463 }
464
465 void MussaWindow::editMotifs()
466 {
467   if (not motif_editor) {
468     motif_editor = new MotifEditor(analysis);
469     connect(motif_editor, SIGNAL(changedMotifs()), 
470             this, SLOT(updateAnnotations()));
471   }
472   motif_editor->show();
473 }
474
475 void MussaWindow::loadMotifList()
476 {
477   QString caption("Mussa Load Motifs");
478   QString filter("Motif list(*.txt *.mtl)");
479   QString path = QFileDialog::getOpenFileName(this,
480                                               caption, 
481                                               default_dir->absolutePath(),
482                                               filter);
483   // user hit cancel?
484   if (path.isNull()) 
485     return;
486   // try to load safely
487   try {
488     fs::path converted_path(path.toStdString(), fs::native);
489     analysis->load_motifs(converted_path);
490     default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
491     emit changedMotifs();
492   } catch (std::exception e) {
493     QString msg("Unable to load ");
494     msg += path;
495     msg += "\n";
496     msg += e.what();
497     QMessageBox::warning(this, caption, msg);
498   }
499 }
500
501 void MussaWindow::saveMotifList()
502 {
503   QString caption("Mussa Save Motifs");
504   QString filter("Motif list(*.txt *.mtl)");
505   QString path = QFileDialog::getSaveFileName(this,
506                                               caption, 
507                                               default_dir->absolutePath(),
508                                               filter);
509   // user hit cancel?
510   if (path.isNull()) 
511     return;
512   // try to load safely
513   try {
514     fs::path converted_path(path.toStdString(), fs::native);
515     if (fs::extension(converted_path).size() == 0) {
516       // no extension, so add one
517       fs::path base_path = converted_path.branch_path();
518       fs::path filename(converted_path.leaf() + ".mtl", fs::native);
519       converted_path = base_path / filename;
520     }
521     analysis->save_motifs(converted_path);
522     default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
523   } catch (std::exception e) {
524     QString msg("Unable to save ");
525     msg += path;
526     msg += "\n";
527     msg += e.what();
528     QMessageBox::warning(this, caption, msg);
529   }}
530
531 void MussaWindow::loadMupa()
532 {
533   QString caption("Load a mussa parameter file");
534   QString filter("Mussa Parameters (*.mupa)");
535   QString mupa_path = QFileDialog::getOpenFileName(
536                          this,
537                          caption, 
538                          default_dir->absolutePath(),
539                          filter
540                       );
541   // user hit cancel?
542   if (mupa_path.isNull()) 
543     return;
544   // try to load safely
545   try {
546     // ideally we should open a new window if there's an analysis
547     // but this should work for the moment.
548     if (not isClearingAnalysisSafe()) return;
549
550     MussaRef m = Mussa::init();
551     fs::path converted_path(mupa_path.toStdString(), fs::native);
552     connect(m.get(), SIGNAL(progress(const QString&, int, int)),
553             this, SLOT(updateProgress(const QString&, int, int)));
554     m->load_mupa_file(converted_path);
555     m->analyze();
556     setAnalysis(m);
557     updateTitle();
558     // grab the path ignoring the mupa file portion
559     default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
560   } catch (mussa_load_error e) {
561     QString msg("Unable to load ");
562     msg += mupa_path;
563     msg += "\n";
564     msg += e.what();
565     QMessageBox::warning(this, "Load Parameter", msg);
566   }
567   assert (analysis != 0);
568 }
569
570 void MussaWindow::loadSavedAnalysis()
571 {
572   QString caption("Load a previously run analysis");
573   QString muway_dir = QFileDialog::getExistingDirectory(
574                         this,
575                         caption, 
576                         default_dir->absolutePath()
577                       );
578   // user hit cancel?
579   if (muway_dir.isNull()) 
580     return;
581   // try to safely load
582   try {
583     // ideally we should open a new window if there's an analysis
584     // but this should work for the moment.
585     if (not isClearingAnalysisSafe()) return;
586
587     MussaRef m = Mussa::init();
588     fs::path converted_path(muway_dir.toStdString(), fs::native);
589     connect(m.get(), SIGNAL(progress(const QString&, int, int)),
590             this, SLOT(updateProgress(const QString&, int, int)));
591     m->load(converted_path);
592     // only switch mussas if we loaded without error
593     if (analysis->empty()) {
594       // our current window is empty so load and replace.
595       setAnalysis(m);
596       updateTitle();
597       default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
598     } else {
599       MussaWindow *win = new MussaWindow(m);
600       updateTitle();
601       win->default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
602       win->show();
603     }
604   } catch (boost::filesystem::filesystem_error e) {
605     QString msg("Unable to load ");
606     msg += muway_dir;
607     msg += "\n";
608     msg += e.what();
609     QMessageBox::warning(this, "Load Parameter", msg);    
610   } catch (mussa_load_error e) {
611     QString msg("Unable to load ");
612     msg += muway_dir;
613     msg += "\n";
614     msg += e.what();
615     QMessageBox::warning(this, "Load Parameter", msg);
616   }
617   assert (analysis != 0);
618 }
619
620 void MussaWindow::newMussaWindow()
621 {
622   MussaWindow *win = new MussaWindow(Mussa::init());
623   win->default_dir = default_dir;
624   win->show();
625 }
626
627 void MussaWindow::setSoftThreshold(int value)
628 {
629   if (analysis->get_soft_threshold() != value) {
630     threshold->setEnabled( false );
631     analysis->set_soft_threshold(value);
632     analysis->nway();
633     updateLinks();
634     update();
635     threshold->setEnabled( true );
636   }
637 }
638
639 void MussaWindow::showMussaToolbar()
640 {
641   if (mussaViewTB->isVisible())
642     mussaViewTB->hide();
643   else
644     mussaViewTB->show();
645 }
646
647 void MussaWindow::showBasePairsCopied(size_t bp_copied)
648 {
649   QString msg("Copied ");
650   QString num;
651   num.setNum(bp_copied);
652   msg += num + " base pairs";
653   statusBar()->showMessage(msg, 5000);
654 }
655
656
657 void MussaWindow::toggleMotifs()
658 {
659   NotImplementedBox();
660 }
661
662 void MussaWindow::showManual()
663 {
664 #if defined(QT_QTASSISTANT_FOUND)
665   if (manualAssistant) { 
666     manualAssistant->openAssistant();
667   } else {
668     QMessageBox::warning(this,
669                          tr("Mussa Help Error"),
670                          tr("QtAssistant not setup correctly"),
671                          QMessageBox::Ok,
672                          QMessageBox::NoButton,
673                          QMessageBox::NoButton);
674   }
675 #else
676   QUrl manual_url("http://woldlab.caltech.edu/~king/mussagl_manual/");
677   if (not QDesktopServices::openUrl(manual_url)) {
678     QMessageBox::warning(this,
679                          tr("Mussa Help Error"),
680                          tr("Unable to launch webbrowser"),
681                          QMessageBox::Ok,
682                          QMessageBox::NoButton,
683                          QMessageBox::NoButton);
684   }
685 #endif //QT_QTASSISTANT_FOUND
686 }
687
688 void MussaWindow::assistantError(QString message)
689 {
690   //std::cout << "QAssistantError: " << message.toStdString() << "\n";
691   QMessageBox::warning ( this, "Warning: Mussagl Manual", message, 
692                          QMessageBox::Ok, 
693                          QMessageBox::NoButton);
694 }
695
696 void MussaWindow::NotImplementedBox()
697 {
698   QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
699 }      
700
701 void MussaWindow::viewMussaAlignment()
702 {
703   const set<int>& selected_paths = browser->selectedPaths();
704   if (selected_paths.size() == 0 ) {
705     QMessageBox::warning(this, 
706                          QObject::tr("mussa"),
707                          QObject::tr("you should probably select some paths "
708                                      "first"));
709   } else {
710     MussaAlignedWindowRef ma_win( 
711       new MussaAlignedWindow(analysis, default_dir, selected_paths, subanalysis_window)
712     );
713
714     aligned_windows.push_back(ma_win);
715     connect(this, SIGNAL(changedAnnotations()), 
716             aligned_windows.back().get(), SLOT(update()));
717     aligned_windows.back()->show();
718   }
719 }
720                         
721 void MussaWindow::updateAnalysis()
722 {
723   const Mussa::vector_sequence_type& seqs = analysis->sequences();
724   browser->setSequences(seqs, analysis->colorMapper());
725   assert(browser->sequences().size() == analysis->size());
726
727   // setRange eventually emits something that causes updateLinks to be called
728   // but it's possible for us to not have had a chance to set out sequences
729   // yet.
730   threshold->setRange(analysis->get_threshold(),analysis->get_window());
731   updateLinks();
732   browser->zoomOut();
733   zoom->setValue(browser->zoom());
734 }
735
736 void MussaWindow::updateAnnotations()
737 {
738   // motifs were changed in the sequences by 
739   // Mussa::update_sequences_motifs
740   emit changedAnnotations();
741   browser->update();
742 }
743
744 void MussaWindow::updateLinks()
745 {
746   if(browser->sequences().size() == 0) {
747     // we don't have any sequences load so we have no business setting links
748     return;
749   }
750
751   browser->clear_links();
752   bool reversed = false;
753   const NwayPaths& nway = analysis->paths();
754
755   typedef list<ConservedPath> conserved_paths;
756   typedef conserved_paths::const_iterator const_conserved_paths_itor;
757   for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
758       path_itor != nway.refined_pathz.end();
759       ++path_itor)
760   {
761     // since we were drawing to the start of a window, and opengl lines
762     // are centered around the two connecting points our lines were slightly
763     // offset. the idea of window_offset is to adjust them to the
764     // right for forward compliment or left for reverse compliment
765     // FIXME: figure out how to unit test these computations
766     //GLfloat window_offset = (path_itor->window_size)/2.0;
767
768     size_t track_len = path_itor->track_indexes.size();
769     vector<int> normalized_path;
770     normalized_path.reserve(track_len);
771     vector<bool> rc_flags(false, track_len);
772     for (size_t track_i=0; track_i != track_len; ++track_i)
773     {
774       int x = path_itor->track_indexes[track_i];
775       // at some point when we modify the pathz data structure to keep
776       // track of the score we can put grab the depth here.
777       //
778       // are we reverse complimented?
779       if ( x>=0) {
780         reversed = false;
781       } else {
782         reversed = true;
783         // make positive 
784         x = -x;
785       }
786       normalized_path.push_back(x);
787       rc_flags.push_back(reversed);
788     }
789     browser->link(normalized_path, rc_flags, path_itor->window_size);
790   }
791   browser->update();
792 }
793
794 void 
795 MussaWindow::updateProgress(const QString& description, int current, int max)
796 {  
797   // if we're done  
798   if (current == max) {
799     if (progress_dialog != 0) {
800       progress_dialog->hide();
801       delete progress_dialog;
802       progress_dialog = 0;
803     }
804   } else {
805     // if we're starting, create the dialog
806     if (progress_dialog == 0) {
807       QString cancel("Cancel");
808       progress_dialog = new QProgressDialog(description, cancel, current, max, this);
809       progress_dialog->show();
810     } else {
811       // just update the dialog
812       progress_dialog->setValue(current);
813     }
814   }
815   qApp->processEvents();
816 }
817
818 void MussaWindow::updateAnalysisModified(bool is_modified)
819 {
820   setWindowModified(is_modified);
821 }
822
823 void MussaWindow::updateTitle()
824 {
825   if (analysis) {
826     QString title(analysis->get_title().c_str());
827     title += "[*]";
828     setWindowTitle(title);
829   }
830 }