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