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