Added Qt version to Mussagl about box.
[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 += "Qt: ";
320   msg += qVersion();
321   msg += "\n";
322   msg += "OpenGL: ";
323   msg += (char *)glGetString(GL_VERSION);
324   msg += "\n";
325   QMessageBox::about(this, tr("About mussa"), msg);
326 }
327
328 void MussaWindow::clear()
329 {
330   if (motif_editor != 0) {
331     motif_editor->hide();
332     delete motif_editor;
333   }
334   
335   aligned_windows.clear();
336   browser->clear();
337 }
338
339 void MussaWindow::createNewAnalysis()
340 {
341   try {
342     // ideally we should open a new window if there's an analysis
343     // but this should work for the moment.
344     if (not isClearingAnalysisSafe()) return;
345
346     if (setup_analysis_dialog->exec()) {
347       setAnalysis(setup_analysis_dialog->getMussa());
348     }
349   } catch(mussa_error e) {
350     QString msg(e.what());
351     QMessageBox::warning(this, tr("Create New Analysis"), msg);
352   }
353 }
354
355 void MussaWindow::createSubAnalysis()
356 {
357   list<SequenceLocation> result;
358   SequenceLocationModel& model = subanalysis_window->getModel();
359   browser->copySelectedTracksAsSeqLocation(result);
360   for(list<SequenceLocation>::iterator result_itor = result.begin();
361       result_itor != result.end();
362       ++result_itor)
363   {
364     model.push_back(*result_itor);
365   }
366
367   if (not subanalysis_window->isVisible()) {
368     subanalysis_window->show();
369   }
370 }
371
372 void MussaWindow::saveAnalysis()
373 {
374   // if we've got an analysis
375   if (analysis and not analysis->empty()) {
376     // if it doesn't have a name we need to pick one
377     if (analysis->get_analysis_path().empty()) {
378       saveAnalysisAs();
379     } else {     
380       // if we've got a name, has it changed any?
381       // this doesn't work when a sequence changes something (like its
382       // name)
383       //else if (analysis->is_dirty())
384       try { 
385         analysis->save();
386       } catch (std::exception e) {
387         QMessageBox::critical(this, 
388                               tr("Mussa Save Error"),
389                               tr(e.what()),
390                               QMessageBox::Ok, 0, 0);
391   }
392       
393     }
394   }
395 }
396
397 void MussaWindow::saveAnalysisAs()
398 {
399   std::auto_ptr<QFileDialog> dialog(new QFileDialog(this));
400   dialog->setAcceptMode(QFileDialog::AcceptSave);
401   dialog->setFileMode(QFileDialog::AnyFile);
402   dialog->setDirectory(*default_dir);
403
404   QStringList fileNames;
405   if (not dialog->exec()) {
406     return;
407   }
408   fileNames = dialog->selectedFiles();
409    
410   if (fileNames.size() != 1) {
411     return;
412   }
413   try {
414     fs::path save_path(fileNames[0].toStdString(), fs::native);
415     // do you want to overwrite?
416     if (fs::exists(save_path) and 
417         QMessageBox::question(
418           this,
419           tr("Overwrite File? -- Mussa"),
420           tr("A file called %1 already exists"
421              "do you want to overwrite it?")
422              .arg(fileNames[0]),
423           tr("&Yes"), tr("&No"),
424           QString(), 0, 1)
425         ) {
426       return;
427     }
428     analysis->save(save_path);
429     fs::path normalized_path = (save_path / "..").normalize();   
430     default_dir->setPath(normalized_path.native_directory_string().c_str());
431   } catch (std::exception e) {
432     QMessageBox::critical(this, 
433                           tr("Mussa Save Error"),
434                           tr(e.what()),
435                           QMessageBox::Ok, 0, 0);
436   }
437 }
438
439 bool MussaWindow::isClearingAnalysisSafe()
440 {
441   if (analysis and not analysis->empty() and analysis->is_dirty()) {
442     switch (QMessageBox::question(
443       this,
444       tr("Save Unsaved Changes -- Mussa"),
445       tr("There are unsaved changes,\ndo you want to save?"),
446       tr("&Yes"), tr("&No"), tr("&Cancel"),
447       0, 2)) {
448     case 0:
449       // save
450       saveAnalysis();
451       break;
452     case 1:
453       // don't save
454       break;
455     case 2:
456       // don't replace 
457       return false;
458       break;
459     default:
460       // error
461       throw runtime_error("isClearingAnalysis QMesageBox failure");
462     }
463   } 
464   // if we're here we've been saved and can replace
465   return true;
466 }
467
468 void MussaWindow::editMotifs()
469 {
470   if (not motif_editor) {
471     motif_editor = new MotifEditor(analysis);
472     connect(motif_editor, SIGNAL(changedMotifs()), 
473             this, SLOT(updateAnnotations()));
474   }
475   motif_editor->show();
476 }
477
478 void MussaWindow::loadMotifList()
479 {
480   QString caption("Mussa Load Motifs");
481   QString filter("Motif list(*.txt *.mtl)");
482   QString path = QFileDialog::getOpenFileName(this,
483                                               caption, 
484                                               default_dir->absolutePath(),
485                                               filter);
486   // user hit cancel?
487   if (path.isNull()) 
488     return;
489   // try to load safely
490   try {
491     fs::path converted_path(path.toStdString(), fs::native);
492     analysis->load_motifs(converted_path);
493     default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
494     emit changedMotifs();
495   } catch (std::exception e) {
496     QString msg("Unable to load ");
497     msg += path;
498     msg += "\n";
499     msg += e.what();
500     QMessageBox::warning(this, caption, msg);
501   }
502 }
503
504 void MussaWindow::saveMotifList()
505 {
506   QString caption("Mussa Save Motifs");
507   QString filter("Motif list(*.txt *.mtl)");
508   QString path = QFileDialog::getSaveFileName(this,
509                                               caption, 
510                                               default_dir->absolutePath(),
511                                               filter);
512   // user hit cancel?
513   if (path.isNull()) 
514     return;
515   // try to load safely
516   try {
517     fs::path converted_path(path.toStdString(), fs::native);
518     if (fs::extension(converted_path).size() == 0) {
519       // no extension, so add one
520       fs::path base_path = converted_path.branch_path();
521       fs::path filename(converted_path.leaf() + ".mtl", fs::native);
522       converted_path = base_path / filename;
523     }
524     analysis->save_motifs(converted_path);
525     default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
526   } catch (std::exception e) {
527     QString msg("Unable to save ");
528     msg += path;
529     msg += "\n";
530     msg += e.what();
531     QMessageBox::warning(this, caption, msg);
532   }}
533
534 void MussaWindow::loadMupa()
535 {
536   QString caption("Load a mussa parameter file");
537   QString filter("Mussa Parameters (*.mupa)");
538   QString mupa_path = QFileDialog::getOpenFileName(
539                          this,
540                          caption, 
541                          default_dir->absolutePath(),
542                          filter
543                       );
544   // user hit cancel?
545   if (mupa_path.isNull()) 
546     return;
547   // try to load safely
548   try {
549     // ideally we should open a new window if there's an analysis
550     // but this should work for the moment.
551     if (not isClearingAnalysisSafe()) return;
552
553     MussaRef m = Mussa::init();
554     fs::path converted_path(mupa_path.toStdString(), fs::native);
555     connect(m.get(), SIGNAL(progress(const QString&, int, int)),
556             this, SLOT(updateProgress(const QString&, int, int)));
557     m->load_mupa_file(converted_path);
558     m->analyze();
559     setAnalysis(m);
560     updateTitle();
561     // grab the path ignoring the mupa file portion
562     default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
563   } catch (mussa_load_error e) {
564     QString msg("Unable to load ");
565     msg += mupa_path;
566     msg += "\n";
567     msg += e.what();
568     QMessageBox::warning(this, "Load Parameter", msg);
569   }
570   assert (analysis != 0);
571 }
572
573 void MussaWindow::loadSavedAnalysis()
574 {
575   QString caption("Load a previously run analysis");
576   QString muway_dir = QFileDialog::getExistingDirectory(
577                         this,
578                         caption, 
579                         default_dir->absolutePath()
580                       );
581   // user hit cancel?
582   if (muway_dir.isNull()) 
583     return;
584   // try to safely load
585   try {
586     // ideally we should open a new window if there's an analysis
587     // but this should work for the moment.
588     if (not isClearingAnalysisSafe()) return;
589
590     MussaRef m = Mussa::init();
591     fs::path converted_path(muway_dir.toStdString(), fs::native);
592     connect(m.get(), SIGNAL(progress(const QString&, int, int)),
593             this, SLOT(updateProgress(const QString&, int, int)));
594     m->load(converted_path);
595     // only switch mussas if we loaded without error
596     if (analysis->empty()) {
597       // our current window is empty so load and replace.
598       setAnalysis(m);
599       updateTitle();
600       default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
601     } else {
602       MussaWindow *win = new MussaWindow(m);
603       updateTitle();
604       win->default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
605       win->show();
606     }
607   } catch (boost::filesystem::filesystem_error e) {
608     QString msg("Unable to load ");
609     msg += muway_dir;
610     msg += "\n";
611     msg += e.what();
612     QMessageBox::warning(this, "Load Parameter", msg);    
613   } catch (mussa_load_error e) {
614     QString msg("Unable to load ");
615     msg += muway_dir;
616     msg += "\n";
617     msg += e.what();
618     QMessageBox::warning(this, "Load Parameter", msg);
619   }
620   assert (analysis != 0);
621 }
622
623 void MussaWindow::newMussaWindow()
624 {
625   MussaWindow *win = new MussaWindow(Mussa::init());
626   win->default_dir = default_dir;
627   win->show();
628 }
629
630 void MussaWindow::setSoftThreshold(int value)
631 {
632   if (analysis->get_soft_threshold() != value) {
633     threshold->setEnabled( false );
634     analysis->set_soft_threshold(value);
635     analysis->nway();
636     updateLinks();
637     update();
638     threshold->setEnabled( true );
639   }
640 }
641
642 void MussaWindow::showMussaToolbar()
643 {
644   if (mussaViewTB->isVisible())
645     mussaViewTB->hide();
646   else
647     mussaViewTB->show();
648 }
649
650 void MussaWindow::showBasePairsCopied(size_t bp_copied)
651 {
652   QString msg("Copied ");
653   QString num;
654   num.setNum(bp_copied);
655   msg += num + " base pairs";
656   statusBar()->showMessage(msg, 5000);
657 }
658
659
660 void MussaWindow::toggleMotifs()
661 {
662   NotImplementedBox();
663 }
664
665 void MussaWindow::showManual()
666 {
667 #if defined(QT_QTASSISTANT_FOUND)
668   if (manualAssistant) { 
669     manualAssistant->openAssistant();
670   } else {
671     QMessageBox::warning(this,
672                          tr("Mussa Help Error"),
673                          tr("QtAssistant not setup correctly"),
674                          QMessageBox::Ok,
675                          QMessageBox::NoButton,
676                          QMessageBox::NoButton);
677   }
678 #else
679   QUrl manual_url("http://woldlab.caltech.edu/~king/mussagl_manual/");
680   if (not QDesktopServices::openUrl(manual_url)) {
681     QMessageBox::warning(this,
682                          tr("Mussa Help Error"),
683                          tr("Unable to launch webbrowser"),
684                          QMessageBox::Ok,
685                          QMessageBox::NoButton,
686                          QMessageBox::NoButton);
687   }
688 #endif //QT_QTASSISTANT_FOUND
689 }
690
691 void MussaWindow::assistantError(QString message)
692 {
693   //std::cout << "QAssistantError: " << message.toStdString() << "\n";
694   QMessageBox::warning ( this, "Warning: Mussagl Manual", message, 
695                          QMessageBox::Ok, 
696                          QMessageBox::NoButton);
697 }
698
699 void MussaWindow::NotImplementedBox()
700 {
701   QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
702 }      
703
704 void MussaWindow::viewMussaAlignment()
705 {
706   const set<int>& selected_paths = browser->selectedPaths();
707   if (selected_paths.size() == 0 ) {
708     QMessageBox::warning(this, 
709                          QObject::tr("mussa"),
710                          QObject::tr("you should probably select some paths "
711                                      "first"));
712   } else {
713     MussaAlignedWindowRef ma_win( 
714       new MussaAlignedWindow(analysis, default_dir, selected_paths, subanalysis_window)
715     );
716
717     aligned_windows.push_back(ma_win);
718     connect(this, SIGNAL(changedAnnotations()), 
719             aligned_windows.back().get(), SLOT(update()));
720     aligned_windows.back()->show();
721   }
722 }
723                         
724 void MussaWindow::updateAnalysis()
725 {
726   const Mussa::vector_sequence_type& seqs = analysis->sequences();
727   browser->setSequences(seqs, analysis->colorMapper());
728   assert(browser->sequences().size() == analysis->size());
729
730   // setRange eventually emits something that causes updateLinks to be called
731   // but it's possible for us to not have had a chance to set out sequences
732   // yet.
733   threshold->setRange(analysis->get_threshold(),analysis->get_window());
734   updateLinks();
735   browser->zoomOut();
736   zoom->setValue(browser->zoom());
737 }
738
739 void MussaWindow::updateAnnotations()
740 {
741   // motifs were changed in the sequences by 
742   // Mussa::update_sequences_motifs
743   emit changedAnnotations();
744   browser->update();
745 }
746
747 void MussaWindow::updateLinks()
748 {
749   if(browser->sequences().size() == 0) {
750     // we don't have any sequences load so we have no business setting links
751     return;
752   }
753
754   browser->clear_links();
755   bool reversed = false;
756   const NwayPaths& nway = analysis->paths();
757
758   typedef list<ConservedPath> conserved_paths;
759   typedef conserved_paths::const_iterator const_conserved_paths_itor;
760   for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
761       path_itor != nway.refined_pathz.end();
762       ++path_itor)
763   {
764     // since we were drawing to the start of a window, and opengl lines
765     // are centered around the two connecting points our lines were slightly
766     // offset. the idea of window_offset is to adjust them to the
767     // right for forward compliment or left for reverse compliment
768     // FIXME: figure out how to unit test these computations
769     //GLfloat window_offset = (path_itor->window_size)/2.0;
770
771     size_t track_len = path_itor->track_indexes.size();
772     vector<int> normalized_path;
773     normalized_path.reserve(track_len);
774     vector<bool> rc_flags(false, track_len);
775     for (size_t track_i=0; track_i != track_len; ++track_i)
776     {
777       int x = path_itor->track_indexes[track_i];
778       // at some point when we modify the pathz data structure to keep
779       // track of the score we can put grab the depth here.
780       //
781       // are we reverse complimented?
782       if ( x>=0) {
783         reversed = false;
784       } else {
785         reversed = true;
786         // make positive 
787         x = -x;
788       }
789       normalized_path.push_back(x);
790       rc_flags.push_back(reversed);
791     }
792     browser->link(normalized_path, rc_flags, path_itor->window_size);
793   }
794   browser->update();
795 }
796
797 void 
798 MussaWindow::updateProgress(const QString& description, int current, int max)
799 {  
800   // if we're done  
801   if (current == max) {
802     if (progress_dialog != 0) {
803       progress_dialog->hide();
804       delete progress_dialog;
805       progress_dialog = 0;
806     }
807   } else {
808     // if we're starting, create the dialog
809     if (progress_dialog == 0) {
810       QString cancel("Cancel");
811       progress_dialog = new QProgressDialog(description, cancel, current, max, this);
812       progress_dialog->show();
813     } else {
814       // just update the dialog
815       progress_dialog->setValue(current);
816     }
817   }
818   qApp->processEvents();
819 }
820
821 void MussaWindow::updateAnalysisModified(bool is_modified)
822 {
823   setWindowModified(is_modified);
824 }
825
826 void MussaWindow::updateTitle()
827 {
828   if (analysis) {
829     QString title(analysis->get_title().c_str());
830     title += "[*]";
831     setWindowTitle(title);
832   }
833 }