1 #include "qui/MussaWindow.hpp"
2 #include "mussa_exceptions.hpp"
6 #include <QApplication>
7 #include <QAssistantClient>
9 #include <QDesktopServices>
11 #include <QFileDialog>
12 #include <QHBoxLayout>
15 #include <QMessageBox>
19 #include <QStringList>
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>
35 MussaWindow::MussaWindow(MussaRef analysis_, QWidget *parent) :
39 setup_analysis_dialog(0),
47 createNewAnalysisAction(0),
48 createSubAnalysisAction(0),
50 loadMotifListAction(0),
52 loadSavedAnalysisAction(0),
53 mussaManualAssistantAction(0),
54 newMussaWindowAction(0),
55 saveMotifListAction(0),
56 showMussaViewToolbarAction(0),
57 toggleMotifsAction(0),
58 saveBrowserPixmapAction(0),
60 viewMussaAlignmentAction(0),
68 setWindowIcon(QIcon(":/icons/mussa.png"));
69 default_dir.reset(new QDir(QDir::home().absolutePath()));
71 setCentralWidget(browser);
72 // well updatePosition isn't quite right as we really just need
74 connect(this, SIGNAL(changedAnnotations()), browser, SLOT(update()));
75 connect(this, SIGNAL(changedMotifs()), this, SLOT(updateAnnotations()));
76 connect(browser, SIGNAL(basepairsCopied(size_t)),
77 this, SLOT(showBasePairsCopied(size_t)));
78 connect(zoom, SIGNAL(valueChanged(double)),
79 browser, SLOT(setZoom(double)));
80 mussaViewTB->addWidget(zoom);
82 // threshold range is set in updateAnalysis
83 connect(threshold, SIGNAL(thresholdChanged(int)),
84 this, SLOT(setSoftThreshold(int)));
85 mussaViewTB->addWidget(threshold);
87 addToolBar(mussaViewTB);
89 statusBar()->showMessage("Welcome to mussa", 2000);
91 // FIXME: we should start refactoring the connect call to updateAnalysis or something
93 connect(analysis.get(), SIGNAL(progress(const QString&, int, int)),
94 this, SLOT(updateProgress(const QString&, int, int)));
95 connect(analysis.get(), SIGNAL(isModified(bool)),
96 this, SLOT(updateAnalysisModified(bool)));
102 void MussaWindow::setAnalysis(MussaRef new_analysis)
104 if (new_analysis != 0) {
105 // only switch mussas if we loaded without error
107 analysis.swap(new_analysis);
113 void MussaWindow::setupActions()
115 // we really don't want to run this more than once.
116 assert (closeAction == 0);
118 // the ever popular about box
119 aboutAction = new QAction(tr("&About"), this);
120 connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
121 aboutAction->setIcon(QIcon(":/icons/info.png"));
124 closeAction = new QAction(tr("&Close"), this);
125 closeAction->setStatusTip(tr("Close this window"));
126 connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
127 closeAction->setIcon(QIcon(":/icons/exit.png"));
129 createNewAnalysisAction = new QAction(tr("Create &New Analysis"), this);
130 connect(createNewAnalysisAction, SIGNAL(triggered()),
131 this, SLOT(createNewAnalysis()));
132 createNewAnalysisAction->setIcon(QIcon(":/icons/filenew.png"));
133 createNewAnalysisAction->setShortcut(Qt::CTRL | Qt::Key_N);
135 createSubAnalysisAction = new QAction(tr("Add to Subanalysis"), this);
136 connect(createSubAnalysisAction, SIGNAL(triggered()),
137 this, SLOT(createSubAnalysis()));
139 saveAnalysisAction = new QAction(tr("&Save Analysis"), this);
140 connect(saveAnalysisAction, SIGNAL(triggered()),
141 this, SLOT(saveAnalysis()));
142 saveAnalysisAction->setIcon(QIcon(":/icons/filesave.png"));
143 saveAnalysisAction->setShortcut(Qt::CTRL | Qt::Key_S);
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"));
150 editMotifsAction = new QAction(tr("Edit Motifs"), this);;
151 connect(editMotifsAction, SIGNAL(triggered()), this, SLOT(editMotifs()));
153 loadMotifListAction = new QAction(tr("Open Motif List"), this);
154 connect(loadMotifListAction, SIGNAL(triggered()),
155 this, SLOT(loadMotifList()));
156 loadMotifListAction->setIcon(QIcon(":/icons/fileopen.png"));
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"));
163 loadSavedAnalysisAction = new QAction(tr("&Open Existing &Analysis"), this);
164 connect(loadSavedAnalysisAction, SIGNAL(triggered()),
165 this, SLOT(loadSavedAnalysis()));
166 loadSavedAnalysisAction->setIcon(QIcon(":/icons/fileopen.png"));
167 loadSavedAnalysisAction->setShortcut(Qt::CTRL | Qt::Key_O);
169 mussaManualAssistantAction = new QAction(tr("Mussagl Manual..."), this);
170 mussaManualAssistantAction->setIcon(QIcon(":/icons/contents.png"));
171 connect(mussaManualAssistantAction, SIGNAL(triggered()),
172 this, SLOT(showManual()));
174 newMussaWindowAction = new QAction(tr("&New Mussa Window"), this);
175 newMussaWindowAction->setStatusTip("open another mussa window to allow comparing results");
176 connect(newMussaWindowAction, SIGNAL(triggered()),
177 this, SLOT(newMussaWindow()));
178 newMussaWindowAction->setIcon(QIcon(":/icons/window_new.png"));
180 saveMotifListAction = new QAction(tr("Save Motifs"), this);
181 connect(saveMotifListAction, SIGNAL(triggered()),
182 this, SLOT(saveMotifList()));
183 saveMotifListAction->setIcon(QIcon(":/icons/filesave.png"));
185 showMussaViewToolbarAction = new QAction(tr("Show Toolbar"), this);
186 connect(showMussaViewToolbarAction, SIGNAL(triggered()),
187 this, SLOT(showMussaToolbar()));
188 showMussaViewToolbarAction->setCheckable(true);
189 showMussaViewToolbarAction->setChecked(true);
191 toggleMotifsAction = new QAction(tr("Toggle Motifs"), this);
192 connect(toggleMotifsAction, SIGNAL(triggered()),
193 this, SLOT(toggleMotifs()));
194 toggleMotifsAction->setCheckable(true);
195 toggleMotifsAction->setIcon(QIcon(":/icons/motif_icon.png"));
196 toggleMotifsAction->setWhatsThis(tr("Toggle motif annotations on/off\n\n"
197 "You can load motif annotations via "
198 "'File->Load Motif List' menu option."));
200 //Save pixel map action
201 saveBrowserPixmapAction = new QAction(tr("Save to image..."), this);
202 connect(saveBrowserPixmapAction, (SIGNAL(triggered())),
203 browser, SLOT(promptSaveBrowserPixmap()));
204 saveBrowserPixmapAction->setIcon(QIcon(":/icons/image2.png"));
206 viewMussaAlignmentAction = new QAction(tr("View sequence alignment"), this);
207 connect(viewMussaAlignmentAction, SIGNAL(triggered()),
208 this, SLOT(viewMussaAlignment() ));
209 viewMussaAlignmentAction->setWhatsThis(tr("Create a zoomed in window "
210 "showing alignment of the seqcomp "
213 whatsThisAction = QWhatsThis::createAction(this);
214 whatsThisAction->setIcon(QIcon(":/icons/help.png"));
219 void MussaWindow::closeEvent(QCloseEvent *event)
221 if(isClearingAnalysisSafe()) {
228 void MussaWindow::setupMainMenu()
230 // we need to run setupActions first
231 assert (closeAction != 0);
233 QMenu *newMenu = menuBar()->addMenu(tr("&File"));
235 newMenu->addAction(newMussaWindowAction);
236 newMenu->addAction(createNewAnalysisAction);
237 newMenu->addAction(loadMupaAction);
238 newMenu->addAction(loadSavedAnalysisAction);
239 newMenu->addAction(saveAnalysisAction);
240 newMenu->addAction(saveAnalysisAsAction);
241 newMenu->addSeparator();
242 newMenu->addAction(loadMotifListAction);
243 newMenu->addAction(saveMotifListAction);
244 newMenu->addSeparator();
245 newMenu->addAction(saveBrowserPixmapAction);
246 newMenu->addSeparator();
247 newMenu->addAction(closeAction);
249 newMenu = menuBar()->addMenu(tr("&Edit"));
250 newMenu->addAction(editMotifsAction);
251 newMenu->addAction(browser->getCopySelectedSequenceAsStringAction());
252 newMenu->addAction(browser->getCopySelectedSequenceAsFastaAction());
253 newMenu->addAction(createSubAnalysisAction);
254 newMenu->addAction(browser->getEditSequencePropertiesAction());
256 newMenu = menuBar()->addMenu(tr("&View"));
257 newMenu->addAction(viewMussaAlignmentAction);
258 newMenu->addAction(showMussaViewToolbarAction);
260 newMenu = menuBar()->addMenu(tr("&Help"));
261 newMenu->addAction(mussaManualAssistantAction);
262 newMenu->addAction(whatsThisAction);
263 newMenu->addSeparator();
264 newMenu->addAction(aboutAction);
266 // add some extra features to the context menu
267 QMenu *popupMenu = browser->getPopupMenu();
269 popupMenu->addAction(viewMussaAlignmentAction);
270 popupMenu->addAction(createSubAnalysisAction);
274 void MussaWindow::setupWidgets()
276 setup_analysis_dialog = new MussaSetupDialog;
277 subanalysis_window.reset(new SubanalysisWindow(analysis));
278 browser = new SequenceBrowserWidget(default_dir);
279 mussaViewTB = new QToolBar("Path Views", this);
280 zoom = new ZoomWidget(mussaViewTB);
281 threshold = new ThresholdWidget(mussaViewTB);
284 void MussaWindow::setupAssistant()
286 #if defined(QT_QTASSISTANT_FOUND)
287 QStringList manualAssistantArgs;
288 manualAssistantArgs = QStringList();
289 manualAssistantArgs << "-profile" << "./doc/manual/mussagl_manual.adp";
290 manualAssistant = new QAssistantClient("assistant", this);
291 manualAssistant->setArguments(manualAssistantArgs);
292 connect(manualAssistant, SIGNAL(error(QString)),
293 this, SLOT(assistantError(QString)));
297 void MussaWindow::about()
300 msg += "Welcome to Multiple Species Sequence Analysis\n";
301 msg += "(c) 2005-2006 California Institute of Technology\n";
302 msg += "Diane Trout, Tristan De Buysscher, Brandon King\n";
304 msg += mussa_version;
307 msg += (char *)glGetString(GL_VERSION);
309 QMessageBox::about(this, tr("About mussa"), msg);
312 void MussaWindow::clear()
314 aligned_windows.clear();
318 void MussaWindow::createNewAnalysis()
321 // ideally we should open a new window if there's an analysis
322 // but this should work for the moment.
323 if (not isClearingAnalysisSafe()) return;
325 if (setup_analysis_dialog->exec()) {
326 setAnalysis(setup_analysis_dialog->getMussa());
328 } catch(mussa_error e) {
329 QString msg(e.what());
330 QMessageBox::warning(this, tr("Create New Analysis"), msg);
334 void MussaWindow::createSubAnalysis()
336 list<SequenceLocation> result;
337 SequenceLocationModel& model = subanalysis_window->getModel();
338 browser->copySelectedTracksAsSeqLocation(result);
339 for(list<SequenceLocation>::iterator result_itor = result.begin();
340 result_itor != result.end();
343 model.push_back(*result_itor);
346 if (not subanalysis_window->isVisible()) {
347 subanalysis_window->show();
351 void MussaWindow::saveAnalysis()
353 // if we've got an analysis
354 if (analysis and not analysis->empty()) {
355 // if it doesn't have a name we need to pick one
356 if (analysis->get_analysis_path().empty()) {
359 // if we've got a name, has it changed any?
360 // this doesn't work when a sequence changes something (like its
362 //else if (analysis->is_dirty())
365 } catch (std::exception e) {
366 QMessageBox::critical(this,
367 tr("Mussa Save Error"),
369 QMessageBox::Ok, 0, 0);
376 void MussaWindow::saveAnalysisAs()
378 std::auto_ptr<QFileDialog> dialog(new QFileDialog(this));
379 dialog->setAcceptMode(QFileDialog::AcceptSave);
380 dialog->setFileMode(QFileDialog::AnyFile);
381 dialog->setDirectory(*default_dir);
383 QStringList fileNames;
384 if (not dialog->exec()) {
387 fileNames = dialog->selectedFiles();
389 if (fileNames.size() != 1) {
393 fs::path save_path(fileNames[0].toStdString(), fs::native);
394 // do you want to overwrite?
395 if (fs::exists(save_path) and
396 QMessageBox::question(
398 tr("Overwrite File? -- Mussa"),
399 tr("A file called %1 already exists"
400 "do you want to overwrite it?")
402 tr("&Yes"), tr("&No"),
407 analysis->save(save_path);
408 fs::path normalized_path = (save_path / "..").normalize();
409 default_dir->setPath(normalized_path.native_directory_string().c_str());
410 } catch (std::exception e) {
411 QMessageBox::critical(this,
412 tr("Mussa Save Error"),
414 QMessageBox::Ok, 0, 0);
418 bool MussaWindow::isClearingAnalysisSafe()
420 if (analysis and not analysis->empty() and analysis->is_dirty()) {
421 switch (QMessageBox::question(
423 tr("Save Unsaved Changes -- Mussa"),
424 tr("There are unsaved changes,\ndo you want to save?"),
425 tr("&Yes"), tr("&No"), tr("&Cancel"),
440 throw runtime_error("isClearingAnalysis QMesageBox failure");
443 // if we're here we've been saved and can replace
447 void MussaWindow::editMotifs()
449 if (motif_editor != 0) {
450 motif_editor->hide();
453 motif_editor = new MotifEditor(analysis);
454 connect(motif_editor, SIGNAL(changedMotifs()),
455 this, SLOT(updateAnnotations()));
456 motif_editor->show();
459 void MussaWindow::loadMotifList()
461 QString caption("Mussa Load Motifs");
462 QString filter("Motif list(*.txt *.mtl)");
463 QString path = QFileDialog::getOpenFileName(this,
465 default_dir->absolutePath(),
470 // try to load safely
472 fs::path converted_path(path.toStdString(), fs::native);
473 analysis->load_motifs(converted_path);
474 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
475 emit changedMotifs();
476 } catch (std::exception e) {
477 QString msg("Unable to load ");
481 QMessageBox::warning(this, caption, msg);
485 void MussaWindow::saveMotifList()
487 QString caption("Mussa Save Motifs");
488 QString filter("Motif list(*.txt *.mtl)");
489 QString path = QFileDialog::getSaveFileName(this,
491 default_dir->absolutePath(),
496 // try to load safely
498 fs::path converted_path(path.toStdString(), fs::native);
499 if (fs::extension(converted_path).size() == 0) {
500 // no extension, so add one
501 fs::path base_path = converted_path.branch_path();
502 fs::path filename(converted_path.leaf() + ".mtl", fs::native);
503 converted_path = base_path / filename;
505 analysis->save_motifs(converted_path);
506 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
507 } catch (std::exception e) {
508 QString msg("Unable to save ");
512 QMessageBox::warning(this, caption, msg);
515 void MussaWindow::loadMupa()
517 QString caption("Load a mussa parameter file");
518 QString filter("Mussa Parameters (*.mupa)");
519 QString mupa_path = QFileDialog::getOpenFileName(
522 default_dir->absolutePath(),
526 if (mupa_path.isNull())
528 // try to load safely
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;
534 MussaRef m = Mussa::init();
535 fs::path converted_path(mupa_path.toStdString(), fs::native);
536 connect(m.get(), SIGNAL(progress(const QString&, int, int)),
537 this, SLOT(updateProgress(const QString&, int, int)));
538 m->load_mupa_file(converted_path);
542 // grab the path ignoring the mupa file portion
543 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
544 } catch (mussa_load_error e) {
545 QString msg("Unable to load ");
549 QMessageBox::warning(this, "Load Parameter", msg);
551 assert (analysis != 0);
554 void MussaWindow::loadSavedAnalysis()
556 QString caption("Load a previously run analysis");
557 QString muway_dir = QFileDialog::getExistingDirectory(
560 default_dir->absolutePath()
563 if (muway_dir.isNull())
565 // try to safely load
567 // ideally we should open a new window if there's an analysis
568 // but this should work for the moment.
569 if (not isClearingAnalysisSafe()) return;
571 MussaRef m = Mussa::init();
572 fs::path converted_path(muway_dir.toStdString(), fs::native);
573 connect(m.get(), SIGNAL(progress(const QString&, int, int)),
574 this, SLOT(updateProgress(const QString&, int, int)));
575 m->load(converted_path);
576 // only switch mussas if we loaded without error
577 if (analysis->empty()) {
578 // our current window is empty so load and replace.
581 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
583 MussaWindow *win = new MussaWindow(m);
585 win->default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
588 } catch (boost::filesystem::filesystem_error e) {
589 QString msg("Unable to load ");
593 QMessageBox::warning(this, "Load Parameter", msg);
594 } catch (mussa_load_error e) {
595 QString msg("Unable to load ");
599 QMessageBox::warning(this, "Load Parameter", msg);
601 assert (analysis != 0);
604 void MussaWindow::newMussaWindow()
606 MussaWindow *win = new MussaWindow(Mussa::init());
607 win->default_dir = default_dir;
611 void MussaWindow::setSoftThreshold(int value)
613 if (analysis->get_soft_threshold() != value) {
614 threshold->setEnabled( false );
615 analysis->set_soft_threshold(value);
619 threshold->setEnabled( true );
623 void MussaWindow::showMussaToolbar()
625 if (mussaViewTB->isVisible())
631 void MussaWindow::showBasePairsCopied(size_t bp_copied)
633 QString msg("Copied ");
635 num.setNum(bp_copied);
636 msg += num + " base pairs";
637 statusBar()->showMessage(msg, 5000);
641 void MussaWindow::toggleMotifs()
646 void MussaWindow::showManual()
648 #if defined(QT_QTASSISTANT_FOUND)
649 if (manualAssistant) {
650 manualAssistant->openAssistant();
652 QMessageBox::warning(this,
653 tr("Mussa Help Error"),
654 tr("QtAssistant not setup correctly"),
656 QMessageBox::NoButton,
657 QMessageBox::NoButton);
660 QUrl manual_url("http://woldlab.caltech.edu/~king/mussagl_manual/");
661 if (not QDesktopServices::openUrl(manual_url)) {
662 QMessageBox::warning(this,
663 tr("Mussa Help Error"),
664 tr("Unable to launch webbrowser"),
666 QMessageBox::NoButton,
667 QMessageBox::NoButton);
669 #endif //QT_QTASSISTANT_FOUND
672 void MussaWindow::assistantError(QString message)
674 //std::cout << "QAssistantError: " << message.toStdString() << "\n";
675 QMessageBox::warning ( this, "Warning: Mussagl Manual", message,
677 QMessageBox::NoButton);
680 void MussaWindow::NotImplementedBox()
682 QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
685 void MussaWindow::viewMussaAlignment()
687 const set<int>& selected_paths = browser->selectedPaths();
688 if (selected_paths.size() == 0 ) {
689 QMessageBox::warning(this,
690 QObject::tr("mussa"),
691 QObject::tr("you should probably select some paths "
694 MussaAlignedWindowRef ma_win(
695 new MussaAlignedWindow(analysis, default_dir, selected_paths, subanalysis_window)
698 aligned_windows.push_back(ma_win);
699 connect(this, SIGNAL(changedAnnotations()),
700 aligned_windows.back().get(), SLOT(update()));
701 aligned_windows.back()->show();
705 void MussaWindow::updateAnalysis()
707 const Mussa::vector_sequence_type& seqs = analysis->sequences();
708 browser->setSequences(seqs, analysis->colorMapper());
709 assert(browser->sequences().size() == analysis->size());
711 // setRange eventually emits something that causes updateLinks to be called
712 // but it's possible for us to not have had a chance to set out sequences
714 threshold->setRange(analysis->get_threshold(),analysis->get_window());
717 zoom->setValue(browser->zoom());
720 void MussaWindow::updateAnnotations()
722 // motifs were changed in the sequences by
723 // Mussa::update_sequences_motifs
724 emit changedAnnotations();
728 void MussaWindow::updateLinks()
730 if(browser->sequences().size() == 0) {
731 // we don't have any sequences load so we have no business setting links
735 browser->clear_links();
736 bool reversed = false;
737 const NwayPaths& nway = analysis->paths();
739 typedef list<ConservedPath> conserved_paths;
740 typedef conserved_paths::const_iterator const_conserved_paths_itor;
741 for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
742 path_itor != nway.refined_pathz.end();
745 // since we were drawing to the start of a window, and opengl lines
746 // are centered around the two connecting points our lines were slightly
747 // offset. the idea of window_offset is to adjust them to the
748 // right for forward compliment or left for reverse compliment
749 // FIXME: figure out how to unit test these computations
750 //GLfloat window_offset = (path_itor->window_size)/2.0;
752 size_t track_len = path_itor->track_indexes.size();
753 vector<int> normalized_path;
754 normalized_path.reserve(track_len);
755 vector<bool> rc_flags(false, track_len);
756 for (size_t track_i=0; track_i != track_len; ++track_i)
758 int x = path_itor->track_indexes[track_i];
759 // at some point when we modify the pathz data structure to keep
760 // track of the score we can put grab the depth here.
762 // are we reverse complimented?
770 normalized_path.push_back(x);
771 rc_flags.push_back(reversed);
773 browser->link(normalized_path, rc_flags, path_itor->window_size);
779 MussaWindow::updateProgress(const QString& description, int current, int max)
782 if (current == max) {
783 if (progress_dialog != 0) {
784 progress_dialog->hide();
785 delete progress_dialog;
789 // if we're starting, create the dialog
790 if (progress_dialog == 0) {
791 QString cancel("Cancel");
792 progress_dialog = new QProgressDialog(description, cancel, current, max, this);
793 progress_dialog->show();
795 // just update the dialog
796 progress_dialog->setValue(current);
799 qApp->processEvents();
802 void MussaWindow::updateAnalysisModified(bool is_modified)
804 setWindowModified(is_modified);
807 void MussaWindow::updateTitle()
810 QString title(analysis->get_title().c_str());
812 setWindowTitle(title);