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_ASSISTANT_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("Load a motif list");
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, "Load Motifs", msg);
452 assert (analysis != 0);
455 void MussaWindow::saveMotifList()
460 void MussaWindow::loadMupa()
462 QString caption("Load a mussa parameter file");
463 QString filter("Mussa Parameters (*.mupa)");
464 QDir default_qdir(QDir(default_dir.native_directory_string().c_str()));
466 QString mupa_path = QFileDialog::getOpenFileName(
469 default_qdir.absolutePath(),
473 if (mupa_path.isNull())
475 // try to load safely
477 // ideally we should open a new window if there's an analysis
478 // but this should work for the moment.
479 if (not isClearingAnalysisSafe()) return;
481 MussaRef m(new Mussa);
482 fs::path converted_path(mupa_path.toStdString(), fs::native);
483 connect(m.get(), SIGNAL(progress(const std::string&, int, int)),
484 this, SLOT(updateProgress(const std::string&, int, int)));
485 m->load_mupa_file(converted_path);
489 // grab the path ignoring the mupa file portion
490 default_dir = converted_path.branch_path();
491 } catch (mussa_load_error e) {
492 QString msg("Unable to load ");
496 QMessageBox::warning(this, "Load Parameter", msg);
498 assert (analysis != 0);
501 void MussaWindow::loadSavedAnalysis()
503 QDir default_qdir(QDir(default_dir.native_directory_string().c_str()));
504 QString caption("Load a previously run analysis");
505 QString muway_dir = QFileDialog::getExistingDirectory(
508 default_qdir.absolutePath()
511 if (muway_dir.isNull())
513 // try to safely load
515 // ideally we should open a new window if there's an analysis
516 // but this should work for the moment.
517 if (not isClearingAnalysisSafe()) return;
519 MussaRef m(new Mussa);
520 fs::path converted_path(muway_dir.toStdString(), fs::native);
521 connect(m.get(), SIGNAL(progress(const std::string&, int, int)),
522 this, SLOT(updateProgress(const std::string&, int, int)));
523 m->load(converted_path);
524 // only switch mussas if we loaded without error
525 if (analysis->empty()) {
526 // our current window is empty so load and replace.
529 default_dir = converted_path.branch_path();
531 MussaWindow *win = new MussaWindow(m);
533 win->default_dir = converted_path.branch_path();
536 } catch (mussa_load_error e) {
537 QString msg("Unable to load ");
541 QMessageBox::warning(this, "Load Parameter", msg);
543 assert (analysis != 0);
546 void MussaWindow::newMussaWindow()
548 MussaRef a(new Mussa);
549 MussaWindow *win = new MussaWindow(a);
550 win->default_dir = default_dir;
554 void MussaWindow::setSoftThreshold(int threshold)
556 if (analysis->get_soft_threshold() != threshold) {
557 analysis->set_soft_threshold(threshold);
564 void MussaWindow::showMussaToolbar()
566 if (mussaViewTB->isVisible())
572 void MussaWindow::toggleMotifs()
577 void MussaWindow::showManual()
579 #if defined(QT_QTASSISTANT_FOUND)
580 if (manualAssistant) {
581 manualAssistant->openAssistant();
585 boost::python::object webopen = get_py()["webbrowser.open"];
586 webopen("http://woldlab.caltech.edu/~king/mussagl_manual/");
588 } catch( boost::python::error_already_set ) {
591 #endif //QT_QTASSISTANT_FOUND
592 QMessageBox::warning(this,
593 tr("Unhelpful Help"),
594 tr("Error loading help"),
596 QMessageBox::NoButton,
597 QMessageBox::NoButton);
600 void MussaWindow::assistantError(QString message)
602 //std::cout << "QAssistantError: " << message.toStdString() << "\n";
603 QMessageBox::warning ( this, "Warning: Mussagl Manual", message,
605 QMessageBox::NoButton);
608 void MussaWindow::NotImplementedBox()
610 QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
613 void MussaWindow::viewMussaAlignment()
615 const set<int>& selected_paths = browser->selectedPaths();
616 if (selected_paths.size() == 0 ) {
617 QMessageBox::warning(this,
618 QObject::tr("mussa"),
619 QObject::tr("you should probably select some paths "
622 MussaAlignedWindowRef ma_win(
623 new MussaAlignedWindow(analysis, selected_paths, subanalysis_window)
626 aligned_windows.push_back(ma_win);
627 connect(this, SIGNAL(changedAnnotations()),
628 aligned_windows.back().get(), SLOT(update()));
629 aligned_windows.back()->show();
633 void MussaWindow::updateAnalysis()
635 const Mussa::vector_sequence_type& seqs = analysis->sequences();
636 browser->setSequences(seqs, analysis->colorMapper());
637 assert(browser->sequences().size() == analysis->size());
639 // setRange eventually emits something that causes updateLinks to be called
640 // but it's possible for us to not have had a chance to set out sequences
642 threshold->setRange(analysis->get_threshold(),analysis->get_window());
645 zoom->setValue(browser->zoom());
648 void MussaWindow::updateAnnotations()
650 // motifs were changed in the sequences by
651 // Mussa::update_sequences_motifs
652 emit changedAnnotations();
656 void MussaWindow::updateLinks()
658 if(browser->sequences().size() == 0) {
659 // we don't have any sequences load so we have no business setting links
663 browser->clear_links();
664 bool reversed = false;
665 const NwayPaths& nway = analysis->paths();
667 typedef list<ConservedPath> conserved_paths;
668 typedef conserved_paths::const_iterator const_conserved_paths_itor;
669 for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
670 path_itor != nway.refined_pathz.end();
673 // since we were drawing to the start of a window, and opengl lines
674 // are centered around the two connecting points our lines were slightly
675 // offset. the idea of window_offset is to adjust them to the
676 // right for forward compliment or left for reverse compliment
677 // FIXME: figure out how to unit test these computations
678 //GLfloat window_offset = (path_itor->window_size)/2.0;
680 size_t track_len = path_itor->track_indexes.size();
681 vector<int> normalized_path;
682 normalized_path.reserve(track_len);
683 vector<bool> rc_flags(false, track_len);
684 for (size_t track_i=0; track_i != track_len; ++track_i)
686 int x = path_itor->track_indexes[track_i];
687 // at some point when we modify the pathz data structure to keep
688 // track of the score we can put grab the depth here.
690 // are we reverse complimented?
698 normalized_path.push_back(x);
699 rc_flags.push_back(reversed);
701 browser->link(normalized_path, rc_flags, path_itor->window_size);
707 MussaWindow::updateProgress(const string& description, int current, int max)
710 if (current == max) {
711 if (progress_dialog != 0) {
712 progress_dialog->hide();
713 delete progress_dialog;
717 // if we're starting, create the dialog
718 if (progress_dialog == 0) {
719 QString desc(description.c_str());
720 QString cancel("Cancel");
721 progress_dialog = new QProgressDialog(desc, cancel, current, max, this);
722 progress_dialog->show();
724 // just update the dialog
725 progress_dialog->setValue(current);
728 qApp->processEvents();
731 void MussaWindow::updateTitle()
734 setWindowTitle(analysis->get_title().c_str());