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