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()));
75 //mussaViewTB->addAction(toggleMotifsAction);
76 mussaViewTB->addWidget(zoom);
78 connect(zoom, SIGNAL(valueChanged(double)),
79 browser, SLOT(setZoom(double)));
81 // threshold range is set in updateAnalysis
83 //scene->setClipPlane(20);
84 // FIXME: for when we get the paths drawn at the appropriate depth
85 //connect(threshold, SIGNAL(thresholdChanged(int)),
86 // this, SLOT(setClipPlane(int)));
87 connect(threshold, SIGNAL(thresholdChanged(int)),
88 this, SLOT(setSoftThreshold(int)));
89 mussaViewTB->addWidget(threshold);
91 addToolBar(mussaViewTB);
93 statusBar()->showMessage("Welcome to mussa", 2000);
94 // FIXME: we should start refactoring the connect call to updateAnalysis or something
96 connect(analysis.get(), SIGNAL(progress(const std::string&, int, int)),
97 this, SLOT(updateProgress(const std::string&, int, int)));
103 void MussaWindow::setAnalysis(MussaRef new_analysis)
105 if (new_analysis != 0) {
106 // only switch mussas if we loaded without error
108 analysis.swap(new_analysis);
114 void MussaWindow::setupActions()
116 // we really don't want to run this more than once.
117 assert (closeAction == 0);
119 // the ever popular about box
120 aboutAction = new QAction(tr("&About"), this);
121 connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
122 aboutAction->setIcon(QIcon(":/icons/info.png"));
125 closeAction = new QAction(tr("&Close"), this);
126 closeAction->setStatusTip(tr("Close this window"));
127 connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
128 closeAction->setIcon(QIcon(":/icons/exit.png"));
130 createNewAnalysisAction = new QAction(tr("Create Analysis"), this);
131 connect(createNewAnalysisAction, SIGNAL(triggered()),
132 this, SLOT(createNewAnalysis()));
133 createNewAnalysisAction->setIcon(QIcon(":/icons/filenew.png"));
135 createSubAnalysisAction = new QAction(tr("Add to Subanalysis"), this);
136 connect(createSubAnalysisAction, SIGNAL(triggered()),
137 this, SLOT(createSubAnalysis()));
139 saveAnalysisAction = new QAction(tr("&Save Analysis"), this);
140 connect(saveAnalysisAction, SIGNAL(triggered()),
141 this, SLOT(saveAnalysis()));
142 saveAnalysisAction->setIcon(QIcon(":/icons/filesave.png"));
144 saveAnalysisAsAction = new QAction(tr("Save Analysis &As"), this);
145 connect(saveAnalysisAsAction, SIGNAL(triggered()),
146 this, SLOT(saveAnalysisAs()));
147 saveAnalysisAsAction->setIcon(QIcon(":/icons/filesave.png"));
149 editMotifsAction = new QAction(tr("Edit Motifs"), this);;
150 connect(editMotifsAction, SIGNAL(triggered()), this, SLOT(editMotifs()));
152 loadMotifListAction = new QAction(tr("Load Motif List"), this);
153 connect(loadMotifListAction, SIGNAL(triggered()),
154 this, SLOT(loadMotifList()));
155 loadMotifListAction->setIcon(QIcon(":/icons/fileopen.png"));
157 loadMupaAction = new QAction(tr("Create Analysis from File"), this);
158 connect(loadMupaAction, SIGNAL(triggered()),
159 this, SLOT(loadMupa()));
160 loadMupaAction->setIcon(QIcon(":/icons/fileopen.png"));
162 loadSavedAnalysisAction = new QAction(tr("Load Existing &Analysis"), this);
163 connect(loadSavedAnalysisAction, SIGNAL(triggered()),
164 this, SLOT(loadSavedAnalysis()));
165 loadSavedAnalysisAction->setIcon(QIcon(":/icons/fileopen.png"));
167 mussaManualAssistantAction = new QAction(tr("Mussagl Manual..."), this);
168 mussaManualAssistantAction->setIcon(QIcon(":/icons/contents.png"));
169 connect(mussaManualAssistantAction, SIGNAL(triggered()),
170 this, SLOT(showManual()));
172 newMussaWindowAction = new QAction(tr("&New Mussa Window"), this);
173 newMussaWindowAction->setStatusTip("open another mussa window to allow comparing results");
174 connect(newMussaWindowAction, SIGNAL(triggered()),
175 this, SLOT(newMussaWindow()));
176 newMussaWindowAction->setIcon(QIcon(":/icons/window_new.png"));
178 saveMotifListAction = new QAction(tr("Save Motifs"), this);
179 connect(saveMotifListAction, SIGNAL(triggered()),
180 this, SLOT(saveMotifList()));
181 saveMotifListAction->setIcon(QIcon(":/icons/filesave.png"));
183 showMussaViewToolbarAction = new QAction(tr("Show Toolbar"), this);
184 connect(showMussaViewToolbarAction, SIGNAL(triggered()),
185 this, SLOT(showMussaToolbar()));
186 showMussaViewToolbarAction->setCheckable(true);
187 showMussaViewToolbarAction->setChecked(true);
189 toggleMotifsAction = new QAction(tr("Toggle Motifs"), this);
190 connect(toggleMotifsAction, SIGNAL(triggered()),
191 this, SLOT(toggleMotifs()));
192 toggleMotifsAction->setCheckable(true);
193 toggleMotifsAction->setIcon(QIcon(":/icons/motif_icon.png"));
194 toggleMotifsAction->setWhatsThis(tr("Toggle motif annotations on/off\n\n"
195 "You can load motif annotations via "
196 "'File->Load Motif List' menu option."));
198 //Save pixel map action
199 saveBrowserPixmapAction = new QAction(tr("Save to image..."), this);
200 connect(saveBrowserPixmapAction, (SIGNAL(triggered())),
201 browser, SLOT(promptSaveBrowserPixmap()));
202 saveBrowserPixmapAction->setIcon(QIcon(":/icons/image2.png"));
204 viewMussaAlignmentAction = new QAction(tr("View sequence alignment"), this);
205 connect(viewMussaAlignmentAction, SIGNAL(triggered()),
206 this, SLOT(viewMussaAlignment() ));
207 viewMussaAlignmentAction->setWhatsThis(tr("Create a zoomed in window "
208 "showing alignment of the seqcomp "
211 whatsThisAction = QWhatsThis::createAction(this);
212 whatsThisAction->setIcon(QIcon(":/icons/help.png"));
217 void MussaWindow::closeEvent(QCloseEvent *event)
219 if(isClearingAnalysisSafe()) {
226 void MussaWindow::setupMainMenu()
228 // we need to run setupActions first
229 assert (closeAction != 0);
231 QMenu *newMenu = menuBar()->addMenu(tr("&File"));
233 newMenu->addAction(newMussaWindowAction);
234 newMenu->addAction(createNewAnalysisAction);
235 newMenu->addAction(loadMupaAction);
236 newMenu->addAction(loadSavedAnalysisAction);
237 newMenu->addAction(saveAnalysisAction);
238 newMenu->addAction(saveAnalysisAsAction);
239 newMenu->addSeparator();
240 newMenu->addAction(loadMotifListAction);
241 newMenu->addAction(saveMotifListAction);
242 newMenu->addSeparator();
243 newMenu->addAction(saveBrowserPixmapAction);
244 newMenu->addSeparator();
245 newMenu->addAction(closeAction);
247 newMenu = menuBar()->addMenu(tr("&Edit"));
248 newMenu->addAction(editMotifsAction);
249 newMenu->addAction(browser->getCopySelectedSequenceAsFastaAction());
250 newMenu->addAction(createSubAnalysisAction);
252 newMenu = menuBar()->addMenu(tr("&View"));
253 newMenu->addAction(viewMussaAlignmentAction);
254 newMenu->addAction(showMussaViewToolbarAction);
256 newMenu = menuBar()->addMenu(tr("&Help"));
257 newMenu->addAction(mussaManualAssistantAction);
258 newMenu->addAction(whatsThisAction);
259 newMenu->addSeparator();
260 newMenu->addAction(aboutAction);
262 // add some extra features to the context menu
263 QMenu *popupMenu = browser->getPopupMenu();
265 popupMenu->addAction(viewMussaAlignmentAction);
266 popupMenu->addAction(createSubAnalysisAction);
270 void MussaWindow::setupAssistant()
272 #if defined(QT_ASSISTANT_FOUND)
273 QStringList manualAssistantArgs;
274 manualAssistantArgs = QStringList();
275 manualAssistantArgs << "-profile" << "./doc/manual/mussagl_manual.adp";
276 manualAssistant = new QAssistantClient("assistant", this);
277 manualAssistant->setArguments(manualAssistantArgs);
278 connect(manualAssistant, SIGNAL(error(QString)),
279 this, SLOT(assistantError(QString)));
283 void MussaWindow::about()
285 QString msg("Welcome to Multiple Species Sequence Analysis\n"
286 "(c) 2005-2006 California Institute of Technology\n"
287 "Tristan De Buysscher, Diane Trout\n");
290 msg += (char *)glGetString(GL_VERSION);
292 QMessageBox::about(this, tr("About mussa"), msg);
295 void MussaWindow::clear()
297 aligned_windows.clear();
301 void MussaWindow::createNewAnalysis()
304 // ideally we should open a new window if there's an analysis
305 // but this should work for the moment.
306 if (not isClearingAnalysisSafe()) return;
308 if (setup_analysis_dialog->exec()) {
309 setAnalysis(setup_analysis_dialog->getMussa());
311 } catch(mussa_error e) {
312 QString msg(e.what());
313 QMessageBox::warning(this, tr("Create New Analysis"), msg);
317 void MussaWindow::createSubAnalysis()
319 list<SequenceLocation> result;
320 SequenceLocationModel& model = subanalysis_window->getModel();
321 browser->copySelectedTracksAsSeqLocation(result);
322 for(list<SequenceLocation>::iterator result_itor = result.begin();
323 result_itor != result.end();
326 model.push_back(*result_itor);
329 if (not subanalysis_window->isVisible()) {
330 subanalysis_window->show();
334 void MussaWindow::saveAnalysis()
336 // if we've got an analysis
337 if (analysis and not analysis->empty()) {
338 // if it doesn't have a name we need to pick one
339 if (analysis->get_analysis_path().empty()) {
342 // if we've got a name, has it changed any?
343 // this doesn't work when a sequence changes something (like its
345 //else if (analysis->is_dirty())
351 void MussaWindow::saveAnalysisAs()
353 std::auto_ptr<QFileDialog> dialog(new QFileDialog(this));
354 dialog->setAcceptMode(QFileDialog::AcceptSave);
355 dialog->setFileMode(QFileDialog::AnyFile);
356 dialog->setDirectory(QDir(default_dir.native_directory_string().c_str()));
358 QStringList fileNames;
359 if (not dialog->exec()) {
362 fileNames = dialog->selectedFiles();
364 if (fileNames.size() != 1) {
367 fs::path save_path(fileNames[0].toStdString(), fs::native);
368 // do you want to overwrite?
369 if (fs::exists(save_path) and
370 QMessageBox::question(
372 tr("Overwrite File? -- Mussa"),
373 tr("A file called %1 already exists"
374 "do you want to overwrite it?")
376 tr("&Yes"), tr("&No"),
381 analysis->save(save_path);
382 default_dir = (save_path / "..").normalize();
385 bool MussaWindow::isClearingAnalysisSafe()
387 if (analysis and not analysis->empty() and analysis->is_dirty()) {
388 switch (QMessageBox::question(
390 tr("Save Unsaved Changes -- Mussa"),
391 tr("There are unsaved changes,\ndo you want to save?"),
392 tr("&Yes"), tr("&No"), tr("&Cancel"),
407 throw runtime_error("isClearingAnalysis QMesageBox failure");
410 // if we're here we've been saved and can replace
414 void MussaWindow::editMotifs()
416 if (motif_editor != 0) {
417 motif_editor->hide();
420 motif_editor = new MotifEditor(analysis);
421 connect(motif_editor, SIGNAL(changedMotifs()),
422 this, SLOT(updateAnnotations()));
423 motif_editor->show();
426 void MussaWindow::loadMotifList()
428 QString caption("Load a motif list");
429 QString filter("Motif list(*.txt *.mtl)");
430 QDir default_qdir(default_dir.native_directory_string().c_str());
431 QString path = QFileDialog::getOpenFileName(this,
433 default_qdir.absolutePath(),
438 // try to load safely
440 fs::path converted_path(path.toStdString(), fs::native);
441 analysis->load_motifs(converted_path);
442 default_dir = converted_path.branch_path();
443 } catch (runtime_error e) {
444 QString msg("Unable to load ");
448 QMessageBox::warning(this, "Load Motifs", msg);
450 assert (analysis != 0);
453 void MussaWindow::saveMotifList()
458 void MussaWindow::loadMupa()
460 QString caption("Load a mussa parameter file");
461 QString filter("Mussa Parameters (*.mupa)");
462 QDir default_qdir(QDir(default_dir.native_directory_string().c_str()));
464 QString mupa_path = QFileDialog::getOpenFileName(
467 default_qdir.absolutePath(),
471 if (mupa_path.isNull())
473 // try to load safely
475 // ideally we should open a new window if there's an analysis
476 // but this should work for the moment.
477 if (not isClearingAnalysisSafe()) return;
479 MussaRef m(new Mussa);
480 fs::path converted_path(mupa_path.toStdString(), fs::native);
481 connect(m.get(), SIGNAL(progress(const std::string&, int, int)),
482 this, SLOT(updateProgress(const std::string&, int, int)));
483 m->load_mupa_file(converted_path);
487 // grab the path ignoring the mupa file portion
488 default_dir = converted_path.branch_path();
489 } catch (mussa_load_error e) {
490 QString msg("Unable to load ");
494 QMessageBox::warning(this, "Load Parameter", msg);
496 assert (analysis != 0);
499 void MussaWindow::loadSavedAnalysis()
501 QDir default_qdir(QDir(default_dir.native_directory_string().c_str()));
502 QString caption("Load a previously run analysis");
503 QString muway_dir = QFileDialog::getExistingDirectory(
506 default_qdir.absolutePath()
509 if (muway_dir.isNull())
511 // try to safely load
513 // ideally we should open a new window if there's an analysis
514 // but this should work for the moment.
515 if (not isClearingAnalysisSafe()) return;
517 MussaRef m(new Mussa);
518 fs::path converted_path(muway_dir.toStdString(), fs::native);
519 connect(m.get(), SIGNAL(progress(const std::string&, int, int)),
520 this, SLOT(updateProgress(const std::string&, int, int)));
521 m->load(converted_path);
522 // only switch mussas if we loaded without error
523 if (analysis->empty()) {
524 // our current window is empty so load and replace.
527 default_dir = converted_path.branch_path();
529 MussaWindow *win = new MussaWindow(m);
531 win->default_dir = converted_path.branch_path();
534 } catch (mussa_load_error e) {
535 QString msg("Unable to load ");
539 QMessageBox::warning(this, "Load Parameter", msg);
541 assert (analysis != 0);
544 void MussaWindow::newMussaWindow()
546 MussaRef a(new Mussa);
547 MussaWindow *win = new MussaWindow(a);
548 win->default_dir = default_dir;
552 void MussaWindow::setSoftThreshold(int threshold)
554 if (analysis->get_soft_threshold() != threshold) {
555 analysis->set_soft_threshold(threshold);
562 void MussaWindow::showMussaToolbar()
564 if (mussaViewTB->isVisible())
570 void MussaWindow::toggleMotifs()
575 void MussaWindow::showManual()
577 #if QT_QTASSISTANT_FOUND
578 manualAssistant->openAssistant();
581 boost::python::object webopen = get_py()["webbrowser.open"];
582 webopen("http://woldlab.caltech.edu/~king/mussagl_manual/");
583 } catch( boost::python::error_already_set ) {
586 #endif //QT_QTASSISTANT_FOUND
589 void MussaWindow::assistantError(QString message)
591 //std::cout << "QAssistantError: " << message.toStdString() << "\n";
592 QMessageBox::warning ( this, "Warning: Mussagl Manual", message,
594 QMessageBox::NoButton);
597 void MussaWindow::NotImplementedBox()
599 QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
602 void MussaWindow::viewMussaAlignment()
604 const set<int>& selected_paths = browser->selectedPaths();
605 if (selected_paths.size() == 0 ) {
606 QMessageBox::warning(this,
607 QObject::tr("mussa"),
608 QObject::tr("you should probably select some paths "
611 MussaAlignedWindowRef ma_win(
612 new MussaAlignedWindow(analysis, selected_paths, subanalysis_window)
615 aligned_windows.push_back(ma_win);
616 connect(this, SIGNAL(changedAnnotations()),
617 aligned_windows.back().get(), SLOT(update()));
618 aligned_windows.back()->show();
622 void MussaWindow::updateAnalysis()
624 const Mussa::vector_sequence_type& seqs = analysis->sequences();
625 browser->setSequences(seqs, analysis->colorMapper());
626 assert(browser->sequences().size() == analysis->size());
628 // setRange eventually emits something that causes updateLinks to be called
629 // but it's possible for us to not have had a chance to set out sequences
631 threshold->setRange(analysis->get_threshold(),analysis->get_window());
634 zoom->setValue(browser->zoom());
637 void MussaWindow::updateAnnotations()
639 // motifs were changed in the sequences by
640 // Mussa::update_sequences_motifs
641 emit changedAnnotations();
645 void MussaWindow::updateLinks()
647 if(browser->sequences().size() == 0) {
648 // we don't have any sequences load so we have no business setting links
652 browser->clear_links();
653 bool reversed = false;
654 const NwayPaths& nway = analysis->paths();
656 typedef list<ConservedPath> conserved_paths;
657 typedef conserved_paths::const_iterator const_conserved_paths_itor;
658 for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
659 path_itor != nway.refined_pathz.end();
662 // since we were drawing to the start of a window, and opengl lines
663 // are centered around the two connecting points our lines were slightly
664 // offset. the idea of window_offset is to adjust them to the
665 // right for forward compliment or left for reverse compliment
666 // FIXME: figure out how to unit test these computations
667 //GLfloat window_offset = (path_itor->window_size)/2.0;
669 size_t track_len = path_itor->track_indexes.size();
670 vector<int> normalized_path;
671 normalized_path.reserve(track_len);
672 vector<bool> rc_flags(false, track_len);
673 for (size_t track_i=0; track_i != track_len; ++track_i)
675 int x = path_itor->track_indexes[track_i];
676 // at some point when we modify the pathz data structure to keep
677 // track of the score we can put grab the depth here.
679 // are we reverse complimented?
687 normalized_path.push_back(x);
688 rc_flags.push_back(reversed);
690 browser->link(normalized_path, rc_flags, path_itor->window_size);
696 MussaWindow::updateProgress(const string& description, int current, int max)
699 if (current == max) {
700 if (progress_dialog != 0) {
701 progress_dialog->hide();
702 delete progress_dialog;
706 // if we're starting, create the dialog
707 if (progress_dialog == 0) {
708 QString desc(description.c_str());
709 QString cancel("Cancel");
710 progress_dialog = new QProgressDialog(desc, cancel, current, max, this);
711 progress_dialog->show();
713 // just update the dialog
714 progress_dialog->setValue(current);
717 qApp->processEvents();
720 void MussaWindow::updateTitle()
723 setWindowTitle(analysis->get_title().c_str());