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