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