1 #include "py/python.hpp"
2 #include "qui/MussaWindow.hpp"
3 #include "mussa_exceptions.hpp"
7 #include <QApplication>
8 #include <QAssistantClient>
11 #include <QFileDialog>
12 #include <QHBoxLayout>
15 #include <QMessageBox>
19 #include <QStringList>
26 #include <boost/filesystem/path.hpp>
27 #include <boost/filesystem/operations.hpp>
28 #include <boost/filesystem/convenience.hpp>
29 namespace fs = boost::filesystem;
30 #include <boost/bind.hpp>
34 MussaWindow::MussaWindow(MussaRef analysis_, QWidget *parent) :
37 default_dir(new QDir(QDir::home().absolutePath())),
39 setup_analysis_dialog(new MussaSetupDialog(this)),
40 subanalysis_window(new SubanalysisWindow(analysis)),
41 browser(new SequenceBrowserWidget(default_dir, this)),
42 mussaViewTB(new QToolBar("Path Views")),
44 threshold(new ThresholdWidget),
48 createNewAnalysisAction(0),
49 createSubAnalysisAction(0),
51 loadMotifListAction(0),
53 loadSavedAnalysisAction(0),
54 mussaManualAssistantAction(0),
55 newMussaWindowAction(0),
56 saveMotifListAction(0),
57 showMussaViewToolbarAction(0),
58 toggleMotifsAction(0),
59 saveBrowserPixmapAction(0),
61 viewMussaAlignmentAction(0),
68 //This next setWhatsThis function prevents
69 // a segfault when using WhatsThis feature with
71 //scene->setWhatsThis(tr("Mussa in OpenGL!"));
72 setCentralWidget(browser);
73 // well updatePosition isn't quite right as we really just need
75 connect(this, SIGNAL(changedAnnotations()), browser, SLOT(update()));
76 connect(this, SIGNAL(changedMotifs()), this, SLOT(updateAnnotations()));
77 connect(browser, SIGNAL(basepairsCopied(size_t)),
78 this, SLOT(showBasePairsCopied(size_t)));
80 //mussaViewTB->addAction(toggleMotifsAction);
81 mussaViewTB->addWidget(zoom);
83 connect(zoom, SIGNAL(valueChanged(double)),
84 browser, SLOT(setZoom(double)));
86 // threshold range is set in updateAnalysis
88 //scene->setClipPlane(20);
89 // FIXME: for when we get the paths drawn at the appropriate depth
90 //connect(threshold, SIGNAL(thresholdChanged(int)),
91 // this, SLOT(setClipPlane(int)));
92 connect(threshold, SIGNAL(thresholdChanged(int)),
93 this, SLOT(setSoftThreshold(int)));
94 mussaViewTB->addWidget(threshold);
96 addToolBar(mussaViewTB);
98 statusBar()->showMessage("Welcome to mussa", 2000);
99 // FIXME: we should start refactoring the connect call to updateAnalysis or something
101 connect(analysis.get(), SIGNAL(progress(const std::string&, int, int)),
102 this, SLOT(updateProgress(const std::string&, int, int)));
103 connect(analysis.get(), SIGNAL(isModified(bool)),
104 this, SLOT(updateAnalysisModified(bool)));
110 void MussaWindow::setAnalysis(MussaRef new_analysis)
112 if (new_analysis != 0) {
113 // only switch mussas if we loaded without error
115 analysis.swap(new_analysis);
121 void MussaWindow::setupActions()
123 // we really don't want to run this more than once.
124 assert (closeAction == 0);
126 // the ever popular about box
127 aboutAction = new QAction(tr("&About"), this);
128 connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
129 aboutAction->setIcon(QIcon(":/icons/info.png"));
132 closeAction = new QAction(tr("&Close"), this);
133 closeAction->setStatusTip(tr("Close this window"));
134 connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
135 closeAction->setIcon(QIcon(":/icons/exit.png"));
137 createNewAnalysisAction = new QAction(tr("Create &New Analysis"), this);
138 connect(createNewAnalysisAction, SIGNAL(triggered()),
139 this, SLOT(createNewAnalysis()));
140 createNewAnalysisAction->setIcon(QIcon(":/icons/filenew.png"));
141 createNewAnalysisAction->setShortcut(Qt::CTRL | Qt::Key_N);
143 createSubAnalysisAction = new QAction(tr("Add to Subanalysis"), this);
144 connect(createSubAnalysisAction, SIGNAL(triggered()),
145 this, SLOT(createSubAnalysis()));
147 saveAnalysisAction = new QAction(tr("&Save Analysis"), this);
148 connect(saveAnalysisAction, SIGNAL(triggered()),
149 this, SLOT(saveAnalysis()));
150 saveAnalysisAction->setIcon(QIcon(":/icons/filesave.png"));
151 saveAnalysisAction->setShortcut(Qt::CTRL | Qt::Key_S);
153 saveAnalysisAsAction = new QAction(tr("Save Analysis &As"), this);
154 connect(saveAnalysisAsAction, SIGNAL(triggered()),
155 this, SLOT(saveAnalysisAs()));
156 saveAnalysisAsAction->setIcon(QIcon(":/icons/filesave.png"));
158 editMotifsAction = new QAction(tr("Edit Motifs"), this);;
159 connect(editMotifsAction, SIGNAL(triggered()), this, SLOT(editMotifs()));
161 loadMotifListAction = new QAction(tr("Open Motif List"), this);
162 connect(loadMotifListAction, SIGNAL(triggered()),
163 this, SLOT(loadMotifList()));
164 loadMotifListAction->setIcon(QIcon(":/icons/fileopen.png"));
166 loadMupaAction = new QAction(tr("Create Analysis from File"), this);
167 connect(loadMupaAction, SIGNAL(triggered()),
168 this, SLOT(loadMupa()));
169 loadMupaAction->setIcon(QIcon(":/icons/fileopen.png"));
171 loadSavedAnalysisAction = new QAction(tr("&Open Existing &Analysis"), this);
172 connect(loadSavedAnalysisAction, SIGNAL(triggered()),
173 this, SLOT(loadSavedAnalysis()));
174 loadSavedAnalysisAction->setIcon(QIcon(":/icons/fileopen.png"));
175 loadSavedAnalysisAction->setShortcut(Qt::CTRL | Qt::Key_O);
177 mussaManualAssistantAction = new QAction(tr("Mussagl Manual..."), this);
178 mussaManualAssistantAction->setIcon(QIcon(":/icons/contents.png"));
179 connect(mussaManualAssistantAction, SIGNAL(triggered()),
180 this, SLOT(showManual()));
182 newMussaWindowAction = new QAction(tr("&New Mussa Window"), this);
183 newMussaWindowAction->setStatusTip("open another mussa window to allow comparing results");
184 connect(newMussaWindowAction, SIGNAL(triggered()),
185 this, SLOT(newMussaWindow()));
186 newMussaWindowAction->setIcon(QIcon(":/icons/window_new.png"));
188 saveMotifListAction = new QAction(tr("Save Motifs"), this);
189 connect(saveMotifListAction, SIGNAL(triggered()),
190 this, SLOT(saveMotifList()));
191 saveMotifListAction->setIcon(QIcon(":/icons/filesave.png"));
193 showMussaViewToolbarAction = new QAction(tr("Show Toolbar"), this);
194 connect(showMussaViewToolbarAction, SIGNAL(triggered()),
195 this, SLOT(showMussaToolbar()));
196 showMussaViewToolbarAction->setCheckable(true);
197 showMussaViewToolbarAction->setChecked(true);
199 toggleMotifsAction = new QAction(tr("Toggle Motifs"), this);
200 connect(toggleMotifsAction, SIGNAL(triggered()),
201 this, SLOT(toggleMotifs()));
202 toggleMotifsAction->setCheckable(true);
203 toggleMotifsAction->setIcon(QIcon(":/icons/motif_icon.png"));
204 toggleMotifsAction->setWhatsThis(tr("Toggle motif annotations on/off\n\n"
205 "You can load motif annotations via "
206 "'File->Load Motif List' menu option."));
208 //Save pixel map action
209 saveBrowserPixmapAction = new QAction(tr("Save to image..."), this);
210 connect(saveBrowserPixmapAction, (SIGNAL(triggered())),
211 browser, SLOT(promptSaveBrowserPixmap()));
212 saveBrowserPixmapAction->setIcon(QIcon(":/icons/image2.png"));
214 viewMussaAlignmentAction = new QAction(tr("View sequence alignment"), this);
215 connect(viewMussaAlignmentAction, SIGNAL(triggered()),
216 this, SLOT(viewMussaAlignment() ));
217 viewMussaAlignmentAction->setWhatsThis(tr("Create a zoomed in window "
218 "showing alignment of the seqcomp "
221 whatsThisAction = QWhatsThis::createAction(this);
222 whatsThisAction->setIcon(QIcon(":/icons/help.png"));
227 void MussaWindow::closeEvent(QCloseEvent *event)
229 if(isClearingAnalysisSafe()) {
236 void MussaWindow::setupMainMenu()
238 // we need to run setupActions first
239 assert (closeAction != 0);
241 QMenu *newMenu = menuBar()->addMenu(tr("&File"));
243 newMenu->addAction(newMussaWindowAction);
244 newMenu->addAction(createNewAnalysisAction);
245 newMenu->addAction(loadMupaAction);
246 newMenu->addAction(loadSavedAnalysisAction);
247 newMenu->addAction(saveAnalysisAction);
248 newMenu->addAction(saveAnalysisAsAction);
249 newMenu->addSeparator();
250 newMenu->addAction(loadMotifListAction);
251 newMenu->addAction(saveMotifListAction);
252 newMenu->addSeparator();
253 newMenu->addAction(saveBrowserPixmapAction);
254 newMenu->addSeparator();
255 newMenu->addAction(closeAction);
257 newMenu = menuBar()->addMenu(tr("&Edit"));
258 newMenu->addAction(editMotifsAction);
259 newMenu->addAction(browser->getCopySelectedSequenceAsStringAction());
260 newMenu->addAction(browser->getCopySelectedSequenceAsFastaAction());
261 newMenu->addAction(createSubAnalysisAction);
263 newMenu = menuBar()->addMenu(tr("&View"));
264 newMenu->addAction(viewMussaAlignmentAction);
265 newMenu->addAction(showMussaViewToolbarAction);
267 newMenu = menuBar()->addMenu(tr("&Help"));
268 newMenu->addAction(mussaManualAssistantAction);
269 newMenu->addAction(whatsThisAction);
270 newMenu->addSeparator();
271 newMenu->addAction(aboutAction);
273 // add some extra features to the context menu
274 QMenu *popupMenu = browser->getPopupMenu();
276 popupMenu->addAction(viewMussaAlignmentAction);
277 popupMenu->addAction(createSubAnalysisAction);
281 void MussaWindow::setupAssistant()
283 #if defined(QT_QTASSISTANT_FOUND)
284 QStringList manualAssistantArgs;
285 manualAssistantArgs = QStringList();
286 manualAssistantArgs << "-profile" << "./doc/manual/mussagl_manual.adp";
287 manualAssistant = new QAssistantClient("assistant", this);
288 manualAssistant->setArguments(manualAssistantArgs);
289 connect(manualAssistant, SIGNAL(error(QString)),
290 this, SLOT(assistantError(QString)));
294 void MussaWindow::about()
297 msg += "Welcome to Multiple Species Sequence Analysis\n";
298 msg += "(c) 2005-2006 California Institute of Technology\n";
299 msg += "Diane Trout, Tristan De Buysscher, Brandon King\n";
301 msg += mussa_version;
304 msg += (char *)glGetString(GL_VERSION);
306 QMessageBox::about(this, tr("About mussa"), msg);
309 void MussaWindow::clear()
311 aligned_windows.clear();
315 void MussaWindow::createNewAnalysis()
318 // ideally we should open a new window if there's an analysis
319 // but this should work for the moment.
320 if (not isClearingAnalysisSafe()) return;
322 if (setup_analysis_dialog->exec()) {
323 setAnalysis(setup_analysis_dialog->getMussa());
325 } catch(mussa_error e) {
326 QString msg(e.what());
327 QMessageBox::warning(this, tr("Create New Analysis"), msg);
331 void MussaWindow::createSubAnalysis()
333 list<SequenceLocation> result;
334 SequenceLocationModel& model = subanalysis_window->getModel();
335 browser->copySelectedTracksAsSeqLocation(result);
336 for(list<SequenceLocation>::iterator result_itor = result.begin();
337 result_itor != result.end();
340 model.push_back(*result_itor);
343 if (not subanalysis_window->isVisible()) {
344 subanalysis_window->show();
348 void MussaWindow::saveAnalysis()
350 // if we've got an analysis
351 if (analysis and not analysis->empty()) {
352 // if it doesn't have a name we need to pick one
353 if (analysis->get_analysis_path().empty()) {
356 // if we've got a name, has it changed any?
357 // this doesn't work when a sequence changes something (like its
359 //else if (analysis->is_dirty())
362 } catch (std::exception e) {
363 QMessageBox::critical(this,
364 tr("Mussa Save Error"),
366 QMessageBox::Ok, 0, 0);
373 void MussaWindow::saveAnalysisAs()
375 std::auto_ptr<QFileDialog> dialog(new QFileDialog(this));
376 dialog->setAcceptMode(QFileDialog::AcceptSave);
377 dialog->setFileMode(QFileDialog::AnyFile);
378 dialog->setDirectory(*default_dir);
380 QStringList fileNames;
381 if (not dialog->exec()) {
384 fileNames = dialog->selectedFiles();
386 if (fileNames.size() != 1) {
390 fs::path save_path(fileNames[0].toStdString(), fs::native);
391 // do you want to overwrite?
392 if (fs::exists(save_path) and
393 QMessageBox::question(
395 tr("Overwrite File? -- Mussa"),
396 tr("A file called %1 already exists"
397 "do you want to overwrite it?")
399 tr("&Yes"), tr("&No"),
404 analysis->save(save_path);
405 fs::path normalized_path = (save_path / "..").normalize();
406 default_dir->setPath(normalized_path.native_directory_string().c_str());
407 } catch (std::exception e) {
408 QMessageBox::critical(this,
409 tr("Mussa Save Error"),
411 QMessageBox::Ok, 0, 0);
415 bool MussaWindow::isClearingAnalysisSafe()
417 if (analysis and not analysis->empty() and analysis->is_dirty()) {
418 switch (QMessageBox::question(
420 tr("Save Unsaved Changes -- Mussa"),
421 tr("There are unsaved changes,\ndo you want to save?"),
422 tr("&Yes"), tr("&No"), tr("&Cancel"),
437 throw runtime_error("isClearingAnalysis QMesageBox failure");
440 // if we're here we've been saved and can replace
444 void MussaWindow::editMotifs()
446 if (motif_editor != 0) {
447 motif_editor->hide();
450 motif_editor = new MotifEditor(analysis);
451 connect(motif_editor, SIGNAL(changedMotifs()),
452 this, SLOT(updateAnnotations()));
453 motif_editor->show();
456 void MussaWindow::loadMotifList()
458 QString caption("Mussa Load Motifs");
459 QString filter("Motif list(*.txt *.mtl)");
460 QString path = QFileDialog::getOpenFileName(this,
462 default_dir->absolutePath(),
467 // try to load safely
469 fs::path converted_path(path.toStdString(), fs::native);
470 analysis->load_motifs(converted_path);
471 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
472 emit changedMotifs();
473 } catch (std::exception e) {
474 QString msg("Unable to load ");
478 QMessageBox::warning(this, caption, msg);
482 void MussaWindow::saveMotifList()
484 QString caption("Mussa Save Motifs");
485 QString filter("Motif list(*.txt *.mtl)");
486 QString path = QFileDialog::getSaveFileName(this,
488 default_dir->absolutePath(),
493 // try to load safely
495 fs::path converted_path(path.toStdString(), fs::native);
496 if (fs::extension(converted_path).size() == 0) {
497 // no extension, so add one
498 fs::path base_path = converted_path.branch_path();
499 fs::path filename(converted_path.leaf() + ".mtl", fs::native);
500 converted_path = base_path / filename;
502 analysis->save_motifs(converted_path);
503 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
504 } catch (std::exception e) {
505 QString msg("Unable to save ");
509 QMessageBox::warning(this, caption, msg);
512 void MussaWindow::loadMupa()
514 QString caption("Load a mussa parameter file");
515 QString filter("Mussa Parameters (*.mupa)");
516 QString mupa_path = QFileDialog::getOpenFileName(
519 default_dir->absolutePath(),
523 if (mupa_path.isNull())
525 // try to load safely
527 // ideally we should open a new window if there's an analysis
528 // but this should work for the moment.
529 if (not isClearingAnalysisSafe()) return;
531 MussaRef m(new Mussa);
532 fs::path converted_path(mupa_path.toStdString(), fs::native);
533 connect(m.get(), SIGNAL(progress(const std::string&, int, int)),
534 this, SLOT(updateProgress(const std::string&, int, int)));
535 m->load_mupa_file(converted_path);
539 // grab the path ignoring the mupa file portion
540 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
541 } catch (mussa_load_error e) {
542 QString msg("Unable to load ");
546 QMessageBox::warning(this, "Load Parameter", msg);
548 assert (analysis != 0);
551 void MussaWindow::loadSavedAnalysis()
553 QString caption("Load a previously run analysis");
554 QString muway_dir = QFileDialog::getExistingDirectory(
557 default_dir->absolutePath()
560 if (muway_dir.isNull())
562 // try to safely load
564 // ideally we should open a new window if there's an analysis
565 // but this should work for the moment.
566 if (not isClearingAnalysisSafe()) return;
568 MussaRef m(new Mussa);
569 fs::path converted_path(muway_dir.toStdString(), fs::native);
570 connect(m.get(), SIGNAL(progress(const std::string&, int, int)),
571 this, SLOT(updateProgress(const std::string&, int, int)));
572 m->load(converted_path);
573 // only switch mussas if we loaded without error
574 if (analysis->empty()) {
575 // our current window is empty so load and replace.
578 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
580 MussaWindow *win = new MussaWindow(m);
582 win->default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
585 } catch (mussa_load_error e) {
586 QString msg("Unable to load ");
590 QMessageBox::warning(this, "Load Parameter", msg);
592 assert (analysis != 0);
595 void MussaWindow::newMussaWindow()
597 MussaRef a(new Mussa);
598 MussaWindow *win = new MussaWindow(a);
599 win->default_dir = default_dir;
603 void MussaWindow::setSoftThreshold(int value)
605 if (analysis->get_soft_threshold() != value) {
606 threshold->setEnabled( false );
607 analysis->set_soft_threshold(value);
611 threshold->setEnabled( true );
615 void MussaWindow::showMussaToolbar()
617 if (mussaViewTB->isVisible())
623 void MussaWindow::showBasePairsCopied(size_t bp_copied)
625 QString msg("Copied ");
627 num.setNum(bp_copied);
628 msg += num + " base pairs";
629 statusBar()->showMessage(msg, 5000);
633 void MussaWindow::toggleMotifs()
638 void MussaWindow::showManual()
640 #if defined(QT_QTASSISTANT_FOUND)
641 if (manualAssistant) {
642 manualAssistant->openAssistant();
644 QMessageBox::warning(this,
645 tr("Mussa Help Error"),
646 tr("QtAssistant not setup correctly"),
648 QMessageBox::NoButton,
649 QMessageBox::NoButton);
653 boost::python::object webopen = get_py()["webbrowser.open"];
654 webopen("http://woldlab.caltech.edu/~king/mussagl_manual/");
655 } catch( boost::python::error_already_set ) {
657 QMessageBox::warning(this,
658 tr("Mussa Help Error"),
659 tr("Unable to launch webbrowser"),
661 QMessageBox::NoButton,
662 QMessageBox::NoButton);
664 #endif //QT_QTASSISTANT_FOUND
667 void MussaWindow::assistantError(QString message)
669 //std::cout << "QAssistantError: " << message.toStdString() << "\n";
670 QMessageBox::warning ( this, "Warning: Mussagl Manual", message,
672 QMessageBox::NoButton);
675 void MussaWindow::NotImplementedBox()
677 QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
680 void MussaWindow::viewMussaAlignment()
682 const set<int>& selected_paths = browser->selectedPaths();
683 if (selected_paths.size() == 0 ) {
684 QMessageBox::warning(this,
685 QObject::tr("mussa"),
686 QObject::tr("you should probably select some paths "
689 MussaAlignedWindowRef ma_win(
690 new MussaAlignedWindow(analysis, default_dir, selected_paths, subanalysis_window)
693 aligned_windows.push_back(ma_win);
694 connect(this, SIGNAL(changedAnnotations()),
695 aligned_windows.back().get(), SLOT(update()));
696 aligned_windows.back()->show();
700 void MussaWindow::updateAnalysis()
702 const Mussa::vector_sequence_type& seqs = analysis->sequences();
703 browser->setSequences(seqs, analysis->colorMapper());
704 assert(browser->sequences().size() == analysis->size());
706 // setRange eventually emits something that causes updateLinks to be called
707 // but it's possible for us to not have had a chance to set out sequences
709 threshold->setRange(analysis->get_threshold(),analysis->get_window());
712 zoom->setValue(browser->zoom());
715 void MussaWindow::updateAnnotations()
717 // motifs were changed in the sequences by
718 // Mussa::update_sequences_motifs
719 emit changedAnnotations();
723 void MussaWindow::updateLinks()
725 if(browser->sequences().size() == 0) {
726 // we don't have any sequences load so we have no business setting links
730 browser->clear_links();
731 bool reversed = false;
732 const NwayPaths& nway = analysis->paths();
734 typedef list<ConservedPath> conserved_paths;
735 typedef conserved_paths::const_iterator const_conserved_paths_itor;
736 for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
737 path_itor != nway.refined_pathz.end();
740 // since we were drawing to the start of a window, and opengl lines
741 // are centered around the two connecting points our lines were slightly
742 // offset. the idea of window_offset is to adjust them to the
743 // right for forward compliment or left for reverse compliment
744 // FIXME: figure out how to unit test these computations
745 //GLfloat window_offset = (path_itor->window_size)/2.0;
747 size_t track_len = path_itor->track_indexes.size();
748 vector<int> normalized_path;
749 normalized_path.reserve(track_len);
750 vector<bool> rc_flags(false, track_len);
751 for (size_t track_i=0; track_i != track_len; ++track_i)
753 int x = path_itor->track_indexes[track_i];
754 // at some point when we modify the pathz data structure to keep
755 // track of the score we can put grab the depth here.
757 // are we reverse complimented?
765 normalized_path.push_back(x);
766 rc_flags.push_back(reversed);
768 browser->link(normalized_path, rc_flags, path_itor->window_size);
774 MussaWindow::updateProgress(const string& description, int current, int max)
777 if (current == max) {
778 if (progress_dialog != 0) {
779 progress_dialog->hide();
780 delete progress_dialog;
784 // if we're starting, create the dialog
785 if (progress_dialog == 0) {
786 QString desc(description.c_str());
787 QString cancel("Cancel");
788 progress_dialog = new QProgressDialog(desc, cancel, current, max, this);
789 progress_dialog->show();
791 // just update the dialog
792 progress_dialog->setValue(current);
795 qApp->processEvents();
798 void MussaWindow::updateAnalysisModified(bool is_modified)
800 setWindowModified(is_modified);
803 void MussaWindow::updateTitle()
806 QString title(analysis->get_title().c_str());
808 setWindowTitle(title);