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 QT_QTASSISTANT_FOUND
580 manualAssistant->openAssistant();
583 boost::python::object webopen = get_py()["webbrowser.open"];
584 webopen("http://woldlab.caltech.edu/~king/mussagl_manual/");
585 } catch( boost::python::error_already_set ) {
588 #endif //QT_QTASSISTANT_FOUND
591 void MussaWindow::assistantError(QString message)
593 //std::cout << "QAssistantError: " << message.toStdString() << "\n";
594 QMessageBox::warning ( this, "Warning: Mussagl Manual", message,
596 QMessageBox::NoButton);
599 void MussaWindow::NotImplementedBox()
601 QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
604 void MussaWindow::viewMussaAlignment()
606 const set<int>& selected_paths = browser->selectedPaths();
607 if (selected_paths.size() == 0 ) {
608 QMessageBox::warning(this,
609 QObject::tr("mussa"),
610 QObject::tr("you should probably select some paths "
613 MussaAlignedWindowRef ma_win(
614 new MussaAlignedWindow(analysis, selected_paths, subanalysis_window)
617 aligned_windows.push_back(ma_win);
618 connect(this, SIGNAL(changedAnnotations()),
619 aligned_windows.back().get(), SLOT(update()));
620 aligned_windows.back()->show();
624 void MussaWindow::updateAnalysis()
626 const Mussa::vector_sequence_type& seqs = analysis->sequences();
627 browser->setSequences(seqs, analysis->colorMapper());
628 assert(browser->sequences().size() == analysis->size());
630 // setRange eventually emits something that causes updateLinks to be called
631 // but it's possible for us to not have had a chance to set out sequences
633 threshold->setRange(analysis->get_threshold(),analysis->get_window());
636 zoom->setValue(browser->zoom());
639 void MussaWindow::updateAnnotations()
641 // motifs were changed in the sequences by
642 // Mussa::update_sequences_motifs
643 emit changedAnnotations();
647 void MussaWindow::updateLinks()
649 if(browser->sequences().size() == 0) {
650 // we don't have any sequences load so we have no business setting links
654 browser->clear_links();
655 bool reversed = false;
656 const NwayPaths& nway = analysis->paths();
658 typedef list<ConservedPath> conserved_paths;
659 typedef conserved_paths::const_iterator const_conserved_paths_itor;
660 for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
661 path_itor != nway.refined_pathz.end();
664 // since we were drawing to the start of a window, and opengl lines
665 // are centered around the two connecting points our lines were slightly
666 // offset. the idea of window_offset is to adjust them to the
667 // right for forward compliment or left for reverse compliment
668 // FIXME: figure out how to unit test these computations
669 //GLfloat window_offset = (path_itor->window_size)/2.0;
671 size_t track_len = path_itor->track_indexes.size();
672 vector<int> normalized_path;
673 normalized_path.reserve(track_len);
674 vector<bool> rc_flags(false, track_len);
675 for (size_t track_i=0; track_i != track_len; ++track_i)
677 int x = path_itor->track_indexes[track_i];
678 // at some point when we modify the pathz data structure to keep
679 // track of the score we can put grab the depth here.
681 // are we reverse complimented?
689 normalized_path.push_back(x);
690 rc_flags.push_back(reversed);
692 browser->link(normalized_path, rc_flags, path_itor->window_size);
698 MussaWindow::updateProgress(const string& description, int current, int max)
701 if (current == max) {
702 if (progress_dialog != 0) {
703 progress_dialog->hide();
704 delete progress_dialog;
708 // if we're starting, create the dialog
709 if (progress_dialog == 0) {
710 QString desc(description.c_str());
711 QString cancel("Cancel");
712 progress_dialog = new QProgressDialog(desc, cancel, current, max, this);
713 progress_dialog->show();
715 // just update the dialog
716 progress_dialog->setValue(current);
719 qApp->processEvents();
722 void MussaWindow::updateTitle()
725 setWindowTitle(analysis->get_title().c_str());