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(this),
39 mussaViewTB("Path Views"),
45 createNewAnalysisAction(0),
46 createSubAnalysisAction(0),
48 loadMotifListAction(0),
50 loadSavedAnalysisAction(0),
51 mussaManualAssistantAction(0),
52 newMussaWindowAction(0),
53 saveMotifListAction(0),
54 showMussaViewToolbarAction(0),
55 toggleMotifsAction(0),
56 saveBrowserPixmapAction(0),
58 viewMussaAlignmentAction(0),
65 //This next setWhatsThis function prevents
66 // a segfault when using WhatsThis feature with
68 //scene->setWhatsThis(tr("Mussa in OpenGL!"));
69 setCentralWidget(&browser);
70 // well updatePosition isn't quite right as we really just need
72 connect(this, SIGNAL(changedAnnotations()), &browser, SLOT(update()));
74 //mussaViewTB.addAction(toggleMotifsAction);
75 mussaViewTB.addWidget(&zoom);
77 connect(&zoom, SIGNAL(valueChanged(double)),
78 &browser, SLOT(setZoom(double)));
80 // threshold range is set in updateAnalysis
82 //scene->setClipPlane(20);
83 // FIXME: for when we get the paths drawn at the appropriate depth
84 //connect(&threshold, SIGNAL(thresholdChanged(int)),
85 // this, SLOT(setClipPlane(int)));
86 connect(&threshold, SIGNAL(thresholdChanged(int)),
87 this, SLOT(setSoftThreshold(int)));
88 mussaViewTB.addWidget(&threshold);
90 addToolBar(&mussaViewTB);
92 statusBar()->showMessage("Welcome to mussa", 2000);
93 connect(analysis, SIGNAL(progress(const std::string&, int, int)),
94 this, SLOT(updateProgress(const std::string&, int, int)));
98 void MussaWindow::setAnalysis(Mussa *new_analysis)
100 if (new_analysis != 0) {
101 // only switch mussas if we loaded without error
104 analysis = new_analysis;
105 setWindowTitle(analysis->get_name().c_str());
110 void MussaWindow::setupActions()
112 // we really don't want to run this more than once.
113 assert (closeAction == 0);
115 // the ever popular about box
116 aboutAction = new QAction(tr("&About"), this);
117 connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
118 aboutAction->setIcon(QIcon(":/icons/info.png"));
121 closeAction = new QAction(tr("&Close"), this);
122 closeAction->setStatusTip(tr("Close this window"));
123 connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
124 closeAction->setIcon(QIcon(":/icons/exit.png"));
126 createNewAnalysisAction = new QAction(tr("Create Analysis"), this);
127 connect(createNewAnalysisAction, SIGNAL(triggered()),
128 this, SLOT(createNewAnalysis()));
129 createNewAnalysisAction->setIcon(QIcon(":/icons/filenew.png"));
131 createSubAnalysisAction = new QAction(tr("Add to Subanalysis"), this);
132 connect(createSubAnalysisAction, SIGNAL(triggered()),
133 this, SLOT(createSubAnalysis()));
135 saveAnalysisAction = new QAction(tr("&Save Analysis"), this);
136 connect(saveAnalysisAction, SIGNAL(triggered()),
137 this, SLOT(saveAnalysis()));
138 saveAnalysisAction->setIcon(QIcon(":/icons/filesave.png"));
140 saveAnalysisAsAction = new QAction(tr("Save Analysis &As"), this);
141 connect(saveAnalysisAsAction, SIGNAL(triggered()),
142 this, SLOT(saveAnalysisAs()));
143 saveAnalysisAsAction->setIcon(QIcon(":/icons/filesave.png"));
145 editMotifsAction = new QAction(tr("Edit Motifs"), this);;
146 connect(editMotifsAction, SIGNAL(triggered()), this, SLOT(editMotifs()));
148 loadMotifListAction = new QAction(tr("Load Motif List"), this);
149 connect(loadMotifListAction, SIGNAL(triggered()),
150 this, SLOT(loadMotifList()));
151 loadMotifListAction->setIcon(QIcon(":/icons/fileopen.png"));
153 loadMupaAction = new QAction(tr("Create Analysis from File"), this);
154 connect(loadMupaAction, SIGNAL(triggered()),
155 this, SLOT(loadMupa()));
156 loadMupaAction->setIcon(QIcon(":/icons/fileopen.png"));
158 loadSavedAnalysisAction = new QAction(tr("Load Existing &Analysis"), this);
159 connect(loadSavedAnalysisAction, SIGNAL(triggered()),
160 this, SLOT(loadSavedAnalysis()));
161 loadSavedAnalysisAction->setIcon(QIcon(":/icons/fileopen.png"));
163 mussaManualAssistantAction = new QAction(tr("Mussagl Manual..."), this);
164 mussaManualAssistantAction->setIcon(QIcon(":/icons/contents.png"));
165 connect(mussaManualAssistantAction, SIGNAL(triggered()),
166 this, SLOT(showManual()));
168 newMussaWindowAction = new QAction(tr("&New Mussa Window"), this);
169 newMussaWindowAction->setStatusTip("open another mussa window to allow comparing results");
170 connect(newMussaWindowAction, SIGNAL(triggered()),
171 this, SLOT(newMussaWindow()));
172 newMussaWindowAction->setIcon(QIcon(":/icons/window_new.png"));
174 saveMotifListAction = new QAction(tr("Save Motifs"), this);
175 connect(saveMotifListAction, SIGNAL(triggered()),
176 this, SLOT(saveMotifList()));
177 saveMotifListAction->setIcon(QIcon(":/icons/filesave.png"));
179 showMussaViewToolbarAction = new QAction(tr("Show Toolbar"), this);
180 connect(showMussaViewToolbarAction, SIGNAL(triggered()),
181 this, SLOT(showMussaToolbar()));
182 showMussaViewToolbarAction->setCheckable(true);
183 showMussaViewToolbarAction->setChecked(true);
185 toggleMotifsAction = new QAction(tr("Toggle Motifs"), this);
186 connect(toggleMotifsAction, SIGNAL(triggered()),
187 this, SLOT(toggleMotifs()));
188 toggleMotifsAction->setCheckable(true);
189 toggleMotifsAction->setIcon(QIcon(":/icons/motif_icon.png"));
190 toggleMotifsAction->setWhatsThis(tr("Toggle motif annotations on/off\n\n"
191 "You can load motif annotations via "
192 "'File->Load Motif List' menu option."));
194 //Save pixel map action
195 saveBrowserPixmapAction = new QAction(tr("Save to image..."), this);
196 connect(saveBrowserPixmapAction, (SIGNAL(triggered())),
197 &browser, SLOT(promptSaveBrowserPixmap()));
198 saveBrowserPixmapAction->setIcon(QIcon(":/icons/image2.png"));
200 viewMussaAlignmentAction = new QAction(tr("View sequence alignment"), this);
201 connect(viewMussaAlignmentAction, SIGNAL(triggered()),
202 this, SLOT(viewMussaAlignment() ));
203 viewMussaAlignmentAction->setWhatsThis(tr("Create a zoomed in window "
204 "showing alignment of the seqcomp "
207 whatsThisAction = QWhatsThis::createAction(this);
208 whatsThisAction->setIcon(QIcon(":/icons/help.png"));
213 void MussaWindow::closeEvent(QCloseEvent *event)
215 if(isClearingAnalysisSafe()) {
222 void MussaWindow::setupMainMenu()
224 // we need to run setupActions first
225 assert (closeAction != 0);
227 QMenu *newMenu = menuBar()->addMenu(tr("&File"));
229 newMenu->addAction(newMussaWindowAction);
230 newMenu->addAction(createNewAnalysisAction);
231 newMenu->addAction(loadMupaAction);
232 newMenu->addAction(loadSavedAnalysisAction);
233 newMenu->addAction(saveAnalysisAction);
234 newMenu->addAction(saveAnalysisAsAction);
235 newMenu->addSeparator();
236 newMenu->addAction(loadMotifListAction);
237 newMenu->addAction(saveMotifListAction);
238 newMenu->addSeparator();
239 newMenu->addAction(saveBrowserPixmapAction);
240 newMenu->addSeparator();
241 newMenu->addAction(closeAction);
243 newMenu = menuBar()->addMenu(tr("&Edit"));
244 newMenu->addAction(editMotifsAction);
245 newMenu->addAction(&browser.getCopySelectedSequenceAsFastaAction());
246 newMenu->addAction(createSubAnalysisAction);
248 newMenu = menuBar()->addMenu(tr("&View"));
249 newMenu->addAction(viewMussaAlignmentAction);
250 newMenu->addAction(showMussaViewToolbarAction);
252 newMenu = menuBar()->addMenu(tr("&Help"));
253 newMenu->addAction(mussaManualAssistantAction);
254 newMenu->addAction(whatsThisAction);
255 newMenu->addSeparator();
256 newMenu->addAction(aboutAction);
258 // add some extra features to the context menu
259 QMenu& popupMenu = browser.getPopupMenu();
260 popupMenu.addAction(viewMussaAlignmentAction);
261 popupMenu.addAction(createSubAnalysisAction);
264 void MussaWindow::setupAssistant()
266 #if defined(QT_ASSISTANT_FOUND)
267 QStringList manualAssistantArgs;
268 manualAssistantArgs = QStringList();
269 manualAssistantArgs << "-profile" << "./doc/manual/mussagl_manual.adp";
270 manualAssistant = new QAssistantClient("assistant", this);
271 manualAssistant->setArguments(manualAssistantArgs);
272 connect(manualAssistant, SIGNAL(error(QString)),
273 this, SLOT(assistantError(QString)));
277 void MussaWindow::about()
279 QString msg("Welcome to Multiple Species Sequence Analysis\n"
280 "(c) 2005-2006 California Institute of Technology\n"
281 "Tristan De Buysscher, Diane Trout\n");
284 msg += (char *)glGetString(GL_VERSION);
286 QMessageBox::about(this, tr("About mussa"), msg);
289 void MussaWindow::clear()
291 aligned_windows.clear();
295 void MussaWindow::createNewAnalysis()
298 // ideally we should open a new window if there's an analysis
299 // but this should work for the moment.
300 if (not isClearingAnalysisSafe()) return;
302 if (setup_analysis_dialog.exec()) {
304 m = setup_analysis_dialog.getMussa();
307 } catch(mussa_error e) {
308 QString msg(e.what());
309 QMessageBox::warning(this, tr("Create New Analysis"), msg);
313 void MussaWindow::createSubAnalysis()
315 list<SequenceLocation> result;
316 SequenceLocationModel& model = subanalysis_window.getModel();
317 browser.copySelectedTracksAsSeqLocation(result);
318 for(list<SequenceLocation>::iterator result_itor = result.begin();
319 result_itor != result.end();
322 model.push_back(*result_itor);
325 if (not subanalysis_window.isVisible()) {
326 subanalysis_window.show();
330 void MussaWindow::saveAnalysis()
332 // if we've got an analysis
333 if (analysis and not analysis->empty()) {
334 // if it doesn't have a name we need to pick one
335 if (analysis->get_analysis_path().empty()) {
338 // if we've got a name, has it changed any?
339 // this doesn't work when a sequence changes something (like its
341 //else if (analysis->is_dirty())
347 void MussaWindow::saveAnalysisAs()
349 std::auto_ptr<QFileDialog> dialog(new QFileDialog(this));
350 dialog->setAcceptMode(QFileDialog::AcceptSave);
351 dialog->setFileMode(QFileDialog::AnyFile);
352 dialog->setDirectory(QDir(default_dir.native_directory_string().c_str()));
354 QStringList fileNames;
355 if (not dialog->exec()) {
358 fileNames = dialog->selectedFiles();
360 if (fileNames.size() != 1) {
364 fs::path save_path(fileNames[0].toStdString());
365 // do you want to overwrite?
366 if (fs::exists(save_path) and
367 QMessageBox::question(
369 tr("Overwrite File? -- Mussa"),
370 tr("A file called %1 already exists"
371 "do you want to overwrite it?")
373 tr("&Yes"), tr("&No"),
378 analysis->save(save_path);
379 default_dir = (save_path / "..").normalize();
382 bool MussaWindow::isClearingAnalysisSafe()
384 if (analysis and not analysis->empty() and analysis->is_dirty()) {
385 switch (QMessageBox::question(
387 tr("Save Unsaved Changes -- Mussa"),
388 tr("There are unsaved changes,\ndo you want to save?"),
389 tr("&Yes"), tr("&No"), tr("&Cancel"),
404 throw runtime_error("isClearingAnalysis QMesageBox failure");
407 // if we're here we've been saved and can replace
411 void MussaWindow::editMotifs()
413 if (motif_editor != 0) {
414 motif_editor->hide();
417 motif_editor = new MotifEditor(analysis);
418 connect(motif_editor, SIGNAL(changedMotifs()),
419 this, SLOT(updateAnnotations()));
420 motif_editor->show();
423 void MussaWindow::loadMotifList()
425 QString caption("Load a motif list");
426 QString filter("Motif list(*.txt *.mtl)");
427 QDir default_qdir(default_dir.native_directory_string().c_str());
428 QString path = QFileDialog::getOpenFileName(this,
430 default_qdir.absolutePath(),
435 // try to load safely
437 fs::path converted_path(path.toStdString(), fs::native);
438 analysis->load_motifs(converted_path);
439 default_dir = converted_path.branch_path();
440 } catch (runtime_error e) {
441 QString msg("Unable to load ");
445 QMessageBox::warning(this, "Load Motifs", msg);
447 assert (analysis != 0);
450 void MussaWindow::saveMotifList()
455 void MussaWindow::loadMupa()
457 QString caption("Load a mussa parameter file");
458 QString filter("Mussa Parameters (*.mupa)");
459 QDir default_qdir(QDir(default_dir.native_directory_string().c_str()));
461 QString mupa_path = QFileDialog::getOpenFileName(
464 default_qdir.absolutePath(),
468 if (mupa_path.isNull())
470 // try to load safely
472 // ideally we should open a new window if there's an analysis
473 // but this should work for the moment.
474 if (not isClearingAnalysisSafe()) return;
476 Mussa *m = new Mussa;
477 fs::path converted_path(mupa_path.toStdString(), fs::native);
478 connect(m, SIGNAL(progress(const std::string&, int, int)),
479 this, SLOT(updateProgress(const std::string&, int, int)));
480 m->load_mupa_file(converted_path);
483 setWindowTitle(converted_path.native_file_string().c_str());
484 // grab the path ignoring the mupa file portion
485 default_dir = converted_path.branch_path();
486 } catch (mussa_load_error e) {
487 QString msg("Unable to load ");
491 QMessageBox::warning(this, "Load Parameter", msg);
493 assert (analysis != 0);
496 void MussaWindow::loadSavedAnalysis()
498 QDir default_qdir(QDir(default_dir.native_directory_string().c_str()));
499 QString caption("Load a previously run analysis");
500 QString muway_dir = QFileDialog::getExistingDirectory(
503 default_qdir.absolutePath()
506 if (muway_dir.isNull())
508 // try to safely load
510 // ideally we should open a new window if there's an analysis
511 // but this should work for the moment.
512 if (not isClearingAnalysisSafe()) return;
514 Mussa *m = new Mussa;
515 fs::path converted_path(muway_dir.toStdString(), fs::native);
516 connect(m, SIGNAL(progress(const std::string&, int, int)),
517 this, SLOT(updateProgress(const std::string&, int, int)));
518 m->load(converted_path);
519 // only switch mussas if we loaded without error
520 if (analysis->empty()) {
521 // our current window is empty so load and replace.
523 setWindowTitle(converted_path.native_file_string().c_str());
524 default_dir = converted_path.branch_path();
526 MussaWindow *win = new MussaWindow(m);
527 win->setWindowTitle(converted_path.native_file_string().c_str());
528 win->default_dir = converted_path.branch_path();
531 } catch (mussa_load_error e) {
532 QString msg("Unable to load ");
536 QMessageBox::warning(this, "Load Parameter", msg);
538 assert (analysis != 0);
541 void MussaWindow::newMussaWindow()
543 Mussa *a = new Mussa();
544 MussaWindow *win = new MussaWindow(a);
545 win->default_dir = default_dir;
549 void MussaWindow::setSoftThreshold(int threshold)
551 if (analysis->get_soft_threshold() != threshold) {
552 analysis->set_soft_threshold(threshold);
559 void MussaWindow::showMussaToolbar()
561 if (mussaViewTB.isVisible())
567 void MussaWindow::toggleMotifs()
572 void MussaWindow::showManual()
574 #if QT_QTASSISTANT_FOUND
575 manualAssistant->openAssistant();
578 boost::python::object webopen = get_py()["webbrowser.open"];
579 webopen("http://woldlab.caltech.edu/~king/mussagl_manual/");
580 } catch( boost::python::error_already_set ) {
583 #endif //QT_QTASSISTANT_FOUND
586 void MussaWindow::assistantError(QString message)
588 //std::cout << "QAssistantError: " << message.toStdString() << "\n";
589 QMessageBox::warning ( this, "Warning: Mussagl Manual", message,
591 QMessageBox::NoButton);
594 void MussaWindow::NotImplementedBox()
596 QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
599 void MussaWindow::viewMussaAlignment()
601 const set<int>& selected_paths = browser.selectedPaths();
602 if (selected_paths.size() == 0 ) {
603 QMessageBox::warning(this,
604 QObject::tr("mussa"),
605 QObject::tr("you should probably select some paths "
608 boost::shared_ptr<MussaAlignedWindow> ma_win(
609 new MussaAlignedWindow(*analysis, selected_paths)
612 aligned_windows.push_back(ma_win);
613 connect(this, SIGNAL(changedAnnotations()),
614 aligned_windows.back().get(), SLOT(update()));
615 aligned_windows.back()->show();
619 void MussaWindow::updateAnalysis()
621 threshold.setRange(analysis->get_threshold(),analysis->get_window());
623 const Mussa::vector_sequence_type& seqs = analysis->sequences();
624 browser.setSequences(seqs, analysis->colorMapper());
627 zoom.setValue(browser.zoom());
630 void MussaWindow::updateAnnotations()
632 // motifs were changed in the sequences by
633 // Mussa::update_sequences_motifs
634 emit changedAnnotations();
638 void MussaWindow::updateLinks()
640 browser.clear_links();
641 bool reversed = false;
642 const NwayPaths& nway = analysis->paths();
644 typedef list<ConservedPath> conserved_paths;
645 typedef conserved_paths::const_iterator const_conserved_paths_itor;
646 for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
647 path_itor != nway.refined_pathz.end();
650 // since we were drawing to the start of a window, and opengl lines
651 // are centered around the two connecting points our lines were slightly
652 // offset. the idea of window_offset is to adjust them to the
653 // right for forward compliment or left for reverse compliment
654 // FIXME: figure out how to unit test these computations
655 //GLfloat window_offset = (path_itor->window_size)/2.0;
657 size_t track_len = path_itor->track_indexes.size();
658 vector<int> normalized_path;
659 normalized_path.reserve(track_len);
660 vector<bool> rc_flags(false, track_len);
661 for (size_t track_i=0; track_i != track_len; ++track_i)
663 int x = path_itor->track_indexes[track_i];
664 // at some point when we modify the pathz data structure to keep
665 // track of the score we can put grab the depth here.
667 // are we reverse complimented?
675 normalized_path.push_back(x);
676 rc_flags.push_back(reversed);
678 browser.link(normalized_path, rc_flags, path_itor->window_size);
684 MussaWindow::updateProgress(const string& description, int current, int max)
687 if (current == max) {
688 if (progress_dialog != 0) {
689 progress_dialog->hide();
690 delete progress_dialog;
694 // if we're starting, create the dialog
695 if (progress_dialog == 0) {
696 QString desc(description.c_str());
697 QString cancel("Cancel");
698 progress_dialog = new QProgressDialog(desc, cancel, current, max, this);
699 progress_dialog->show();
701 // just update the dialog
702 progress_dialog->setValue(current);
705 qApp->processEvents();