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