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 static void init_resources() {
36 static bool resources_loaded = false;
37 if (not resources_loaded) {
38 Q_INIT_RESOURCE(icons);
39 resources_loaded = true;
43 MussaWindow::MussaWindow(MussaRef analysis_, QWidget *parent) :
47 setup_analysis_dialog(0),
55 createNewAnalysisAction(0),
56 createSubAnalysisAction(0),
58 loadMotifListAction(0),
60 loadSavedAnalysisAction(0),
61 mussaManualAssistantAction(0),
62 newMussaWindowAction(0),
63 saveMotifListAction(0),
64 showMussaViewToolbarAction(0),
65 toggleMotifsAction(0),
66 saveBrowserPixmapAction(0),
68 viewMussaAlignmentAction(0),
73 default_dir.reset(new QDir(QDir::home().absolutePath()));
80 setWindowIcon(QIcon(":/icons/mussa.png"));
82 setCentralWidget(browser);
83 // well updatePosition isn't quite right as we really just need
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);
93 // threshold range is set in updateAnalysis
94 connect(threshold, SIGNAL(thresholdChanged(int)),
95 this, SLOT(setSoftThreshold(int)));
96 mussaViewTB->addWidget(threshold);
98 addToolBar(mussaViewTB);
100 statusBar()->showMessage("Welcome to mussa", 2000);
102 // FIXME: we should start refactoring the connect call to updateAnalysis or something
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)));
113 void MussaWindow::setAnalysis(MussaRef new_analysis)
115 if (new_analysis != 0) {
116 // only switch mussas if we loaded without error
118 analysis.swap(new_analysis);
124 void MussaWindow::setupActions()
126 // we really don't want to run this more than once.
127 assert (closeAction == 0);
129 // the ever popular about box
130 aboutAction = new QAction(tr("&About"), this);
131 connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
132 aboutAction->setIcon(QIcon(":/icons/info.png"));
135 closeAction = new QAction(tr("&Close"), this);
136 closeAction->setStatusTip(tr("Close this window"));
137 connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
138 closeAction->setIcon(QIcon(":/icons/exit.png"));
140 createNewAnalysisAction = new QAction(tr("Create &New Analysis"), this);
141 connect(createNewAnalysisAction, SIGNAL(triggered()),
142 this, SLOT(createNewAnalysis()));
143 createNewAnalysisAction->setIcon(QIcon(":/icons/filenew.png"));
144 createNewAnalysisAction->setShortcut(Qt::CTRL | Qt::Key_N);
146 createSubAnalysisAction = new QAction(tr("Add to Subanalysis"), this);
147 connect(createSubAnalysisAction, SIGNAL(triggered()),
148 this, SLOT(createSubAnalysis()));
150 saveAnalysisAction = new QAction(tr("&Save Analysis"), this);
151 connect(saveAnalysisAction, SIGNAL(triggered()),
152 this, SLOT(saveAnalysis()));
153 saveAnalysisAction->setIcon(QIcon(":/icons/filesave.png"));
154 saveAnalysisAction->setShortcut(Qt::CTRL | Qt::Key_S);
156 saveAnalysisAsAction = new QAction(tr("Save Analysis &As"), this);
157 connect(saveAnalysisAsAction, SIGNAL(triggered()),
158 this, SLOT(saveAnalysisAs()));
159 saveAnalysisAsAction->setIcon(QIcon(":/icons/filesave.png"));
161 editMotifsAction = new QAction(tr("Edit Motifs"), this);;
162 connect(editMotifsAction, SIGNAL(triggered()), this, SLOT(editMotifs()));
164 loadMotifListAction = new QAction(tr("Open Motif List"), this);
165 connect(loadMotifListAction, SIGNAL(triggered()),
166 this, SLOT(loadMotifList()));
167 loadMotifListAction->setIcon(QIcon(":/icons/fileopen.png"));
169 loadMupaAction = new QAction(tr("Create Analysis from File"), this);
170 connect(loadMupaAction, SIGNAL(triggered()),
171 this, SLOT(loadMupa()));
172 loadMupaAction->setIcon(QIcon(":/icons/fileopen.png"));
174 loadSavedAnalysisAction = new QAction(tr("&Open Existing &Analysis"), this);
175 connect(loadSavedAnalysisAction, SIGNAL(triggered()),
176 this, SLOT(loadSavedAnalysis()));
177 loadSavedAnalysisAction->setIcon(QIcon(":/icons/fileopen.png"));
178 loadSavedAnalysisAction->setShortcut(Qt::CTRL | Qt::Key_O);
180 mussaManualAssistantAction = new QAction(tr("Mussagl Manual..."), this);
181 mussaManualAssistantAction->setIcon(QIcon(":/icons/contents.png"));
182 connect(mussaManualAssistantAction, SIGNAL(triggered()),
183 this, SLOT(showManual()));
185 newMussaWindowAction = new QAction(tr("&New Mussa Window"), this);
186 newMussaWindowAction->setStatusTip("open another mussa window to allow comparing results");
187 connect(newMussaWindowAction, SIGNAL(triggered()),
188 this, SLOT(newMussaWindow()));
189 newMussaWindowAction->setIcon(QIcon(":/icons/window_new.png"));
191 saveMotifListAction = new QAction(tr("Save Motifs"), this);
192 connect(saveMotifListAction, SIGNAL(triggered()),
193 this, SLOT(saveMotifList()));
194 saveMotifListAction->setIcon(QIcon(":/icons/filesave.png"));
196 showMussaViewToolbarAction = new QAction(tr("Show Toolbar"), this);
197 connect(showMussaViewToolbarAction, SIGNAL(triggered()),
198 this, SLOT(showMussaToolbar()));
199 showMussaViewToolbarAction->setCheckable(true);
200 showMussaViewToolbarAction->setChecked(true);
202 toggleMotifsAction = new QAction(tr("Toggle Motifs"), this);
203 connect(toggleMotifsAction, SIGNAL(triggered()),
204 this, SLOT(toggleMotifs()));
205 toggleMotifsAction->setCheckable(true);
206 toggleMotifsAction->setIcon(QIcon(":/icons/motif_icon.png"));
207 toggleMotifsAction->setWhatsThis(tr("Toggle motif annotations on/off\n\n"
208 "You can load motif annotations via "
209 "'File->Load Motif List' menu option."));
211 //Save pixel map action
212 saveBrowserPixmapAction = new QAction(tr("Save to image..."), this);
214 connect(saveBrowserPixmapAction, (SIGNAL(triggered())),
215 browser, SLOT(promptSaveBrowserPixmap()));
216 saveBrowserPixmapAction->setIcon(QIcon(":/icons/image2.png"));
219 viewMussaAlignmentAction = new QAction(tr("View sequence alignment"), this);
220 connect(viewMussaAlignmentAction, SIGNAL(triggered()),
221 this, SLOT(viewMussaAlignment() ));
222 viewMussaAlignmentAction->setWhatsThis(tr("Create a zoomed in window "
223 "showing alignment of the seqcomp "
226 whatsThisAction = QWhatsThis::createAction(this);
227 whatsThisAction->setIcon(QIcon(":/icons/help.png"));
232 void MussaWindow::closeEvent(QCloseEvent *event)
234 if(isClearingAnalysisSafe()) {
241 void MussaWindow::setupMainMenu()
243 // we need to run setupActions first
244 assert (closeAction != 0);
246 QMenu *newMenu = menuBar()->addMenu(tr("&File"));
248 newMenu->addAction(newMussaWindowAction);
249 newMenu->addAction(createNewAnalysisAction);
250 newMenu->addAction(loadMupaAction);
251 newMenu->addAction(loadSavedAnalysisAction);
252 newMenu->addAction(saveAnalysisAction);
253 newMenu->addAction(saveAnalysisAsAction);
254 newMenu->addSeparator();
255 newMenu->addAction(loadMotifListAction);
256 newMenu->addAction(saveMotifListAction);
257 newMenu->addSeparator();
258 newMenu->addAction(saveBrowserPixmapAction);
259 newMenu->addSeparator();
260 newMenu->addAction(closeAction);
262 newMenu = menuBar()->addMenu(tr("&Edit"));
263 newMenu->addAction(editMotifsAction);
264 if (browser) newMenu->addAction(browser->getCopySelectedSequenceAsStringAction());
265 if (browser) newMenu->addAction(browser->getCopySelectedSequenceAsFastaAction());
266 newMenu->addAction(createSubAnalysisAction);
267 if (browser) newMenu->addAction(browser->getEditSequencePropertiesAction());
269 newMenu = menuBar()->addMenu(tr("&View"));
270 newMenu->addAction(viewMussaAlignmentAction);
271 newMenu->addAction(showMussaViewToolbarAction);
273 newMenu = menuBar()->addMenu(tr("&Help"));
274 newMenu->addAction(mussaManualAssistantAction);
275 newMenu->addAction(whatsThisAction);
276 newMenu->addSeparator();
277 newMenu->addAction(aboutAction);
279 // add some extra features to the context menu
281 QMenu *popupMenu = browser->getPopupMenu();
283 popupMenu->addAction(viewMussaAlignmentAction);
284 popupMenu->addAction(createSubAnalysisAction);
289 void MussaWindow::setupWidgets()
291 setup_analysis_dialog = new MussaSetupDialog;
292 subanalysis_window.reset(new SubanalysisWindow(analysis));
293 browser = new SequenceBrowserWidget(default_dir);
294 mussaViewTB = new QToolBar("Path Views", this);
295 zoom = new ZoomWidget(mussaViewTB);
296 threshold = new ThresholdWidget(mussaViewTB);
299 void MussaWindow::setupAssistant()
301 #if defined(QT_QTASSISTANT_FOUND)
302 QStringList manualAssistantArgs;
303 manualAssistantArgs = QStringList();
304 manualAssistantArgs << "-profile" << "./doc/manual/mussagl_manual.adp";
305 manualAssistant = new QAssistantClient("assistant", this);
306 manualAssistant->setArguments(manualAssistantArgs);
307 connect(manualAssistant, SIGNAL(error(QString)),
308 this, SLOT(assistantError(QString)));
312 void MussaWindow::about()
315 msg += "Welcome to Multiple Species Sequence Analysis\n";
316 msg += "(c) 2005-2006 California Institute of Technology\n";
317 msg += "Diane Trout, Tristan De Buysscher, Brandon King\n";
319 msg += mussa_version;
325 msg += (char *)glGetString(GL_VERSION);
327 QMessageBox::about(this, tr("About mussa"), msg);
330 void MussaWindow::clear()
332 if (motif_editor != 0) {
333 motif_editor->hide();
337 aligned_windows.clear();
341 void MussaWindow::createNewAnalysis()
344 // ideally we should open a new window if there's an analysis
345 // but this should work for the moment.
346 if (not isClearingAnalysisSafe()) return;
348 if (setup_analysis_dialog->exec()) {
349 setAnalysis(setup_analysis_dialog->getMussa());
351 } catch(mussa_error e) {
352 QString msg(e.what());
353 QMessageBox::warning(this, tr("Create New Analysis"), msg);
357 void MussaWindow::createSubAnalysis()
359 list<SequenceLocation> result;
360 SequenceLocationModel& model = subanalysis_window->getModel();
361 browser->copySelectedTracksAsSeqLocation(result);
362 for(list<SequenceLocation>::iterator result_itor = result.begin();
363 result_itor != result.end();
366 model.push_back(*result_itor);
369 if (not subanalysis_window->isVisible()) {
370 subanalysis_window->show();
374 void MussaWindow::saveAnalysis()
376 // if we've got an analysis
377 if (analysis and not analysis->empty()) {
378 // if it doesn't have a name we need to pick one
379 if (analysis->get_analysis_path().empty()) {
382 // if we've got a name, has it changed any?
383 // this doesn't work when a sequence changes something (like its
385 //else if (analysis->is_dirty())
388 } catch (std::exception e) {
389 QMessageBox::critical(this,
390 tr("Mussa Save Error"),
392 QMessageBox::Ok, 0, 0);
399 void MussaWindow::saveAnalysisAs()
401 std::auto_ptr<QFileDialog> dialog(new QFileDialog(this));
402 dialog->setAcceptMode(QFileDialog::AcceptSave);
403 dialog->setFileMode(QFileDialog::AnyFile);
404 dialog->setDirectory(*default_dir);
406 QStringList fileNames;
407 if (not dialog->exec()) {
410 fileNames = dialog->selectedFiles();
412 if (fileNames.size() != 1) {
416 fs::path save_path(fileNames[0].toStdString(), fs::native);
417 // do you want to overwrite?
418 if (fs::exists(save_path) and
419 QMessageBox::question(
421 tr("Overwrite File? -- Mussa"),
422 tr("A file called %1 already exists"
423 "do you want to overwrite it?")
425 tr("&Yes"), tr("&No"),
430 analysis->save(save_path);
431 fs::path normalized_path = (save_path / "..").normalize();
432 default_dir->setPath(normalized_path.native_directory_string().c_str());
433 } catch (std::exception e) {
434 QMessageBox::critical(this,
435 tr("Mussa Save Error"),
437 QMessageBox::Ok, 0, 0);
441 bool MussaWindow::isClearingAnalysisSafe()
443 if (analysis and not analysis->empty() and analysis->is_dirty()) {
444 switch (QMessageBox::question(
446 tr("Save Unsaved Changes -- Mussa"),
447 tr("There are unsaved changes,\ndo you want to save?"),
448 tr("&Yes"), tr("&No"), tr("&Cancel"),
463 throw runtime_error("isClearingAnalysis QMesageBox failure");
466 // if we're here we've been saved and can replace
470 void MussaWindow::editMotifs()
472 if (not motif_editor) {
473 motif_editor = new MotifEditor(analysis);
474 connect(motif_editor, SIGNAL(changedMotifs()),
475 this, SLOT(updateAnnotations()));
477 motif_editor->show();
480 void MussaWindow::loadMotifList()
482 QString caption("Mussa Load Motifs");
483 QString filter("Motif list(*.txt *.mtl)");
484 QString path = QFileDialog::getOpenFileName(this,
486 default_dir->absolutePath(),
491 // try to load safely
493 fs::path converted_path(path.toStdString(), fs::native);
494 analysis->load_motifs(converted_path);
495 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
496 emit changedMotifs();
497 } catch (std::exception e) {
498 QString msg("Unable to load ");
502 QMessageBox::warning(this, caption, msg);
506 void MussaWindow::saveMotifList()
508 QString caption("Mussa Save Motifs");
509 QString filter("Motif list(*.txt *.mtl)");
510 QString path = QFileDialog::getSaveFileName(this,
512 default_dir->absolutePath(),
517 // try to load safely
519 fs::path converted_path(path.toStdString(), fs::native);
520 if (fs::extension(converted_path).size() == 0) {
521 // no extension, so add one
522 fs::path base_path = converted_path.branch_path();
523 fs::path filename(converted_path.leaf() + ".mtl", fs::native);
524 converted_path = base_path / filename;
526 analysis->save_motifs(converted_path);
527 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
528 } catch (std::exception e) {
529 QString msg("Unable to save ");
533 QMessageBox::warning(this, caption, msg);
536 void MussaWindow::loadMupa()
538 QString caption("Load a mussa parameter file");
539 QString filter("Mussa Parameters (*.mupa)");
540 QString mupa_path = QFileDialog::getOpenFileName(
543 default_dir->absolutePath(),
547 if (mupa_path.isNull())
549 // try to load safely
551 // ideally we should open a new window if there's an analysis
552 // but this should work for the moment.
553 if (not isClearingAnalysisSafe()) return;
555 MussaRef m = Mussa::init();
556 fs::path converted_path(mupa_path.toStdString(), fs::native);
557 connect(m.get(), SIGNAL(progress(const QString&, int, int)),
558 this, SLOT(updateProgress(const QString&, int, int)));
559 m->load_mupa_file(converted_path);
563 // grab the path ignoring the mupa file portion
564 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
565 } catch (mussa_load_error e) {
566 QString msg("Unable to load ");
570 QMessageBox::warning(this, "Load Parameter", msg);
572 assert (analysis != 0);
575 void MussaWindow::loadSavedAnalysis()
577 QString caption("Load a previously run analysis");
578 QString muway_dir = QFileDialog::getExistingDirectory(
581 default_dir->absolutePath()
584 if (muway_dir.isNull())
586 // try to safely load
588 // ideally we should open a new window if there's an analysis
589 // but this should work for the moment.
590 if (not isClearingAnalysisSafe()) return;
592 MussaRef m = Mussa::init();
593 fs::path converted_path(muway_dir.toStdString(), fs::native);
594 connect(m.get(), SIGNAL(progress(const QString&, int, int)),
595 this, SLOT(updateProgress(const QString&, int, int)));
596 m->load(converted_path);
597 // only switch mussas if we loaded without error
598 if (analysis->empty()) {
599 // our current window is empty so load and replace.
602 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
604 MussaWindow *win = new MussaWindow(m);
606 win->default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
609 } catch (boost::filesystem::filesystem_error e) {
610 QString msg("Unable to load ");
614 QMessageBox::warning(this, "Load Parameter", msg);
615 } catch (mussa_load_error e) {
616 QString msg("Unable to load ");
620 QMessageBox::warning(this, "Load Parameter", msg);
622 assert (analysis != 0);
625 void MussaWindow::newMussaWindow()
627 MussaWindow *win = new MussaWindow(Mussa::init());
628 win->default_dir = default_dir;
632 void MussaWindow::setSoftThreshold(int value)
634 if (analysis->get_soft_threshold() != value) {
635 threshold->setEnabled( false );
636 analysis->set_soft_threshold(value);
640 threshold->setEnabled( true );
644 void MussaWindow::showMussaToolbar()
646 if (mussaViewTB->isVisible())
652 void MussaWindow::showBasePairsCopied(size_t bp_copied)
654 QString msg("Copied ");
656 num.setNum(bp_copied);
657 msg += num + " base pairs";
658 statusBar()->showMessage(msg, 5000);
662 void MussaWindow::toggleMotifs()
667 void MussaWindow::showManual()
669 #if defined(QT_QTASSISTANT_FOUND)
670 if (manualAssistant) {
671 manualAssistant->openAssistant();
673 QMessageBox::warning(this,
674 tr("Mussa Help Error"),
675 tr("QtAssistant not setup correctly"),
677 QMessageBox::NoButton,
678 QMessageBox::NoButton);
681 QUrl manual_url("http://woldlab.caltech.edu/~king/mussagl_manual/");
682 if (not QDesktopServices::openUrl(manual_url)) {
683 QMessageBox::warning(this,
684 tr("Mussa Help Error"),
685 tr("Unable to launch webbrowser"),
687 QMessageBox::NoButton,
688 QMessageBox::NoButton);
690 #endif //QT_QTASSISTANT_FOUND
693 void MussaWindow::assistantError(QString message)
695 //std::cout << "QAssistantError: " << message.toStdString() << "\n";
696 QMessageBox::warning ( this, "Warning: Mussagl Manual", message,
698 QMessageBox::NoButton);
701 void MussaWindow::NotImplementedBox()
703 QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
706 void MussaWindow::viewMussaAlignment()
708 const set<int>& selected_paths = browser->selectedPaths();
709 if (selected_paths.size() == 0 ) {
710 QMessageBox::warning(this,
711 QObject::tr("mussa"),
712 QObject::tr("you should probably select some paths "
715 MussaAlignedWindowRef ma_win(
716 new MussaAlignedWindow(analysis, default_dir, selected_paths, subanalysis_window)
719 aligned_windows.push_back(ma_win);
720 connect(this, SIGNAL(changedAnnotations()),
721 aligned_windows.back().get(), SLOT(update()));
722 aligned_windows.back()->show();
726 void MussaWindow::updateAnalysis()
728 const Mussa::vector_sequence_type& seqs = analysis->sequences();
729 browser->setSequences(seqs, analysis->colorMapper());
730 assert(browser->sequences().size() == analysis->size());
732 // setRange eventually emits something that causes updateLinks to be called
733 // but it's possible for us to not have had a chance to set out sequences
735 threshold->setRange(analysis->get_threshold(),analysis->get_window());
738 zoom->setValue(browser->zoom());
741 void MussaWindow::updateAnnotations()
743 // motifs were changed in the sequences by
744 // Mussa::update_sequences_motifs
745 emit changedAnnotations();
749 void MussaWindow::updateLinks()
751 if(browser->sequences().size() == 0) {
752 // we don't have any sequences load so we have no business setting links
756 browser->clear_links();
757 bool reversed = false;
758 const NwayPaths& nway = analysis->paths();
760 typedef list<ConservedPath> conserved_paths;
761 typedef conserved_paths::const_iterator const_conserved_paths_itor;
762 for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
763 path_itor != nway.refined_pathz.end();
766 // since we were drawing to the start of a window, and opengl lines
767 // are centered around the two connecting points our lines were slightly
768 // offset. the idea of window_offset is to adjust them to the
769 // right for forward compliment or left for reverse compliment
770 // FIXME: figure out how to unit test these computations
771 //GLfloat window_offset = (path_itor->window_size)/2.0;
773 size_t track_len = path_itor->track_indexes.size();
774 vector<int> normalized_path;
775 normalized_path.reserve(track_len);
776 vector<bool> rc_flags(false, track_len);
777 for (size_t track_i=0; track_i != track_len; ++track_i)
779 int x = path_itor->track_indexes[track_i];
780 // at some point when we modify the pathz data structure to keep
781 // track of the score we can put grab the depth here.
783 // are we reverse complimented?
791 normalized_path.push_back(x);
792 rc_flags.push_back(reversed);
794 browser->link(normalized_path, rc_flags, path_itor->window_size);
800 MussaWindow::updateProgress(const QString& description, int current, int max)
803 if (current == max) {
804 if (progress_dialog != 0) {
805 progress_dialog->hide();
806 delete progress_dialog;
810 // if we're starting, create the dialog
811 if (progress_dialog == 0) {
812 QString cancel("Cancel");
813 progress_dialog = new QProgressDialog(description, cancel, current, max, this);
814 progress_dialog->show();
816 // just update the dialog
817 progress_dialog->setValue(current);
820 qApp->processEvents();
823 void MussaWindow::updateAnalysisModified(bool is_modified)
825 setWindowModified(is_modified);
828 void MussaWindow::updateTitle()
831 QString title(analysis->get_title().c_str());
833 setWindowTitle(title);