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()));
78 //mussaViewTB->addAction(toggleMotifsAction);
79 mussaViewTB->addWidget(zoom);
81 connect(zoom, SIGNAL(valueChanged(double)),
82 browser, SLOT(setZoom(double)));
84 // threshold range is set in updateAnalysis
86 //scene->setClipPlane(20);
87 // FIXME: for when we get the paths drawn at the appropriate depth
88 //connect(threshold, SIGNAL(thresholdChanged(int)),
89 // this, SLOT(setClipPlane(int)));
90 connect(threshold, SIGNAL(thresholdChanged(int)),
91 this, SLOT(setSoftThreshold(int)));
92 mussaViewTB->addWidget(threshold);
94 addToolBar(mussaViewTB);
96 statusBar()->showMessage("Welcome to mussa", 2000);
97 // FIXME: we should start refactoring the connect call to updateAnalysis or something
99 connect(analysis.get(), SIGNAL(progress(const std::string&, int, int)),
100 this, SLOT(updateProgress(const std::string&, int, int)));
106 void MussaWindow::setAnalysis(MussaRef new_analysis)
108 if (new_analysis != 0) {
109 // only switch mussas if we loaded without error
111 analysis.swap(new_analysis);
117 void MussaWindow::setupActions()
119 // we really don't want to run this more than once.
120 assert (closeAction == 0);
122 // the ever popular about box
123 aboutAction = new QAction(tr("&About"), this);
124 connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
125 aboutAction->setIcon(QIcon(":/icons/info.png"));
128 closeAction = new QAction(tr("&Close"), this);
129 closeAction->setStatusTip(tr("Close this window"));
130 connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
131 closeAction->setIcon(QIcon(":/icons/exit.png"));
133 createNewAnalysisAction = new QAction(tr("Create Analysis"), this);
134 connect(createNewAnalysisAction, SIGNAL(triggered()),
135 this, SLOT(createNewAnalysis()));
136 createNewAnalysisAction->setIcon(QIcon(":/icons/filenew.png"));
138 createSubAnalysisAction = new QAction(tr("Add to Subanalysis"), this);
139 connect(createSubAnalysisAction, SIGNAL(triggered()),
140 this, SLOT(createSubAnalysis()));
142 saveAnalysisAction = new QAction(tr("&Save Analysis"), this);
143 connect(saveAnalysisAction, SIGNAL(triggered()),
144 this, SLOT(saveAnalysis()));
145 saveAnalysisAction->setIcon(QIcon(":/icons/filesave.png"));
147 saveAnalysisAsAction = new QAction(tr("Save Analysis &As"), this);
148 connect(saveAnalysisAsAction, SIGNAL(triggered()),
149 this, SLOT(saveAnalysisAs()));
150 saveAnalysisAsAction->setIcon(QIcon(":/icons/filesave.png"));
152 editMotifsAction = new QAction(tr("Edit Motifs"), this);;
153 connect(editMotifsAction, SIGNAL(triggered()), this, SLOT(editMotifs()));
155 loadMotifListAction = new QAction(tr("Load Motif List"), this);
156 connect(loadMotifListAction, SIGNAL(triggered()),
157 this, SLOT(loadMotifList()));
158 loadMotifListAction->setIcon(QIcon(":/icons/fileopen.png"));
160 loadMupaAction = new QAction(tr("Create Analysis from File"), this);
161 connect(loadMupaAction, SIGNAL(triggered()),
162 this, SLOT(loadMupa()));
163 loadMupaAction->setIcon(QIcon(":/icons/fileopen.png"));
165 loadSavedAnalysisAction = new QAction(tr("Load Existing &Analysis"), this);
166 connect(loadSavedAnalysisAction, SIGNAL(triggered()),
167 this, SLOT(loadSavedAnalysis()));
168 loadSavedAnalysisAction->setIcon(QIcon(":/icons/fileopen.png"));
170 mussaManualAssistantAction = new QAction(tr("Mussagl Manual..."), this);
171 mussaManualAssistantAction->setIcon(QIcon(":/icons/contents.png"));
172 connect(mussaManualAssistantAction, SIGNAL(triggered()),
173 this, SLOT(showManual()));
175 newMussaWindowAction = new QAction(tr("&New Mussa Window"), this);
176 newMussaWindowAction->setStatusTip("open another mussa window to allow comparing results");
177 connect(newMussaWindowAction, SIGNAL(triggered()),
178 this, SLOT(newMussaWindow()));
179 newMussaWindowAction->setIcon(QIcon(":/icons/window_new.png"));
181 saveMotifListAction = new QAction(tr("Save Motifs"), this);
182 connect(saveMotifListAction, SIGNAL(triggered()),
183 this, SLOT(saveMotifList()));
184 saveMotifListAction->setIcon(QIcon(":/icons/filesave.png"));
186 showMussaViewToolbarAction = new QAction(tr("Show Toolbar"), this);
187 connect(showMussaViewToolbarAction, SIGNAL(triggered()),
188 this, SLOT(showMussaToolbar()));
189 showMussaViewToolbarAction->setCheckable(true);
190 showMussaViewToolbarAction->setChecked(true);
192 toggleMotifsAction = new QAction(tr("Toggle Motifs"), this);
193 connect(toggleMotifsAction, SIGNAL(triggered()),
194 this, SLOT(toggleMotifs()));
195 toggleMotifsAction->setCheckable(true);
196 toggleMotifsAction->setIcon(QIcon(":/icons/motif_icon.png"));
197 toggleMotifsAction->setWhatsThis(tr("Toggle motif annotations on/off\n\n"
198 "You can load motif annotations via "
199 "'File->Load Motif List' menu option."));
201 //Save pixel map action
202 saveBrowserPixmapAction = new QAction(tr("Save to image..."), this);
203 connect(saveBrowserPixmapAction, (SIGNAL(triggered())),
204 browser, SLOT(promptSaveBrowserPixmap()));
205 saveBrowserPixmapAction->setIcon(QIcon(":/icons/image2.png"));
207 viewMussaAlignmentAction = new QAction(tr("View sequence alignment"), this);
208 connect(viewMussaAlignmentAction, SIGNAL(triggered()),
209 this, SLOT(viewMussaAlignment() ));
210 viewMussaAlignmentAction->setWhatsThis(tr("Create a zoomed in window "
211 "showing alignment of the seqcomp "
214 whatsThisAction = QWhatsThis::createAction(this);
215 whatsThisAction->setIcon(QIcon(":/icons/help.png"));
220 void MussaWindow::closeEvent(QCloseEvent *event)
222 if(isClearingAnalysisSafe()) {
229 void MussaWindow::setupMainMenu()
231 // we need to run setupActions first
232 assert (closeAction != 0);
234 QMenu *newMenu = menuBar()->addMenu(tr("&File"));
236 newMenu->addAction(newMussaWindowAction);
237 newMenu->addAction(createNewAnalysisAction);
238 newMenu->addAction(loadMupaAction);
239 newMenu->addAction(loadSavedAnalysisAction);
240 newMenu->addAction(saveAnalysisAction);
241 newMenu->addAction(saveAnalysisAsAction);
242 newMenu->addSeparator();
243 newMenu->addAction(loadMotifListAction);
244 newMenu->addAction(saveMotifListAction);
245 newMenu->addSeparator();
246 newMenu->addAction(saveBrowserPixmapAction);
247 newMenu->addSeparator();
248 newMenu->addAction(closeAction);
250 newMenu = menuBar()->addMenu(tr("&Edit"));
251 newMenu->addAction(editMotifsAction);
252 newMenu->addAction(browser->getCopySelectedSequenceAsFastaAction());
253 newMenu->addAction(createSubAnalysisAction);
255 newMenu = menuBar()->addMenu(tr("&View"));
256 newMenu->addAction(viewMussaAlignmentAction);
257 newMenu->addAction(showMussaViewToolbarAction);
259 newMenu = menuBar()->addMenu(tr("&Help"));
260 newMenu->addAction(mussaManualAssistantAction);
261 newMenu->addAction(whatsThisAction);
262 newMenu->addSeparator();
263 newMenu->addAction(aboutAction);
265 // add some extra features to the context menu
266 QMenu *popupMenu = browser->getPopupMenu();
268 popupMenu->addAction(viewMussaAlignmentAction);
269 popupMenu->addAction(createSubAnalysisAction);
273 void MussaWindow::setupAssistant()
275 #if defined(QT_QTASSISTANT_FOUND)
276 QStringList manualAssistantArgs;
277 manualAssistantArgs = QStringList();
278 manualAssistantArgs << "-profile" << "./doc/manual/mussagl_manual.adp";
279 manualAssistant = new QAssistantClient("assistant", this);
280 manualAssistant->setArguments(manualAssistantArgs);
281 connect(manualAssistant, SIGNAL(error(QString)),
282 this, SLOT(assistantError(QString)));
286 void MussaWindow::about()
289 msg += "Welcome to Multiple Species Sequence Analysis\n";
290 msg += "(c) 2005-2006 California Institute of Technology\n";
291 msg += "Diane Trout, Tristan De Buysscher, Brandon King\n";
293 msg += mussa_version;
296 msg += (char *)glGetString(GL_VERSION);
298 QMessageBox::about(this, tr("About mussa"), msg);
301 void MussaWindow::clear()
303 aligned_windows.clear();
307 void MussaWindow::createNewAnalysis()
310 // ideally we should open a new window if there's an analysis
311 // but this should work for the moment.
312 if (not isClearingAnalysisSafe()) return;
314 if (setup_analysis_dialog->exec()) {
315 setAnalysis(setup_analysis_dialog->getMussa());
317 } catch(mussa_error e) {
318 QString msg(e.what());
319 QMessageBox::warning(this, tr("Create New Analysis"), msg);
323 void MussaWindow::createSubAnalysis()
325 list<SequenceLocation> result;
326 SequenceLocationModel& model = subanalysis_window->getModel();
327 browser->copySelectedTracksAsSeqLocation(result);
328 for(list<SequenceLocation>::iterator result_itor = result.begin();
329 result_itor != result.end();
332 model.push_back(*result_itor);
335 if (not subanalysis_window->isVisible()) {
336 subanalysis_window->show();
340 void MussaWindow::saveAnalysis()
342 // if we've got an analysis
343 if (analysis and not analysis->empty()) {
344 // if it doesn't have a name we need to pick one
345 if (analysis->get_analysis_path().empty()) {
348 // if we've got a name, has it changed any?
349 // this doesn't work when a sequence changes something (like its
351 //else if (analysis->is_dirty())
357 void MussaWindow::saveAnalysisAs()
359 std::auto_ptr<QFileDialog> dialog(new QFileDialog(this));
360 dialog->setAcceptMode(QFileDialog::AcceptSave);
361 dialog->setFileMode(QFileDialog::AnyFile);
362 dialog->setDirectory(*default_dir);
364 QStringList fileNames;
365 if (not dialog->exec()) {
368 fileNames = dialog->selectedFiles();
370 if (fileNames.size() != 1) {
373 fs::path save_path(fileNames[0].toStdString(), fs::native);
374 // do you want to overwrite?
375 if (fs::exists(save_path) and
376 QMessageBox::question(
378 tr("Overwrite File? -- Mussa"),
379 tr("A file called %1 already exists"
380 "do you want to overwrite it?")
382 tr("&Yes"), tr("&No"),
387 analysis->save(save_path);
388 fs::path normalized_path = (save_path / "..").normalize();
389 default_dir->setPath(normalized_path.native_directory_string().c_str());
392 bool MussaWindow::isClearingAnalysisSafe()
394 if (analysis and not analysis->empty() and analysis->is_dirty()) {
395 switch (QMessageBox::question(
397 tr("Save Unsaved Changes -- Mussa"),
398 tr("There are unsaved changes,\ndo you want to save?"),
399 tr("&Yes"), tr("&No"), tr("&Cancel"),
414 throw runtime_error("isClearingAnalysis QMesageBox failure");
417 // if we're here we've been saved and can replace
421 void MussaWindow::editMotifs()
423 if (motif_editor != 0) {
424 motif_editor->hide();
427 motif_editor = new MotifEditor(analysis);
428 connect(motif_editor, SIGNAL(changedMotifs()),
429 this, SLOT(updateAnnotations()));
430 motif_editor->show();
433 void MussaWindow::loadMotifList()
435 QString caption("Mussa Load Motifs");
436 QString filter("Motif list(*.txt *.mtl)");
437 QString path = QFileDialog::getOpenFileName(this,
439 default_dir->absolutePath(),
444 // try to load safely
446 fs::path converted_path(path.toStdString(), fs::native);
447 analysis->load_motifs(converted_path);
448 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
449 emit changedMotifs();
450 } catch (runtime_error e) {
451 QString msg("Unable to load ");
455 QMessageBox::warning(this, caption, msg);
459 void MussaWindow::saveMotifList()
461 QString caption("Mussa Save Motifs");
462 QString filter("Motif list(*.txt *.mtl)");
463 QString path = QFileDialog::getSaveFileName(this,
465 default_dir->absolutePath(),
470 // try to load safely
472 fs::path converted_path(path.toStdString(), fs::native);
473 if (fs::extension(converted_path).size() == 0) {
474 // no extension, so add one
475 converted_path = converted_path.string() + ".mtl";
477 analysis->save_motifs(converted_path);
478 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
479 } catch (runtime_error e) {
480 QString msg("Unable to save ");
484 QMessageBox::warning(this, caption, msg);
487 void MussaWindow::loadMupa()
489 QString caption("Load a mussa parameter file");
490 QString filter("Mussa Parameters (*.mupa)");
491 QString mupa_path = QFileDialog::getOpenFileName(
494 default_dir->absolutePath(),
498 if (mupa_path.isNull())
500 // try to load safely
502 // ideally we should open a new window if there's an analysis
503 // but this should work for the moment.
504 if (not isClearingAnalysisSafe()) return;
506 MussaRef m(new Mussa);
507 fs::path converted_path(mupa_path.toStdString(), fs::native);
508 connect(m.get(), SIGNAL(progress(const std::string&, int, int)),
509 this, SLOT(updateProgress(const std::string&, int, int)));
510 m->load_mupa_file(converted_path);
514 // grab the path ignoring the mupa file portion
515 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
516 } catch (mussa_load_error e) {
517 QString msg("Unable to load ");
521 QMessageBox::warning(this, "Load Parameter", msg);
523 assert (analysis != 0);
526 void MussaWindow::loadSavedAnalysis()
528 QString caption("Load a previously run analysis");
529 QString muway_dir = QFileDialog::getExistingDirectory(
532 default_dir->absolutePath()
535 if (muway_dir.isNull())
537 // try to safely load
539 // ideally we should open a new window if there's an analysis
540 // but this should work for the moment.
541 if (not isClearingAnalysisSafe()) return;
543 MussaRef m(new Mussa);
544 fs::path converted_path(muway_dir.toStdString(), fs::native);
545 connect(m.get(), SIGNAL(progress(const std::string&, int, int)),
546 this, SLOT(updateProgress(const std::string&, int, int)));
547 m->load(converted_path);
548 // only switch mussas if we loaded without error
549 if (analysis->empty()) {
550 // our current window is empty so load and replace.
553 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
555 MussaWindow *win = new MussaWindow(m);
557 win->default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
560 } catch (mussa_load_error e) {
561 QString msg("Unable to load ");
565 QMessageBox::warning(this, "Load Parameter", msg);
567 assert (analysis != 0);
570 void MussaWindow::newMussaWindow()
572 MussaRef a(new Mussa);
573 MussaWindow *win = new MussaWindow(a);
574 win->default_dir = default_dir;
578 void MussaWindow::setSoftThreshold(int threshold)
580 if (analysis->get_soft_threshold() != threshold) {
581 analysis->set_soft_threshold(threshold);
588 void MussaWindow::showMussaToolbar()
590 if (mussaViewTB->isVisible())
596 void MussaWindow::toggleMotifs()
601 void MussaWindow::showManual()
603 #if defined(QT_QTASSISTANT_FOUND)
604 if (manualAssistant) {
605 manualAssistant->openAssistant();
607 QMessageBox::warning(this,
608 tr("Mussa Help Error"),
609 tr("QtAssistant not setup correctly"),
611 QMessageBox::NoButton,
612 QMessageBox::NoButton);
616 boost::python::object webopen = get_py()["webbrowser.open"];
617 webopen("http://woldlab.caltech.edu/~king/mussagl_manual/");
618 } catch( boost::python::error_already_set ) {
620 QMessageBox::warning(this,
621 tr("Mussa Help Error"),
622 tr("Unable to launch webbrowser"),
624 QMessageBox::NoButton,
625 QMessageBox::NoButton);
627 #endif //QT_QTASSISTANT_FOUND
630 void MussaWindow::assistantError(QString message)
632 //std::cout << "QAssistantError: " << message.toStdString() << "\n";
633 QMessageBox::warning ( this, "Warning: Mussagl Manual", message,
635 QMessageBox::NoButton);
638 void MussaWindow::NotImplementedBox()
640 QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
643 void MussaWindow::viewMussaAlignment()
645 const set<int>& selected_paths = browser->selectedPaths();
646 if (selected_paths.size() == 0 ) {
647 QMessageBox::warning(this,
648 QObject::tr("mussa"),
649 QObject::tr("you should probably select some paths "
652 MussaAlignedWindowRef ma_win(
653 new MussaAlignedWindow(analysis, default_dir, selected_paths, subanalysis_window)
656 aligned_windows.push_back(ma_win);
657 connect(this, SIGNAL(changedAnnotations()),
658 aligned_windows.back().get(), SLOT(update()));
659 aligned_windows.back()->show();
663 void MussaWindow::updateAnalysis()
665 const Mussa::vector_sequence_type& seqs = analysis->sequences();
666 browser->setSequences(seqs, analysis->colorMapper());
667 assert(browser->sequences().size() == analysis->size());
669 // setRange eventually emits something that causes updateLinks to be called
670 // but it's possible for us to not have had a chance to set out sequences
672 threshold->setRange(analysis->get_threshold(),analysis->get_window());
675 zoom->setValue(browser->zoom());
678 void MussaWindow::updateAnnotations()
680 // motifs were changed in the sequences by
681 // Mussa::update_sequences_motifs
682 emit changedAnnotations();
686 void MussaWindow::updateLinks()
688 if(browser->sequences().size() == 0) {
689 // we don't have any sequences load so we have no business setting links
693 browser->clear_links();
694 bool reversed = false;
695 const NwayPaths& nway = analysis->paths();
697 typedef list<ConservedPath> conserved_paths;
698 typedef conserved_paths::const_iterator const_conserved_paths_itor;
699 for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
700 path_itor != nway.refined_pathz.end();
703 // since we were drawing to the start of a window, and opengl lines
704 // are centered around the two connecting points our lines were slightly
705 // offset. the idea of window_offset is to adjust them to the
706 // right for forward compliment or left for reverse compliment
707 // FIXME: figure out how to unit test these computations
708 //GLfloat window_offset = (path_itor->window_size)/2.0;
710 size_t track_len = path_itor->track_indexes.size();
711 vector<int> normalized_path;
712 normalized_path.reserve(track_len);
713 vector<bool> rc_flags(false, track_len);
714 for (size_t track_i=0; track_i != track_len; ++track_i)
716 int x = path_itor->track_indexes[track_i];
717 // at some point when we modify the pathz data structure to keep
718 // track of the score we can put grab the depth here.
720 // are we reverse complimented?
728 normalized_path.push_back(x);
729 rc_flags.push_back(reversed);
731 browser->link(normalized_path, rc_flags, path_itor->window_size);
737 MussaWindow::updateProgress(const string& description, int current, int max)
740 if (current == max) {
741 if (progress_dialog != 0) {
742 progress_dialog->hide();
743 delete progress_dialog;
747 // if we're starting, create the dialog
748 if (progress_dialog == 0) {
749 QString desc(description.c_str());
750 QString cancel("Cancel");
751 progress_dialog = new QProgressDialog(desc, cancel, current, max, this);
752 progress_dialog->show();
754 // just update the dialog
755 progress_dialog->setValue(current);
758 qApp->processEvents();
761 void MussaWindow::updateTitle()
764 setWindowTitle(analysis->get_title().c_str());