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