1 #include "py/python.hpp"
2 #include "qui/MussaWindow.hpp"
3 #include "mussa_exceptions.hpp"
6 #include <QApplication>
7 #include <QAssistantClient>
10 #include <QFileDialog>
11 #include <QHBoxLayout>
14 #include <QMessageBox>
18 #include <QStringList>
25 #include <boost/filesystem/path.hpp>
26 #include <boost/filesystem/operations.hpp>
27 namespace fs = boost::filesystem;
28 #include <boost/bind.hpp>
32 MussaWindow::MussaWindow(MussaRef analysis_, QWidget *parent) :
35 default_dir(QDir::home().absolutePath().toStdString(), fs::native),
37 setup_analysis_dialog(new MussaSetupDialog(this)),
38 subanalysis_window(new SubanalysisWindow(analysis)),
39 browser(new SequenceBrowserWidget(this)),
40 mussaViewTB(new QToolBar("Path Views")),
42 threshold(new ThresholdWidget),
46 createNewAnalysisAction(0),
47 createSubAnalysisAction(0),
49 loadMotifListAction(0),
51 loadSavedAnalysisAction(0),
52 mussaManualAssistantAction(0),
53 newMussaWindowAction(0),
54 saveMotifListAction(0),
55 showMussaViewToolbarAction(0),
56 toggleMotifsAction(0),
57 saveBrowserPixmapAction(0),
59 viewMussaAlignmentAction(0),
66 //This next setWhatsThis function prevents
67 // a segfault when using WhatsThis feature with
69 //scene->setWhatsThis(tr("Mussa in OpenGL!"));
70 setCentralWidget(browser);
71 // well updatePosition isn't quite right as we really just need
73 connect(this, SIGNAL(changedAnnotations()), browser, SLOT(update()));
74 connect(this, SIGNAL(changedMotifs()), this, SLOT(updateAnnotations()));
76 //mussaViewTB->addAction(toggleMotifsAction);
77 mussaViewTB->addWidget(zoom);
79 connect(zoom, SIGNAL(valueChanged(double)),
80 browser, SLOT(setZoom(double)));
82 // threshold range is set in updateAnalysis
84 //scene->setClipPlane(20);
85 // FIXME: for when we get the paths drawn at the appropriate depth
86 //connect(threshold, SIGNAL(thresholdChanged(int)),
87 // this, SLOT(setClipPlane(int)));
88 connect(threshold, SIGNAL(thresholdChanged(int)),
89 this, SLOT(setSoftThreshold(int)));
90 mussaViewTB->addWidget(threshold);
92 addToolBar(mussaViewTB);
94 statusBar()->showMessage("Welcome to mussa", 2000);
95 // FIXME: we should start refactoring the connect call to updateAnalysis or something
97 connect(analysis.get(), SIGNAL(progress(const std::string&, int, int)),
98 this, SLOT(updateProgress(const std::string&, int, int)));
104 void MussaWindow::setAnalysis(MussaRef new_analysis)
106 if (new_analysis != 0) {
107 // only switch mussas if we loaded without error
109 analysis.swap(new_analysis);
115 void MussaWindow::setupActions()
117 // we really don't want to run this more than once.
118 assert (closeAction == 0);
120 // the ever popular about box
121 aboutAction = new QAction(tr("&About"), this);
122 connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
123 aboutAction->setIcon(QIcon(":/icons/info.png"));
126 closeAction = new QAction(tr("&Close"), this);
127 closeAction->setStatusTip(tr("Close this window"));
128 connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
129 closeAction->setIcon(QIcon(":/icons/exit.png"));
131 createNewAnalysisAction = new QAction(tr("Create Analysis"), this);
132 connect(createNewAnalysisAction, SIGNAL(triggered()),
133 this, SLOT(createNewAnalysis()));
134 createNewAnalysisAction->setIcon(QIcon(":/icons/filenew.png"));
136 createSubAnalysisAction = new QAction(tr("Add to Subanalysis"), this);
137 connect(createSubAnalysisAction, SIGNAL(triggered()),
138 this, SLOT(createSubAnalysis()));
140 saveAnalysisAction = new QAction(tr("&Save Analysis"), this);
141 connect(saveAnalysisAction, SIGNAL(triggered()),
142 this, SLOT(saveAnalysis()));
143 saveAnalysisAction->setIcon(QIcon(":/icons/filesave.png"));
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("Load 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("Load Existing &Analysis"), this);
164 connect(loadSavedAnalysisAction, SIGNAL(triggered()),
165 this, SLOT(loadSavedAnalysis()));
166 loadSavedAnalysisAction->setIcon(QIcon(":/icons/fileopen.png"));
168 mussaManualAssistantAction = new QAction(tr("Mussagl Manual..."), this);
169 mussaManualAssistantAction->setIcon(QIcon(":/icons/contents.png"));
170 connect(mussaManualAssistantAction, SIGNAL(triggered()),
171 this, SLOT(showManual()));
173 newMussaWindowAction = new QAction(tr("&New Mussa Window"), this);
174 newMussaWindowAction->setStatusTip("open another mussa window to allow comparing results");
175 connect(newMussaWindowAction, SIGNAL(triggered()),
176 this, SLOT(newMussaWindow()));
177 newMussaWindowAction->setIcon(QIcon(":/icons/window_new.png"));
179 saveMotifListAction = new QAction(tr("Save Motifs"), this);
180 connect(saveMotifListAction, SIGNAL(triggered()),
181 this, SLOT(saveMotifList()));
182 saveMotifListAction->setIcon(QIcon(":/icons/filesave.png"));
184 showMussaViewToolbarAction = new QAction(tr("Show Toolbar"), this);
185 connect(showMussaViewToolbarAction, SIGNAL(triggered()),
186 this, SLOT(showMussaToolbar()));
187 showMussaViewToolbarAction->setCheckable(true);
188 showMussaViewToolbarAction->setChecked(true);
190 toggleMotifsAction = new QAction(tr("Toggle Motifs"), this);
191 connect(toggleMotifsAction, SIGNAL(triggered()),
192 this, SLOT(toggleMotifs()));
193 toggleMotifsAction->setCheckable(true);
194 toggleMotifsAction->setIcon(QIcon(":/icons/motif_icon.png"));
195 toggleMotifsAction->setWhatsThis(tr("Toggle motif annotations on/off\n\n"
196 "You can load motif annotations via "
197 "'File->Load Motif List' menu option."));
199 //Save pixel map action
200 saveBrowserPixmapAction = new QAction(tr("Save to image..."), this);
201 connect(saveBrowserPixmapAction, (SIGNAL(triggered())),
202 browser, SLOT(promptSaveBrowserPixmap()));
203 saveBrowserPixmapAction->setIcon(QIcon(":/icons/image2.png"));
205 viewMussaAlignmentAction = new QAction(tr("View sequence alignment"), this);
206 connect(viewMussaAlignmentAction, SIGNAL(triggered()),
207 this, SLOT(viewMussaAlignment() ));
208 viewMussaAlignmentAction->setWhatsThis(tr("Create a zoomed in window "
209 "showing alignment of the seqcomp "
212 whatsThisAction = QWhatsThis::createAction(this);
213 whatsThisAction->setIcon(QIcon(":/icons/help.png"));
218 void MussaWindow::closeEvent(QCloseEvent *event)
220 if(isClearingAnalysisSafe()) {
227 void MussaWindow::setupMainMenu()
229 // we need to run setupActions first
230 assert (closeAction != 0);
232 QMenu *newMenu = menuBar()->addMenu(tr("&File"));
234 newMenu->addAction(newMussaWindowAction);
235 newMenu->addAction(createNewAnalysisAction);
236 newMenu->addAction(loadMupaAction);
237 newMenu->addAction(loadSavedAnalysisAction);
238 newMenu->addAction(saveAnalysisAction);
239 newMenu->addAction(saveAnalysisAsAction);
240 newMenu->addSeparator();
241 newMenu->addAction(loadMotifListAction);
242 newMenu->addAction(saveMotifListAction);
243 newMenu->addSeparator();
244 newMenu->addAction(saveBrowserPixmapAction);
245 newMenu->addSeparator();
246 newMenu->addAction(closeAction);
248 newMenu = menuBar()->addMenu(tr("&Edit"));
249 newMenu->addAction(editMotifsAction);
250 newMenu->addAction(browser->getCopySelectedSequenceAsFastaAction());
251 newMenu->addAction(createSubAnalysisAction);
253 newMenu = menuBar()->addMenu(tr("&View"));
254 newMenu->addAction(viewMussaAlignmentAction);
255 newMenu->addAction(showMussaViewToolbarAction);
257 newMenu = menuBar()->addMenu(tr("&Help"));
258 newMenu->addAction(mussaManualAssistantAction);
259 newMenu->addAction(whatsThisAction);
260 newMenu->addSeparator();
261 newMenu->addAction(aboutAction);
263 // add some extra features to the context menu
264 QMenu *popupMenu = browser->getPopupMenu();
266 popupMenu->addAction(viewMussaAlignmentAction);
267 popupMenu->addAction(createSubAnalysisAction);
271 void MussaWindow::setupAssistant()
273 #if defined(QT_QTASSISTANT_FOUND)
274 QStringList manualAssistantArgs;
275 manualAssistantArgs = QStringList();
276 manualAssistantArgs << "-profile" << "./doc/manual/mussagl_manual.adp";
277 manualAssistant = new QAssistantClient("assistant", this);
278 manualAssistant->setArguments(manualAssistantArgs);
279 connect(manualAssistant, SIGNAL(error(QString)),
280 this, SLOT(assistantError(QString)));
284 void MussaWindow::about()
286 QString msg("Welcome to Multiple Species Sequence Analysis\n"
287 "(c) 2005-2006 California Institute of Technology\n"
288 "Tristan De Buysscher, Diane Trout\n");
291 msg += (char *)glGetString(GL_VERSION);
293 QMessageBox::about(this, tr("About mussa"), msg);
296 void MussaWindow::clear()
298 aligned_windows.clear();
302 void MussaWindow::createNewAnalysis()
305 // ideally we should open a new window if there's an analysis
306 // but this should work for the moment.
307 if (not isClearingAnalysisSafe()) return;
309 if (setup_analysis_dialog->exec()) {
310 setAnalysis(setup_analysis_dialog->getMussa());
312 } catch(mussa_error e) {
313 QString msg(e.what());
314 QMessageBox::warning(this, tr("Create New Analysis"), msg);
318 void MussaWindow::createSubAnalysis()
320 list<SequenceLocation> result;
321 SequenceLocationModel& model = subanalysis_window->getModel();
322 browser->copySelectedTracksAsSeqLocation(result);
323 for(list<SequenceLocation>::iterator result_itor = result.begin();
324 result_itor != result.end();
327 model.push_back(*result_itor);
330 if (not subanalysis_window->isVisible()) {
331 subanalysis_window->show();
335 void MussaWindow::saveAnalysis()
337 // if we've got an analysis
338 if (analysis and not analysis->empty()) {
339 // if it doesn't have a name we need to pick one
340 if (analysis->get_analysis_path().empty()) {
343 // if we've got a name, has it changed any?
344 // this doesn't work when a sequence changes something (like its
346 //else if (analysis->is_dirty())
352 void MussaWindow::saveAnalysisAs()
354 std::auto_ptr<QFileDialog> dialog(new QFileDialog(this));
355 dialog->setAcceptMode(QFileDialog::AcceptSave);
356 dialog->setFileMode(QFileDialog::AnyFile);
357 dialog->setDirectory(QDir(default_dir.native_directory_string().c_str()));
359 QStringList fileNames;
360 if (not dialog->exec()) {
363 fileNames = dialog->selectedFiles();
365 if (fileNames.size() != 1) {
368 fs::path save_path(fileNames[0].toStdString(), fs::native);
369 // do you want to overwrite?
370 if (fs::exists(save_path) and
371 QMessageBox::question(
373 tr("Overwrite File? -- Mussa"),
374 tr("A file called %1 already exists"
375 "do you want to overwrite it?")
377 tr("&Yes"), tr("&No"),
382 analysis->save(save_path);
383 default_dir = (save_path / "..").normalize();
386 bool MussaWindow::isClearingAnalysisSafe()
388 if (analysis and not analysis->empty() and analysis->is_dirty()) {
389 switch (QMessageBox::question(
391 tr("Save Unsaved Changes -- Mussa"),
392 tr("There are unsaved changes,\ndo you want to save?"),
393 tr("&Yes"), tr("&No"), tr("&Cancel"),
408 throw runtime_error("isClearingAnalysis QMesageBox failure");
411 // if we're here we've been saved and can replace
415 void MussaWindow::editMotifs()
417 if (motif_editor != 0) {
418 motif_editor->hide();
421 motif_editor = new MotifEditor(analysis);
422 connect(motif_editor, SIGNAL(changedMotifs()),
423 this, SLOT(updateAnnotations()));
424 motif_editor->show();
427 void MussaWindow::loadMotifList()
429 QString caption("Mussa Load Motifs");
430 QString filter("Motif list(*.txt *.mtl)");
431 QDir default_qdir(default_dir.native_directory_string().c_str());
432 QString path = QFileDialog::getOpenFileName(this,
434 default_qdir.absolutePath(),
439 // try to load safely
441 fs::path converted_path(path.toStdString(), fs::native);
442 analysis->load_motifs(converted_path);
443 default_dir = converted_path.branch_path();
444 emit changedMotifs();
445 } catch (runtime_error e) {
446 QString msg("Unable to load ");
450 QMessageBox::warning(this, caption, msg);
454 void MussaWindow::saveMotifList()
456 QString caption("Mussa Save Motifs");
457 QString filter("Motif list(*.txt *.mtl)");
458 QDir default_qdir(default_dir.native_directory_string().c_str());
459 QString path = QFileDialog::getSaveFileName(this,
461 default_qdir.absolutePath(),
466 // try to load safely
468 fs::path converted_path(path.toStdString(), fs::native);
469 analysis->save_motifs(converted_path);
470 default_dir = converted_path.branch_path();
471 } catch (runtime_error e) {
472 QString msg("Unable to save ");
476 QMessageBox::warning(this, caption, msg);
479 void MussaWindow::loadMupa()
481 QString caption("Load a mussa parameter file");
482 QString filter("Mussa Parameters (*.mupa)");
483 QDir default_qdir(QDir(default_dir.native_directory_string().c_str()));
485 QString mupa_path = QFileDialog::getOpenFileName(
488 default_qdir.absolutePath(),
492 if (mupa_path.isNull())
494 // try to load safely
496 // ideally we should open a new window if there's an analysis
497 // but this should work for the moment.
498 if (not isClearingAnalysisSafe()) return;
500 MussaRef m(new Mussa);
501 fs::path converted_path(mupa_path.toStdString(), fs::native);
502 connect(m.get(), SIGNAL(progress(const std::string&, int, int)),
503 this, SLOT(updateProgress(const std::string&, int, int)));
504 m->load_mupa_file(converted_path);
508 // grab the path ignoring the mupa file portion
509 default_dir = converted_path.branch_path();
510 } catch (mussa_load_error e) {
511 QString msg("Unable to load ");
515 QMessageBox::warning(this, "Load Parameter", msg);
517 assert (analysis != 0);
520 void MussaWindow::loadSavedAnalysis()
522 QDir default_qdir(QDir(default_dir.native_directory_string().c_str()));
523 QString caption("Load a previously run analysis");
524 QString muway_dir = QFileDialog::getExistingDirectory(
527 default_qdir.absolutePath()
530 if (muway_dir.isNull())
532 // try to safely load
534 // ideally we should open a new window if there's an analysis
535 // but this should work for the moment.
536 if (not isClearingAnalysisSafe()) return;
538 MussaRef m(new Mussa);
539 fs::path converted_path(muway_dir.toStdString(), fs::native);
540 connect(m.get(), SIGNAL(progress(const std::string&, int, int)),
541 this, SLOT(updateProgress(const std::string&, int, int)));
542 m->load(converted_path);
543 // only switch mussas if we loaded without error
544 if (analysis->empty()) {
545 // our current window is empty so load and replace.
548 default_dir = converted_path.branch_path();
550 MussaWindow *win = new MussaWindow(m);
552 win->default_dir = converted_path.branch_path();
555 } catch (mussa_load_error e) {
556 QString msg("Unable to load ");
560 QMessageBox::warning(this, "Load Parameter", msg);
562 assert (analysis != 0);
565 void MussaWindow::newMussaWindow()
567 MussaRef a(new Mussa);
568 MussaWindow *win = new MussaWindow(a);
569 win->default_dir = default_dir;
573 void MussaWindow::setSoftThreshold(int threshold)
575 if (analysis->get_soft_threshold() != threshold) {
576 analysis->set_soft_threshold(threshold);
583 void MussaWindow::showMussaToolbar()
585 if (mussaViewTB->isVisible())
591 void MussaWindow::toggleMotifs()
596 void MussaWindow::showManual()
598 #if defined(QT_QTASSISTANT_FOUND)
599 if (manualAssistant) {
600 manualAssistant->openAssistant();
602 QMessageBox::warning(this,
603 tr("Mussa Help Error"),
604 tr("QtAssistant not setup correctly"),
606 QMessageBox::NoButton,
607 QMessageBox::NoButton);
611 boost::python::object webopen = get_py()["webbrowser.open"];
612 webopen("http://woldlab.caltech.edu/~king/mussagl_manual/");
613 } catch( boost::python::error_already_set ) {
615 QMessageBox::warning(this,
616 tr("Mussa Help Error"),
617 tr("Unable to launch webbrowser"),
619 QMessageBox::NoButton,
620 QMessageBox::NoButton);
622 #endif //QT_QTASSISTANT_FOUND
625 void MussaWindow::assistantError(QString message)
627 //std::cout << "QAssistantError: " << message.toStdString() << "\n";
628 QMessageBox::warning ( this, "Warning: Mussagl Manual", message,
630 QMessageBox::NoButton);
633 void MussaWindow::NotImplementedBox()
635 QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
638 void MussaWindow::viewMussaAlignment()
640 const set<int>& selected_paths = browser->selectedPaths();
641 if (selected_paths.size() == 0 ) {
642 QMessageBox::warning(this,
643 QObject::tr("mussa"),
644 QObject::tr("you should probably select some paths "
647 MussaAlignedWindowRef ma_win(
648 new MussaAlignedWindow(analysis, selected_paths, subanalysis_window)
651 aligned_windows.push_back(ma_win);
652 connect(this, SIGNAL(changedAnnotations()),
653 aligned_windows.back().get(), SLOT(update()));
654 aligned_windows.back()->show();
658 void MussaWindow::updateAnalysis()
660 const Mussa::vector_sequence_type& seqs = analysis->sequences();
661 browser->setSequences(seqs, analysis->colorMapper());
662 assert(browser->sequences().size() == analysis->size());
664 // setRange eventually emits something that causes updateLinks to be called
665 // but it's possible for us to not have had a chance to set out sequences
667 threshold->setRange(analysis->get_threshold(),analysis->get_window());
670 zoom->setValue(browser->zoom());
673 void MussaWindow::updateAnnotations()
675 // motifs were changed in the sequences by
676 // Mussa::update_sequences_motifs
677 emit changedAnnotations();
681 void MussaWindow::updateLinks()
683 if(browser->sequences().size() == 0) {
684 // we don't have any sequences load so we have no business setting links
688 browser->clear_links();
689 bool reversed = false;
690 const NwayPaths& nway = analysis->paths();
692 typedef list<ConservedPath> conserved_paths;
693 typedef conserved_paths::const_iterator const_conserved_paths_itor;
694 for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
695 path_itor != nway.refined_pathz.end();
698 // since we were drawing to the start of a window, and opengl lines
699 // are centered around the two connecting points our lines were slightly
700 // offset. the idea of window_offset is to adjust them to the
701 // right for forward compliment or left for reverse compliment
702 // FIXME: figure out how to unit test these computations
703 //GLfloat window_offset = (path_itor->window_size)/2.0;
705 size_t track_len = path_itor->track_indexes.size();
706 vector<int> normalized_path;
707 normalized_path.reserve(track_len);
708 vector<bool> rc_flags(false, track_len);
709 for (size_t track_i=0; track_i != track_len; ++track_i)
711 int x = path_itor->track_indexes[track_i];
712 // at some point when we modify the pathz data structure to keep
713 // track of the score we can put grab the depth here.
715 // are we reverse complimented?
723 normalized_path.push_back(x);
724 rc_flags.push_back(reversed);
726 browser->link(normalized_path, rc_flags, path_itor->window_size);
732 MussaWindow::updateProgress(const string& description, int current, int max)
735 if (current == max) {
736 if (progress_dialog != 0) {
737 progress_dialog->hide();
738 delete progress_dialog;
742 // if we're starting, create the dialog
743 if (progress_dialog == 0) {
744 QString desc(description.c_str());
745 QString cancel("Cancel");
746 progress_dialog = new QProgressDialog(desc, cancel, current, max, this);
747 progress_dialog->show();
749 // just update the dialog
750 progress_dialog->setValue(current);
753 qApp->processEvents();
756 void MussaWindow::updateTitle()
759 setWindowTitle(analysis->get_title().c_str());