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