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(new QDir(QDir::home().absolutePath())),
37 setup_analysis_dialog(new MussaSetupDialog(this)),
38 subanalysis_window(new SubanalysisWindow(analysis)),
39 browser(new SequenceBrowserWidget(default_dir, 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->setPath(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->setPath(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->setPath(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->setPath(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->setPath(converted_path.branch_path().native_directory_string().c_str());
546 MussaWindow *win = new MussaWindow(m);
548 win->default_dir->setPath(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, default_dir, 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());