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()),
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(default_dir);
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 fs::path normalized_path = (save_path / "..").normalize();
384 default_dir = normalized_path.native_directory_string().c_str();
387 bool MussaWindow::isClearingAnalysisSafe()
389 if (analysis and not analysis->empty() and analysis->is_dirty()) {
390 switch (QMessageBox::question(
392 tr("Save Unsaved Changes -- Mussa"),
393 tr("There are unsaved changes,\ndo you want to save?"),
394 tr("&Yes"), tr("&No"), tr("&Cancel"),
409 throw runtime_error("isClearingAnalysis QMesageBox failure");
412 // if we're here we've been saved and can replace
416 void MussaWindow::editMotifs()
418 if (motif_editor != 0) {
419 motif_editor->hide();
422 motif_editor = new MotifEditor(analysis);
423 connect(motif_editor, SIGNAL(changedMotifs()),
424 this, SLOT(updateAnnotations()));
425 motif_editor->show();
428 void MussaWindow::loadMotifList()
430 QString caption("Mussa Load Motifs");
431 QString filter("Motif list(*.txt *.mtl)");
432 QString path = QFileDialog::getOpenFileName(this,
434 default_dir.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().native_directory_string().c_str();
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 QString path = QFileDialog::getSaveFileName(this,
460 default_dir.absolutePath(),
465 // try to load safely
467 fs::path converted_path(path.toStdString(), fs::native);
468 analysis->save_motifs(converted_path);
469 default_dir = converted_path.branch_path().native_directory_string().c_str();
470 } catch (runtime_error e) {
471 QString msg("Unable to save ");
475 QMessageBox::warning(this, caption, msg);
478 void MussaWindow::loadMupa()
480 QString caption("Load a mussa parameter file");
481 QString filter("Mussa Parameters (*.mupa)");
482 QString mupa_path = QFileDialog::getOpenFileName(
485 default_dir.absolutePath(),
489 if (mupa_path.isNull())
491 // try to load safely
493 // ideally we should open a new window if there's an analysis
494 // but this should work for the moment.
495 if (not isClearingAnalysisSafe()) return;
497 MussaRef m(new Mussa);
498 fs::path converted_path(mupa_path.toStdString(), fs::native);
499 connect(m.get(), SIGNAL(progress(const std::string&, int, int)),
500 this, SLOT(updateProgress(const std::string&, int, int)));
501 m->load_mupa_file(converted_path);
505 // grab the path ignoring the mupa file portion
506 default_dir = converted_path.branch_path().native_directory_string().c_str();
507 } catch (mussa_load_error e) {
508 QString msg("Unable to load ");
512 QMessageBox::warning(this, "Load Parameter", msg);
514 assert (analysis != 0);
517 void MussaWindow::loadSavedAnalysis()
519 QString caption("Load a previously run analysis");
520 QString muway_dir = QFileDialog::getExistingDirectory(
523 default_dir.absolutePath()
526 if (muway_dir.isNull())
528 // try to safely load
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(new Mussa);
535 fs::path converted_path(muway_dir.toStdString(), fs::native);
536 connect(m.get(), SIGNAL(progress(const std::string&, int, int)),
537 this, SLOT(updateProgress(const std::string&, int, int)));
538 m->load(converted_path);
539 // only switch mussas if we loaded without error
540 if (analysis->empty()) {
541 // our current window is empty so load and replace.
544 default_dir = converted_path.branch_path().native_directory_string().c_str();
546 MussaWindow *win = new MussaWindow(m);
548 win->default_dir = converted_path.branch_path().native_directory_string().c_str();
551 } catch (mussa_load_error e) {
552 QString msg("Unable to load ");
556 QMessageBox::warning(this, "Load Parameter", msg);
558 assert (analysis != 0);
561 void MussaWindow::newMussaWindow()
563 MussaRef a(new Mussa);
564 MussaWindow *win = new MussaWindow(a);
565 win->default_dir = default_dir;
569 void MussaWindow::setSoftThreshold(int threshold)
571 if (analysis->get_soft_threshold() != threshold) {
572 analysis->set_soft_threshold(threshold);
579 void MussaWindow::showMussaToolbar()
581 if (mussaViewTB->isVisible())
587 void MussaWindow::toggleMotifs()
592 void MussaWindow::showManual()
594 #if defined(QT_QTASSISTANT_FOUND)
595 if (manualAssistant) {
596 manualAssistant->openAssistant();
598 QMessageBox::warning(this,
599 tr("Mussa Help Error"),
600 tr("QtAssistant not setup correctly"),
602 QMessageBox::NoButton,
603 QMessageBox::NoButton);
607 boost::python::object webopen = get_py()["webbrowser.open"];
608 webopen("http://woldlab.caltech.edu/~king/mussagl_manual/");
609 } catch( boost::python::error_already_set ) {
611 QMessageBox::warning(this,
612 tr("Mussa Help Error"),
613 tr("Unable to launch webbrowser"),
615 QMessageBox::NoButton,
616 QMessageBox::NoButton);
618 #endif //QT_QTASSISTANT_FOUND
621 void MussaWindow::assistantError(QString message)
623 //std::cout << "QAssistantError: " << message.toStdString() << "\n";
624 QMessageBox::warning ( this, "Warning: Mussagl Manual", message,
626 QMessageBox::NoButton);
629 void MussaWindow::NotImplementedBox()
631 QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
634 void MussaWindow::viewMussaAlignment()
636 const set<int>& selected_paths = browser->selectedPaths();
637 if (selected_paths.size() == 0 ) {
638 QMessageBox::warning(this,
639 QObject::tr("mussa"),
640 QObject::tr("you should probably select some paths "
643 MussaAlignedWindowRef ma_win(
644 new MussaAlignedWindow(analysis, selected_paths, subanalysis_window)
647 aligned_windows.push_back(ma_win);
648 connect(this, SIGNAL(changedAnnotations()),
649 aligned_windows.back().get(), SLOT(update()));
650 aligned_windows.back()->show();
654 void MussaWindow::updateAnalysis()
656 const Mussa::vector_sequence_type& seqs = analysis->sequences();
657 browser->setSequences(seqs, analysis->colorMapper());
658 assert(browser->sequences().size() == analysis->size());
660 // setRange eventually emits something that causes updateLinks to be called
661 // but it's possible for us to not have had a chance to set out sequences
663 threshold->setRange(analysis->get_threshold(),analysis->get_window());
666 zoom->setValue(browser->zoom());
669 void MussaWindow::updateAnnotations()
671 // motifs were changed in the sequences by
672 // Mussa::update_sequences_motifs
673 emit changedAnnotations();
677 void MussaWindow::updateLinks()
679 if(browser->sequences().size() == 0) {
680 // we don't have any sequences load so we have no business setting links
684 browser->clear_links();
685 bool reversed = false;
686 const NwayPaths& nway = analysis->paths();
688 typedef list<ConservedPath> conserved_paths;
689 typedef conserved_paths::const_iterator const_conserved_paths_itor;
690 for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
691 path_itor != nway.refined_pathz.end();
694 // since we were drawing to the start of a window, and opengl lines
695 // are centered around the two connecting points our lines were slightly
696 // offset. the idea of window_offset is to adjust them to the
697 // right for forward compliment or left for reverse compliment
698 // FIXME: figure out how to unit test these computations
699 //GLfloat window_offset = (path_itor->window_size)/2.0;
701 size_t track_len = path_itor->track_indexes.size();
702 vector<int> normalized_path;
703 normalized_path.reserve(track_len);
704 vector<bool> rc_flags(false, track_len);
705 for (size_t track_i=0; track_i != track_len; ++track_i)
707 int x = path_itor->track_indexes[track_i];
708 // at some point when we modify the pathz data structure to keep
709 // track of the score we can put grab the depth here.
711 // are we reverse complimented?
719 normalized_path.push_back(x);
720 rc_flags.push_back(reversed);
722 browser->link(normalized_path, rc_flags, path_itor->window_size);
728 MussaWindow::updateProgress(const string& description, int current, int max)
731 if (current == max) {
732 if (progress_dialog != 0) {
733 progress_dialog->hide();
734 delete progress_dialog;
738 // if we're starting, create the dialog
739 if (progress_dialog == 0) {
740 QString desc(description.c_str());
741 QString cancel("Cancel");
742 progress_dialog = new QProgressDialog(desc, cancel, current, max, this);
743 progress_dialog->show();
745 // just update the dialog
746 progress_dialog->setValue(current);
749 qApp->processEvents();
752 void MussaWindow::updateTitle()
755 setWindowTitle(analysis->get_title().c_str());