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