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