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 #include <boost/filesystem/convenience.hpp>
28 namespace fs = boost::filesystem;
29 #include <boost/bind.hpp>
33 MussaWindow::MussaWindow(MussaRef analysis_, QWidget *parent) :
36 default_dir(new QDir(QDir::home().absolutePath())),
38 setup_analysis_dialog(new MussaSetupDialog(this)),
39 subanalysis_window(new SubanalysisWindow(analysis)),
40 browser(new SequenceBrowserWidget(default_dir, this)),
41 mussaViewTB(new QToolBar("Path Views")),
43 threshold(new ThresholdWidget),
47 createNewAnalysisAction(0),
48 createSubAnalysisAction(0),
50 loadMotifListAction(0),
52 loadSavedAnalysisAction(0),
53 mussaManualAssistantAction(0),
54 newMussaWindowAction(0),
55 saveMotifListAction(0),
56 showMussaViewToolbarAction(0),
57 toggleMotifsAction(0),
58 saveBrowserPixmapAction(0),
60 viewMussaAlignmentAction(0),
67 //This next setWhatsThis function prevents
68 // a segfault when using WhatsThis feature with
70 //scene->setWhatsThis(tr("Mussa in OpenGL!"));
71 setCentralWidget(browser);
72 // well updatePosition isn't quite right as we really just need
74 connect(this, SIGNAL(changedAnnotations()), browser, SLOT(update()));
75 connect(this, SIGNAL(changedMotifs()), this, SLOT(updateAnnotations()));
77 //mussaViewTB->addAction(toggleMotifsAction);
78 mussaViewTB->addWidget(zoom);
80 connect(zoom, SIGNAL(valueChanged(double)),
81 browser, SLOT(setZoom(double)));
83 // threshold range is set in updateAnalysis
85 //scene->setClipPlane(20);
86 // FIXME: for when we get the paths drawn at the appropriate depth
87 //connect(threshold, SIGNAL(thresholdChanged(int)),
88 // this, SLOT(setClipPlane(int)));
89 connect(threshold, SIGNAL(thresholdChanged(int)),
90 this, SLOT(setSoftThreshold(int)));
91 mussaViewTB->addWidget(threshold);
93 addToolBar(mussaViewTB);
95 statusBar()->showMessage("Welcome to mussa", 2000);
96 // FIXME: we should start refactoring the connect call to updateAnalysis or something
98 connect(analysis.get(), SIGNAL(progress(const std::string&, int, int)),
99 this, SLOT(updateProgress(const std::string&, int, int)));
105 void MussaWindow::setAnalysis(MussaRef new_analysis)
107 if (new_analysis != 0) {
108 // only switch mussas if we loaded without error
110 analysis.swap(new_analysis);
116 void MussaWindow::setupActions()
118 // we really don't want to run this more than once.
119 assert (closeAction == 0);
121 // the ever popular about box
122 aboutAction = new QAction(tr("&About"), this);
123 connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
124 aboutAction->setIcon(QIcon(":/icons/info.png"));
127 closeAction = new QAction(tr("&Close"), this);
128 closeAction->setStatusTip(tr("Close this window"));
129 connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
130 closeAction->setIcon(QIcon(":/icons/exit.png"));
132 createNewAnalysisAction = new QAction(tr("Create Analysis"), this);
133 connect(createNewAnalysisAction, SIGNAL(triggered()),
134 this, SLOT(createNewAnalysis()));
135 createNewAnalysisAction->setIcon(QIcon(":/icons/filenew.png"));
137 createSubAnalysisAction = new QAction(tr("Add to Subanalysis"), this);
138 connect(createSubAnalysisAction, SIGNAL(triggered()),
139 this, SLOT(createSubAnalysis()));
141 saveAnalysisAction = new QAction(tr("&Save Analysis"), this);
142 connect(saveAnalysisAction, SIGNAL(triggered()),
143 this, SLOT(saveAnalysis()));
144 saveAnalysisAction->setIcon(QIcon(":/icons/filesave.png"));
146 saveAnalysisAsAction = new QAction(tr("Save Analysis &As"), this);
147 connect(saveAnalysisAsAction, SIGNAL(triggered()),
148 this, SLOT(saveAnalysisAs()));
149 saveAnalysisAsAction->setIcon(QIcon(":/icons/filesave.png"));
151 editMotifsAction = new QAction(tr("Edit Motifs"), this);;
152 connect(editMotifsAction, SIGNAL(triggered()), this, SLOT(editMotifs()));
154 loadMotifListAction = new QAction(tr("Load Motif List"), this);
155 connect(loadMotifListAction, SIGNAL(triggered()),
156 this, SLOT(loadMotifList()));
157 loadMotifListAction->setIcon(QIcon(":/icons/fileopen.png"));
159 loadMupaAction = new QAction(tr("Create Analysis from File"), this);
160 connect(loadMupaAction, SIGNAL(triggered()),
161 this, SLOT(loadMupa()));
162 loadMupaAction->setIcon(QIcon(":/icons/fileopen.png"));
164 loadSavedAnalysisAction = new QAction(tr("Load Existing &Analysis"), this);
165 connect(loadSavedAnalysisAction, SIGNAL(triggered()),
166 this, SLOT(loadSavedAnalysis()));
167 loadSavedAnalysisAction->setIcon(QIcon(":/icons/fileopen.png"));
169 mussaManualAssistantAction = new QAction(tr("Mussagl Manual..."), this);
170 mussaManualAssistantAction->setIcon(QIcon(":/icons/contents.png"));
171 connect(mussaManualAssistantAction, SIGNAL(triggered()),
172 this, SLOT(showManual()));
174 newMussaWindowAction = new QAction(tr("&New Mussa Window"), this);
175 newMussaWindowAction->setStatusTip("open another mussa window to allow comparing results");
176 connect(newMussaWindowAction, SIGNAL(triggered()),
177 this, SLOT(newMussaWindow()));
178 newMussaWindowAction->setIcon(QIcon(":/icons/window_new.png"));
180 saveMotifListAction = new QAction(tr("Save Motifs"), this);
181 connect(saveMotifListAction, SIGNAL(triggered()),
182 this, SLOT(saveMotifList()));
183 saveMotifListAction->setIcon(QIcon(":/icons/filesave.png"));
185 showMussaViewToolbarAction = new QAction(tr("Show Toolbar"), this);
186 connect(showMussaViewToolbarAction, SIGNAL(triggered()),
187 this, SLOT(showMussaToolbar()));
188 showMussaViewToolbarAction->setCheckable(true);
189 showMussaViewToolbarAction->setChecked(true);
191 toggleMotifsAction = new QAction(tr("Toggle Motifs"), this);
192 connect(toggleMotifsAction, SIGNAL(triggered()),
193 this, SLOT(toggleMotifs()));
194 toggleMotifsAction->setCheckable(true);
195 toggleMotifsAction->setIcon(QIcon(":/icons/motif_icon.png"));
196 toggleMotifsAction->setWhatsThis(tr("Toggle motif annotations on/off\n\n"
197 "You can load motif annotations via "
198 "'File->Load Motif List' menu option."));
200 //Save pixel map action
201 saveBrowserPixmapAction = new QAction(tr("Save to image..."), this);
202 connect(saveBrowserPixmapAction, (SIGNAL(triggered())),
203 browser, SLOT(promptSaveBrowserPixmap()));
204 saveBrowserPixmapAction->setIcon(QIcon(":/icons/image2.png"));
206 viewMussaAlignmentAction = new QAction(tr("View sequence alignment"), this);
207 connect(viewMussaAlignmentAction, SIGNAL(triggered()),
208 this, SLOT(viewMussaAlignment() ));
209 viewMussaAlignmentAction->setWhatsThis(tr("Create a zoomed in window "
210 "showing alignment of the seqcomp "
213 whatsThisAction = QWhatsThis::createAction(this);
214 whatsThisAction->setIcon(QIcon(":/icons/help.png"));
219 void MussaWindow::closeEvent(QCloseEvent *event)
221 if(isClearingAnalysisSafe()) {
228 void MussaWindow::setupMainMenu()
230 // we need to run setupActions first
231 assert (closeAction != 0);
233 QMenu *newMenu = menuBar()->addMenu(tr("&File"));
235 newMenu->addAction(newMussaWindowAction);
236 newMenu->addAction(createNewAnalysisAction);
237 newMenu->addAction(loadMupaAction);
238 newMenu->addAction(loadSavedAnalysisAction);
239 newMenu->addAction(saveAnalysisAction);
240 newMenu->addAction(saveAnalysisAsAction);
241 newMenu->addSeparator();
242 newMenu->addAction(loadMotifListAction);
243 newMenu->addAction(saveMotifListAction);
244 newMenu->addSeparator();
245 newMenu->addAction(saveBrowserPixmapAction);
246 newMenu->addSeparator();
247 newMenu->addAction(closeAction);
249 newMenu = menuBar()->addMenu(tr("&Edit"));
250 newMenu->addAction(editMotifsAction);
251 newMenu->addAction(browser->getCopySelectedSequenceAsFastaAction());
252 newMenu->addAction(createSubAnalysisAction);
254 newMenu = menuBar()->addMenu(tr("&View"));
255 newMenu->addAction(viewMussaAlignmentAction);
256 newMenu->addAction(showMussaViewToolbarAction);
258 newMenu = menuBar()->addMenu(tr("&Help"));
259 newMenu->addAction(mussaManualAssistantAction);
260 newMenu->addAction(whatsThisAction);
261 newMenu->addSeparator();
262 newMenu->addAction(aboutAction);
264 // add some extra features to the context menu
265 QMenu *popupMenu = browser->getPopupMenu();
267 popupMenu->addAction(viewMussaAlignmentAction);
268 popupMenu->addAction(createSubAnalysisAction);
272 void MussaWindow::setupAssistant()
274 #if defined(QT_QTASSISTANT_FOUND)
275 QStringList manualAssistantArgs;
276 manualAssistantArgs = QStringList();
277 manualAssistantArgs << "-profile" << "./doc/manual/mussagl_manual.adp";
278 manualAssistant = new QAssistantClient("assistant", this);
279 manualAssistant->setArguments(manualAssistantArgs);
280 connect(manualAssistant, SIGNAL(error(QString)),
281 this, SLOT(assistantError(QString)));
285 void MussaWindow::about()
287 QString msg("Welcome to Multiple Species Sequence Analysis\n"
288 "(c) 2005-2006 California Institute of Technology\n"
289 "Tristan De Buysscher, Diane Trout\n");
292 msg += (char *)glGetString(GL_VERSION);
294 QMessageBox::about(this, tr("About mussa"), msg);
297 void MussaWindow::clear()
299 aligned_windows.clear();
303 void MussaWindow::createNewAnalysis()
306 // ideally we should open a new window if there's an analysis
307 // but this should work for the moment.
308 if (not isClearingAnalysisSafe()) return;
310 if (setup_analysis_dialog->exec()) {
311 setAnalysis(setup_analysis_dialog->getMussa());
313 } catch(mussa_error e) {
314 QString msg(e.what());
315 QMessageBox::warning(this, tr("Create New Analysis"), msg);
319 void MussaWindow::createSubAnalysis()
321 list<SequenceLocation> result;
322 SequenceLocationModel& model = subanalysis_window->getModel();
323 browser->copySelectedTracksAsSeqLocation(result);
324 for(list<SequenceLocation>::iterator result_itor = result.begin();
325 result_itor != result.end();
328 model.push_back(*result_itor);
331 if (not subanalysis_window->isVisible()) {
332 subanalysis_window->show();
336 void MussaWindow::saveAnalysis()
338 // if we've got an analysis
339 if (analysis and not analysis->empty()) {
340 // if it doesn't have a name we need to pick one
341 if (analysis->get_analysis_path().empty()) {
344 // if we've got a name, has it changed any?
345 // this doesn't work when a sequence changes something (like its
347 //else if (analysis->is_dirty())
353 void MussaWindow::saveAnalysisAs()
355 std::auto_ptr<QFileDialog> dialog(new QFileDialog(this));
356 dialog->setAcceptMode(QFileDialog::AcceptSave);
357 dialog->setFileMode(QFileDialog::AnyFile);
358 dialog->setDirectory(*default_dir);
360 QStringList fileNames;
361 if (not dialog->exec()) {
364 fileNames = dialog->selectedFiles();
366 if (fileNames.size() != 1) {
369 fs::path save_path(fileNames[0].toStdString(), fs::native);
370 // do you want to overwrite?
371 if (fs::exists(save_path) and
372 QMessageBox::question(
374 tr("Overwrite File? -- Mussa"),
375 tr("A file called %1 already exists"
376 "do you want to overwrite it?")
378 tr("&Yes"), tr("&No"),
383 analysis->save(save_path);
384 fs::path normalized_path = (save_path / "..").normalize();
385 default_dir->setPath(normalized_path.native_directory_string().c_str());
388 bool MussaWindow::isClearingAnalysisSafe()
390 if (analysis and not analysis->empty() and analysis->is_dirty()) {
391 switch (QMessageBox::question(
393 tr("Save Unsaved Changes -- Mussa"),
394 tr("There are unsaved changes,\ndo you want to save?"),
395 tr("&Yes"), tr("&No"), tr("&Cancel"),
410 throw runtime_error("isClearingAnalysis QMesageBox failure");
413 // if we're here we've been saved and can replace
417 void MussaWindow::editMotifs()
419 if (motif_editor != 0) {
420 motif_editor->hide();
423 motif_editor = new MotifEditor(analysis);
424 connect(motif_editor, SIGNAL(changedMotifs()),
425 this, SLOT(updateAnnotations()));
426 motif_editor->show();
429 void MussaWindow::loadMotifList()
431 QString caption("Mussa Load Motifs");
432 QString filter("Motif list(*.txt *.mtl)");
433 QString path = QFileDialog::getOpenFileName(this,
435 default_dir->absolutePath(),
440 // try to load safely
442 fs::path converted_path(path.toStdString(), fs::native);
443 analysis->load_motifs(converted_path);
444 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
445 emit changedMotifs();
446 } catch (runtime_error e) {
447 QString msg("Unable to load ");
451 QMessageBox::warning(this, caption, msg);
455 void MussaWindow::saveMotifList()
457 QString caption("Mussa Save Motifs");
458 QString filter("Motif list(*.txt *.mtl)");
459 QString path = QFileDialog::getSaveFileName(this,
461 default_dir->absolutePath(),
466 // try to load safely
468 fs::path converted_path(path.toStdString(), fs::native);
469 if (fs::extension(converted_path).size() == 0) {
470 // no extension, so add one
471 converted_path = converted_path.string() + ".mtl";
473 analysis->save_motifs(converted_path);
474 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
475 } catch (runtime_error e) {
476 QString msg("Unable to save ");
480 QMessageBox::warning(this, caption, msg);
483 void MussaWindow::loadMupa()
485 QString caption("Load a mussa parameter file");
486 QString filter("Mussa Parameters (*.mupa)");
487 QString mupa_path = QFileDialog::getOpenFileName(
490 default_dir->absolutePath(),
494 if (mupa_path.isNull())
496 // try to load safely
498 // ideally we should open a new window if there's an analysis
499 // but this should work for the moment.
500 if (not isClearingAnalysisSafe()) return;
502 MussaRef m(new Mussa);
503 fs::path converted_path(mupa_path.toStdString(), fs::native);
504 connect(m.get(), SIGNAL(progress(const std::string&, int, int)),
505 this, SLOT(updateProgress(const std::string&, int, int)));
506 m->load_mupa_file(converted_path);
510 // grab the path ignoring the mupa file portion
511 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
512 } catch (mussa_load_error e) {
513 QString msg("Unable to load ");
517 QMessageBox::warning(this, "Load Parameter", msg);
519 assert (analysis != 0);
522 void MussaWindow::loadSavedAnalysis()
524 QString caption("Load a previously run analysis");
525 QString muway_dir = QFileDialog::getExistingDirectory(
528 default_dir->absolutePath()
531 if (muway_dir.isNull())
533 // try to safely load
535 // ideally we should open a new window if there's an analysis
536 // but this should work for the moment.
537 if (not isClearingAnalysisSafe()) return;
539 MussaRef m(new Mussa);
540 fs::path converted_path(muway_dir.toStdString(), fs::native);
541 connect(m.get(), SIGNAL(progress(const std::string&, int, int)),
542 this, SLOT(updateProgress(const std::string&, int, int)));
543 m->load(converted_path);
544 // only switch mussas if we loaded without error
545 if (analysis->empty()) {
546 // our current window is empty so load and replace.
549 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
551 MussaWindow *win = new MussaWindow(m);
553 win->default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
556 } catch (mussa_load_error e) {
557 QString msg("Unable to load ");
561 QMessageBox::warning(this, "Load Parameter", msg);
563 assert (analysis != 0);
566 void MussaWindow::newMussaWindow()
568 MussaRef a(new Mussa);
569 MussaWindow *win = new MussaWindow(a);
570 win->default_dir = default_dir;
574 void MussaWindow::setSoftThreshold(int threshold)
576 if (analysis->get_soft_threshold() != threshold) {
577 analysis->set_soft_threshold(threshold);
584 void MussaWindow::showMussaToolbar()
586 if (mussaViewTB->isVisible())
592 void MussaWindow::toggleMotifs()
597 void MussaWindow::showManual()
599 #if defined(QT_QTASSISTANT_FOUND)
600 if (manualAssistant) {
601 manualAssistant->openAssistant();
603 QMessageBox::warning(this,
604 tr("Mussa Help Error"),
605 tr("QtAssistant not setup correctly"),
607 QMessageBox::NoButton,
608 QMessageBox::NoButton);
612 boost::python::object webopen = get_py()["webbrowser.open"];
613 webopen("http://woldlab.caltech.edu/~king/mussagl_manual/");
614 } catch( boost::python::error_already_set ) {
616 QMessageBox::warning(this,
617 tr("Mussa Help Error"),
618 tr("Unable to launch webbrowser"),
620 QMessageBox::NoButton,
621 QMessageBox::NoButton);
623 #endif //QT_QTASSISTANT_FOUND
626 void MussaWindow::assistantError(QString message)
628 //std::cout << "QAssistantError: " << message.toStdString() << "\n";
629 QMessageBox::warning ( this, "Warning: Mussagl Manual", message,
631 QMessageBox::NoButton);
634 void MussaWindow::NotImplementedBox()
636 QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
639 void MussaWindow::viewMussaAlignment()
641 const set<int>& selected_paths = browser->selectedPaths();
642 if (selected_paths.size() == 0 ) {
643 QMessageBox::warning(this,
644 QObject::tr("mussa"),
645 QObject::tr("you should probably select some paths "
648 MussaAlignedWindowRef ma_win(
649 new MussaAlignedWindow(analysis, default_dir, selected_paths, subanalysis_window)
652 aligned_windows.push_back(ma_win);
653 connect(this, SIGNAL(changedAnnotations()),
654 aligned_windows.back().get(), SLOT(update()));
655 aligned_windows.back()->show();
659 void MussaWindow::updateAnalysis()
661 const Mussa::vector_sequence_type& seqs = analysis->sequences();
662 browser->setSequences(seqs, analysis->colorMapper());
663 assert(browser->sequences().size() == analysis->size());
665 // setRange eventually emits something that causes updateLinks to be called
666 // but it's possible for us to not have had a chance to set out sequences
668 threshold->setRange(analysis->get_threshold(),analysis->get_window());
671 zoom->setValue(browser->zoom());
674 void MussaWindow::updateAnnotations()
676 // motifs were changed in the sequences by
677 // Mussa::update_sequences_motifs
678 emit changedAnnotations();
682 void MussaWindow::updateLinks()
684 if(browser->sequences().size() == 0) {
685 // we don't have any sequences load so we have no business setting links
689 browser->clear_links();
690 bool reversed = false;
691 const NwayPaths& nway = analysis->paths();
693 typedef list<ConservedPath> conserved_paths;
694 typedef conserved_paths::const_iterator const_conserved_paths_itor;
695 for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
696 path_itor != nway.refined_pathz.end();
699 // since we were drawing to the start of a window, and opengl lines
700 // are centered around the two connecting points our lines were slightly
701 // offset. the idea of window_offset is to adjust them to the
702 // right for forward compliment or left for reverse compliment
703 // FIXME: figure out how to unit test these computations
704 //GLfloat window_offset = (path_itor->window_size)/2.0;
706 size_t track_len = path_itor->track_indexes.size();
707 vector<int> normalized_path;
708 normalized_path.reserve(track_len);
709 vector<bool> rc_flags(false, track_len);
710 for (size_t track_i=0; track_i != track_len; ++track_i)
712 int x = path_itor->track_indexes[track_i];
713 // at some point when we modify the pathz data structure to keep
714 // track of the score we can put grab the depth here.
716 // are we reverse complimented?
724 normalized_path.push_back(x);
725 rc_flags.push_back(reversed);
727 browser->link(normalized_path, rc_flags, path_itor->window_size);
733 MussaWindow::updateProgress(const string& description, int current, int max)
736 if (current == max) {
737 if (progress_dialog != 0) {
738 progress_dialog->hide();
739 delete progress_dialog;
743 // if we're starting, create the dialog
744 if (progress_dialog == 0) {
745 QString desc(description.c_str());
746 QString cancel("Cancel");
747 progress_dialog = new QProgressDialog(desc, cancel, current, max, this);
748 progress_dialog->show();
750 // just update the dialog
751 progress_dialog->setValue(current);
754 qApp->processEvents();
757 void MussaWindow::updateTitle()
760 setWindowTitle(analysis->get_title().c_str());