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(Mussa *analysis_, QWidget *parent) :
35 default_dir(QDir::home().absolutePath().toStdString(), fs::native),
37 setup_analysis_dialog(new MussaSetupDialog(this)),
38 subanalysis_window(new SubanalysisWindow),
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 connect(analysis, SIGNAL(progress(const std::string&, int, int)),
95 this, SLOT(updateProgress(const std::string&, int, int)));
100 void MussaWindow::setAnalysis(Mussa *new_analysis)
102 if (new_analysis != 0) {
103 // only switch mussas if we loaded without error
106 analysis = new_analysis;
112 void MussaWindow::setupActions()
114 // we really don't want to run this more than once.
115 assert (closeAction == 0);
117 // the ever popular about box
118 aboutAction = new QAction(tr("&About"), this);
119 connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
120 aboutAction->setIcon(QIcon(":/icons/info.png"));
123 closeAction = new QAction(tr("&Close"), this);
124 closeAction->setStatusTip(tr("Close this window"));
125 connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
126 closeAction->setIcon(QIcon(":/icons/exit.png"));
128 createNewAnalysisAction = new QAction(tr("Create Analysis"), this);
129 connect(createNewAnalysisAction, SIGNAL(triggered()),
130 this, SLOT(createNewAnalysis()));
131 createNewAnalysisAction->setIcon(QIcon(":/icons/filenew.png"));
133 createSubAnalysisAction = new QAction(tr("Add to Subanalysis"), this);
134 connect(createSubAnalysisAction, SIGNAL(triggered()),
135 this, SLOT(createSubAnalysis()));
137 saveAnalysisAction = new QAction(tr("&Save Analysis"), this);
138 connect(saveAnalysisAction, SIGNAL(triggered()),
139 this, SLOT(saveAnalysis()));
140 saveAnalysisAction->setIcon(QIcon(":/icons/filesave.png"));
142 saveAnalysisAsAction = new QAction(tr("Save Analysis &As"), this);
143 connect(saveAnalysisAsAction, SIGNAL(triggered()),
144 this, SLOT(saveAnalysisAs()));
145 saveAnalysisAsAction->setIcon(QIcon(":/icons/filesave.png"));
147 editMotifsAction = new QAction(tr("Edit Motifs"), this);;
148 connect(editMotifsAction, SIGNAL(triggered()), this, SLOT(editMotifs()));
150 loadMotifListAction = new QAction(tr("Load Motif List"), this);
151 connect(loadMotifListAction, SIGNAL(triggered()),
152 this, SLOT(loadMotifList()));
153 loadMotifListAction->setIcon(QIcon(":/icons/fileopen.png"));
155 loadMupaAction = new QAction(tr("Create Analysis from File"), this);
156 connect(loadMupaAction, SIGNAL(triggered()),
157 this, SLOT(loadMupa()));
158 loadMupaAction->setIcon(QIcon(":/icons/fileopen.png"));
160 loadSavedAnalysisAction = new QAction(tr("Load Existing &Analysis"), this);
161 connect(loadSavedAnalysisAction, SIGNAL(triggered()),
162 this, SLOT(loadSavedAnalysis()));
163 loadSavedAnalysisAction->setIcon(QIcon(":/icons/fileopen.png"));
165 mussaManualAssistantAction = new QAction(tr("Mussagl Manual..."), this);
166 mussaManualAssistantAction->setIcon(QIcon(":/icons/contents.png"));
167 connect(mussaManualAssistantAction, SIGNAL(triggered()),
168 this, SLOT(showManual()));
170 newMussaWindowAction = new QAction(tr("&New Mussa Window"), this);
171 newMussaWindowAction->setStatusTip("open another mussa window to allow comparing results");
172 connect(newMussaWindowAction, SIGNAL(triggered()),
173 this, SLOT(newMussaWindow()));
174 newMussaWindowAction->setIcon(QIcon(":/icons/window_new.png"));
176 saveMotifListAction = new QAction(tr("Save Motifs"), this);
177 connect(saveMotifListAction, SIGNAL(triggered()),
178 this, SLOT(saveMotifList()));
179 saveMotifListAction->setIcon(QIcon(":/icons/filesave.png"));
181 showMussaViewToolbarAction = new QAction(tr("Show Toolbar"), this);
182 connect(showMussaViewToolbarAction, SIGNAL(triggered()),
183 this, SLOT(showMussaToolbar()));
184 showMussaViewToolbarAction->setCheckable(true);
185 showMussaViewToolbarAction->setChecked(true);
187 toggleMotifsAction = new QAction(tr("Toggle Motifs"), this);
188 connect(toggleMotifsAction, SIGNAL(triggered()),
189 this, SLOT(toggleMotifs()));
190 toggleMotifsAction->setCheckable(true);
191 toggleMotifsAction->setIcon(QIcon(":/icons/motif_icon.png"));
192 toggleMotifsAction->setWhatsThis(tr("Toggle motif annotations on/off\n\n"
193 "You can load motif annotations via "
194 "'File->Load Motif List' menu option."));
196 //Save pixel map action
197 saveBrowserPixmapAction = new QAction(tr("Save to image..."), this);
198 connect(saveBrowserPixmapAction, (SIGNAL(triggered())),
199 browser, SLOT(promptSaveBrowserPixmap()));
200 saveBrowserPixmapAction->setIcon(QIcon(":/icons/image2.png"));
202 viewMussaAlignmentAction = new QAction(tr("View sequence alignment"), this);
203 connect(viewMussaAlignmentAction, SIGNAL(triggered()),
204 this, SLOT(viewMussaAlignment() ));
205 viewMussaAlignmentAction->setWhatsThis(tr("Create a zoomed in window "
206 "showing alignment of the seqcomp "
209 whatsThisAction = QWhatsThis::createAction(this);
210 whatsThisAction->setIcon(QIcon(":/icons/help.png"));
215 void MussaWindow::closeEvent(QCloseEvent *event)
217 if(isClearingAnalysisSafe()) {
224 void MussaWindow::setupMainMenu()
226 // we need to run setupActions first
227 assert (closeAction != 0);
229 QMenu *newMenu = menuBar()->addMenu(tr("&File"));
231 newMenu->addAction(newMussaWindowAction);
232 newMenu->addAction(createNewAnalysisAction);
233 newMenu->addAction(loadMupaAction);
234 newMenu->addAction(loadSavedAnalysisAction);
235 newMenu->addAction(saveAnalysisAction);
236 newMenu->addAction(saveAnalysisAsAction);
237 newMenu->addSeparator();
238 newMenu->addAction(loadMotifListAction);
239 newMenu->addAction(saveMotifListAction);
240 newMenu->addSeparator();
241 newMenu->addAction(saveBrowserPixmapAction);
242 newMenu->addSeparator();
243 newMenu->addAction(closeAction);
245 newMenu = menuBar()->addMenu(tr("&Edit"));
246 newMenu->addAction(editMotifsAction);
247 newMenu->addAction(browser->getCopySelectedSequenceAsFastaAction());
248 newMenu->addAction(createSubAnalysisAction);
250 newMenu = menuBar()->addMenu(tr("&View"));
251 newMenu->addAction(viewMussaAlignmentAction);
252 newMenu->addAction(showMussaViewToolbarAction);
254 newMenu = menuBar()->addMenu(tr("&Help"));
255 newMenu->addAction(mussaManualAssistantAction);
256 newMenu->addAction(whatsThisAction);
257 newMenu->addSeparator();
258 newMenu->addAction(aboutAction);
260 // add some extra features to the context menu
261 QMenu *popupMenu = browser->getPopupMenu();
263 popupMenu->addAction(viewMussaAlignmentAction);
264 popupMenu->addAction(createSubAnalysisAction);
268 void MussaWindow::setupAssistant()
270 #if defined(QT_ASSISTANT_FOUND)
271 QStringList manualAssistantArgs;
272 manualAssistantArgs = QStringList();
273 manualAssistantArgs << "-profile" << "./doc/manual/mussagl_manual.adp";
274 manualAssistant = new QAssistantClient("assistant", this);
275 manualAssistant->setArguments(manualAssistantArgs);
276 connect(manualAssistant, SIGNAL(error(QString)),
277 this, SLOT(assistantError(QString)));
281 void MussaWindow::about()
283 QString msg("Welcome to Multiple Species Sequence Analysis\n"
284 "(c) 2005-2006 California Institute of Technology\n"
285 "Tristan De Buysscher, Diane Trout\n");
288 msg += (char *)glGetString(GL_VERSION);
290 QMessageBox::about(this, tr("About mussa"), msg);
293 void MussaWindow::clear()
295 aligned_windows.clear();
299 void MussaWindow::createNewAnalysis()
302 // ideally we should open a new window if there's an analysis
303 // but this should work for the moment.
304 if (not isClearingAnalysisSafe()) return;
306 if (setup_analysis_dialog->exec()) {
308 m = 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 Mussa *m = new Mussa;
480 fs::path converted_path(mupa_path.toStdString(), fs::native);
481 connect(m, 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 Mussa *m = new Mussa;
518 fs::path converted_path(muway_dir.toStdString(), fs::native);
519 connect(m, 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 Mussa *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 boost::shared_ptr<MussaAlignedWindow> ma_win(
612 new MussaAlignedWindow(*analysis, selected_paths)
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 fs::path analysis_path = analysis->get_analysis_path();
724 if (not analysis_path.empty()) {
725 setWindowTitle(analysis_path.native_file_string().c_str());
726 } else if (analysis->get_name().size() > 0) {
727 setWindowTitle(analysis->get_name().c_str());