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