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)));
99 void MussaWindow::setAnalysis(Mussa *new_analysis)
101 if (new_analysis != 0) {
102 // only switch mussas if we loaded without error
105 analysis = new_analysis;
106 setWindowTitle(analysis->get_name().c_str());
111 void MussaWindow::setupActions()
113 // we really don't want to run this more than once.
114 assert (closeAction == 0);
116 // the ever popular about box
117 aboutAction = new QAction(tr("&About"), this);
118 connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
119 aboutAction->setIcon(QIcon(":/icons/info.png"));
122 closeAction = new QAction(tr("&Close"), this);
123 closeAction->setStatusTip(tr("Close this window"));
124 connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
125 closeAction->setIcon(QIcon(":/icons/exit.png"));
127 createNewAnalysisAction = new QAction(tr("Create Analysis"), this);
128 connect(createNewAnalysisAction, SIGNAL(triggered()),
129 this, SLOT(createNewAnalysis()));
130 createNewAnalysisAction->setIcon(QIcon(":/icons/filenew.png"));
132 createSubAnalysisAction = new QAction(tr("Add to Subanalysis"), this);
133 connect(createSubAnalysisAction, SIGNAL(triggered()),
134 this, SLOT(createSubAnalysis()));
136 saveAnalysisAction = new QAction(tr("&Save Analysis"), this);
137 connect(saveAnalysisAction, SIGNAL(triggered()),
138 this, SLOT(saveAnalysis()));
139 saveAnalysisAction->setIcon(QIcon(":/icons/filesave.png"));
141 saveAnalysisAsAction = new QAction(tr("Save Analysis &As"), this);
142 connect(saveAnalysisAsAction, SIGNAL(triggered()),
143 this, SLOT(saveAnalysisAs()));
144 saveAnalysisAsAction->setIcon(QIcon(":/icons/filesave.png"));
146 editMotifsAction = new QAction(tr("Edit Motifs"), this);;
147 connect(editMotifsAction, SIGNAL(triggered()), this, SLOT(editMotifs()));
149 loadMotifListAction = new QAction(tr("Load Motif List"), this);
150 connect(loadMotifListAction, SIGNAL(triggered()),
151 this, SLOT(loadMotifList()));
152 loadMotifListAction->setIcon(QIcon(":/icons/fileopen.png"));
154 loadMupaAction = new QAction(tr("Create Analysis from File"), this);
155 connect(loadMupaAction, SIGNAL(triggered()),
156 this, SLOT(loadMupa()));
157 loadMupaAction->setIcon(QIcon(":/icons/fileopen.png"));
159 loadSavedAnalysisAction = new QAction(tr("Load Existing &Analysis"), this);
160 connect(loadSavedAnalysisAction, SIGNAL(triggered()),
161 this, SLOT(loadSavedAnalysis()));
162 loadSavedAnalysisAction->setIcon(QIcon(":/icons/fileopen.png"));
164 mussaManualAssistantAction = new QAction(tr("Mussagl Manual..."), this);
165 mussaManualAssistantAction->setIcon(QIcon(":/icons/contents.png"));
166 connect(mussaManualAssistantAction, SIGNAL(triggered()),
167 this, SLOT(showManual()));
169 newMussaWindowAction = new QAction(tr("&New Mussa Window"), this);
170 newMussaWindowAction->setStatusTip("open another mussa window to allow comparing results");
171 connect(newMussaWindowAction, SIGNAL(triggered()),
172 this, SLOT(newMussaWindow()));
173 newMussaWindowAction->setIcon(QIcon(":/icons/window_new.png"));
175 saveMotifListAction = new QAction(tr("Save Motifs"), this);
176 connect(saveMotifListAction, SIGNAL(triggered()),
177 this, SLOT(saveMotifList()));
178 saveMotifListAction->setIcon(QIcon(":/icons/filesave.png"));
180 showMussaViewToolbarAction = new QAction(tr("Show Toolbar"), this);
181 connect(showMussaViewToolbarAction, SIGNAL(triggered()),
182 this, SLOT(showMussaToolbar()));
183 showMussaViewToolbarAction->setCheckable(true);
184 showMussaViewToolbarAction->setChecked(true);
186 toggleMotifsAction = new QAction(tr("Toggle Motifs"), this);
187 connect(toggleMotifsAction, SIGNAL(triggered()),
188 this, SLOT(toggleMotifs()));
189 toggleMotifsAction->setCheckable(true);
190 toggleMotifsAction->setIcon(QIcon(":/icons/motif_icon.png"));
191 toggleMotifsAction->setWhatsThis(tr("Toggle motif annotations on/off\n\n"
192 "You can load motif annotations via "
193 "'File->Load Motif List' menu option."));
195 //Save pixel map action
196 saveBrowserPixmapAction = new QAction(tr("Save to image..."), this);
197 connect(saveBrowserPixmapAction, (SIGNAL(triggered())),
198 browser, SLOT(promptSaveBrowserPixmap()));
199 saveBrowserPixmapAction->setIcon(QIcon(":/icons/image2.png"));
201 viewMussaAlignmentAction = new QAction(tr("View sequence alignment"), this);
202 connect(viewMussaAlignmentAction, SIGNAL(triggered()),
203 this, SLOT(viewMussaAlignment() ));
204 viewMussaAlignmentAction->setWhatsThis(tr("Create a zoomed in window "
205 "showing alignment of the seqcomp "
208 whatsThisAction = QWhatsThis::createAction(this);
209 whatsThisAction->setIcon(QIcon(":/icons/help.png"));
214 void MussaWindow::closeEvent(QCloseEvent *event)
216 if(isClearingAnalysisSafe()) {
223 void MussaWindow::setupMainMenu()
225 // we need to run setupActions first
226 assert (closeAction != 0);
228 QMenu *newMenu = menuBar()->addMenu(tr("&File"));
230 newMenu->addAction(newMussaWindowAction);
231 newMenu->addAction(createNewAnalysisAction);
232 newMenu->addAction(loadMupaAction);
233 newMenu->addAction(loadSavedAnalysisAction);
234 newMenu->addAction(saveAnalysisAction);
235 newMenu->addAction(saveAnalysisAsAction);
236 newMenu->addSeparator();
237 newMenu->addAction(loadMotifListAction);
238 newMenu->addAction(saveMotifListAction);
239 newMenu->addSeparator();
240 newMenu->addAction(saveBrowserPixmapAction);
241 newMenu->addSeparator();
242 newMenu->addAction(closeAction);
244 newMenu = menuBar()->addMenu(tr("&Edit"));
245 newMenu->addAction(editMotifsAction);
246 newMenu->addAction(browser->getCopySelectedSequenceAsFastaAction());
247 newMenu->addAction(createSubAnalysisAction);
249 newMenu = menuBar()->addMenu(tr("&View"));
250 newMenu->addAction(viewMussaAlignmentAction);
251 newMenu->addAction(showMussaViewToolbarAction);
253 newMenu = menuBar()->addMenu(tr("&Help"));
254 newMenu->addAction(mussaManualAssistantAction);
255 newMenu->addAction(whatsThisAction);
256 newMenu->addSeparator();
257 newMenu->addAction(aboutAction);
259 // add some extra features to the context menu
260 QMenu *popupMenu = browser->getPopupMenu();
262 popupMenu->addAction(viewMussaAlignmentAction);
263 popupMenu->addAction(createSubAnalysisAction);
267 void MussaWindow::setupAssistant()
269 #if defined(QT_ASSISTANT_FOUND)
270 QStringList manualAssistantArgs;
271 manualAssistantArgs = QStringList();
272 manualAssistantArgs << "-profile" << "./doc/manual/mussagl_manual.adp";
273 manualAssistant = new QAssistantClient("assistant", this);
274 manualAssistant->setArguments(manualAssistantArgs);
275 connect(manualAssistant, SIGNAL(error(QString)),
276 this, SLOT(assistantError(QString)));
280 void MussaWindow::about()
282 QString msg("Welcome to Multiple Species Sequence Analysis\n"
283 "(c) 2005-2006 California Institute of Technology\n"
284 "Tristan De Buysscher, Diane Trout\n");
287 msg += (char *)glGetString(GL_VERSION);
289 QMessageBox::about(this, tr("About mussa"), msg);
292 void MussaWindow::clear()
294 aligned_windows.clear();
298 void MussaWindow::createNewAnalysis()
301 // ideally we should open a new window if there's an analysis
302 // but this should work for the moment.
303 if (not isClearingAnalysisSafe()) return;
305 if (setup_analysis_dialog->exec()) {
307 m = setup_analysis_dialog->getMussa();
310 } catch(mussa_error e) {
311 QString msg(e.what());
312 QMessageBox::warning(this, tr("Create New Analysis"), msg);
316 void MussaWindow::createSubAnalysis()
318 list<SequenceLocation> result;
319 SequenceLocationModel& model = subanalysis_window->getModel();
320 browser->copySelectedTracksAsSeqLocation(result);
321 for(list<SequenceLocation>::iterator result_itor = result.begin();
322 result_itor != result.end();
325 model.push_back(*result_itor);
328 if (not subanalysis_window->isVisible()) {
329 subanalysis_window->show();
333 void MussaWindow::saveAnalysis()
335 // if we've got an analysis
336 if (analysis and not analysis->empty()) {
337 // if it doesn't have a name we need to pick one
338 if (analysis->get_analysis_path().empty()) {
341 // if we've got a name, has it changed any?
342 // this doesn't work when a sequence changes something (like its
344 //else if (analysis->is_dirty())
350 void MussaWindow::saveAnalysisAs()
352 std::auto_ptr<QFileDialog> dialog(new QFileDialog(this));
353 dialog->setAcceptMode(QFileDialog::AcceptSave);
354 dialog->setFileMode(QFileDialog::AnyFile);
355 dialog->setDirectory(QDir(default_dir.native_directory_string().c_str()));
357 QStringList fileNames;
358 if (not dialog->exec()) {
361 fileNames = dialog->selectedFiles();
363 if (fileNames.size() != 1) {
366 fs::path save_path(fileNames[0].toStdString(), fs::native);
367 // do you want to overwrite?
368 if (fs::exists(save_path) and
369 QMessageBox::question(
371 tr("Overwrite File? -- Mussa"),
372 tr("A file called %1 already exists"
373 "do you want to overwrite it?")
375 tr("&Yes"), tr("&No"),
380 analysis->save(save_path);
381 default_dir = (save_path / "..").normalize();
384 bool MussaWindow::isClearingAnalysisSafe()
386 if (analysis and not analysis->empty() and analysis->is_dirty()) {
387 switch (QMessageBox::question(
389 tr("Save Unsaved Changes -- Mussa"),
390 tr("There are unsaved changes,\ndo you want to save?"),
391 tr("&Yes"), tr("&No"), tr("&Cancel"),
406 throw runtime_error("isClearingAnalysis QMesageBox failure");
409 // if we're here we've been saved and can replace
413 void MussaWindow::editMotifs()
415 if (motif_editor != 0) {
416 motif_editor->hide();
419 motif_editor = new MotifEditor(analysis);
420 connect(motif_editor, SIGNAL(changedMotifs()),
421 this, SLOT(updateAnnotations()));
422 motif_editor->show();
425 void MussaWindow::loadMotifList()
427 QString caption("Load a motif list");
428 QString filter("Motif list(*.txt *.mtl)");
429 QDir default_qdir(default_dir.native_directory_string().c_str());
430 QString path = QFileDialog::getOpenFileName(this,
432 default_qdir.absolutePath(),
437 // try to load safely
439 fs::path converted_path(path.toStdString(), fs::native);
440 analysis->load_motifs(converted_path);
441 default_dir = converted_path.branch_path();
442 } catch (runtime_error e) {
443 QString msg("Unable to load ");
447 QMessageBox::warning(this, "Load Motifs", msg);
449 assert (analysis != 0);
452 void MussaWindow::saveMotifList()
457 void MussaWindow::loadMupa()
459 QString caption("Load a mussa parameter file");
460 QString filter("Mussa Parameters (*.mupa)");
461 QDir default_qdir(QDir(default_dir.native_directory_string().c_str()));
463 QString mupa_path = QFileDialog::getOpenFileName(
466 default_qdir.absolutePath(),
470 if (mupa_path.isNull())
472 // try to load safely
474 // ideally we should open a new window if there's an analysis
475 // but this should work for the moment.
476 if (not isClearingAnalysisSafe()) return;
478 Mussa *m = new Mussa;
479 fs::path converted_path(mupa_path.toStdString(), fs::native);
480 connect(m, SIGNAL(progress(const std::string&, int, int)),
481 this, SLOT(updateProgress(const std::string&, int, int)));
482 m->load_mupa_file(converted_path);
485 setWindowTitle(converted_path.native_file_string().c_str());
486 // grab the path ignoring the mupa file portion
487 default_dir = converted_path.branch_path();
488 } catch (mussa_load_error e) {
489 QString msg("Unable to load ");
493 QMessageBox::warning(this, "Load Parameter", msg);
495 assert (analysis != 0);
498 void MussaWindow::loadSavedAnalysis()
500 QDir default_qdir(QDir(default_dir.native_directory_string().c_str()));
501 QString caption("Load a previously run analysis");
502 QString muway_dir = QFileDialog::getExistingDirectory(
505 default_qdir.absolutePath()
508 if (muway_dir.isNull())
510 // try to safely load
512 // ideally we should open a new window if there's an analysis
513 // but this should work for the moment.
514 if (not isClearingAnalysisSafe()) return;
516 Mussa *m = new Mussa;
517 fs::path converted_path(muway_dir.toStdString(), fs::native);
518 connect(m, SIGNAL(progress(const std::string&, int, int)),
519 this, SLOT(updateProgress(const std::string&, int, int)));
520 m->load(converted_path);
521 // only switch mussas if we loaded without error
522 if (analysis->empty()) {
523 // our current window is empty so load and replace.
525 setWindowTitle(converted_path.native_file_string().c_str());
526 default_dir = converted_path.branch_path();
528 MussaWindow *win = new MussaWindow(m);
529 win->setWindowTitle(converted_path.native_file_string().c_str());
530 win->default_dir = converted_path.branch_path();
533 } catch (mussa_load_error e) {
534 QString msg("Unable to load ");
538 QMessageBox::warning(this, "Load Parameter", msg);
540 assert (analysis != 0);
543 void MussaWindow::newMussaWindow()
545 Mussa *a = new Mussa();
546 MussaWindow *win = new MussaWindow(a);
547 win->default_dir = default_dir;
551 void MussaWindow::setSoftThreshold(int threshold)
553 if (analysis->get_soft_threshold() != threshold) {
554 analysis->set_soft_threshold(threshold);
561 void MussaWindow::showMussaToolbar()
563 if (mussaViewTB->isVisible())
569 void MussaWindow::toggleMotifs()
574 void MussaWindow::showManual()
576 #if QT_QTASSISTANT_FOUND
577 manualAssistant->openAssistant();
580 boost::python::object webopen = get_py()["webbrowser.open"];
581 webopen("http://woldlab.caltech.edu/~king/mussagl_manual/");
582 } catch( boost::python::error_already_set ) {
585 #endif //QT_QTASSISTANT_FOUND
588 void MussaWindow::assistantError(QString message)
590 //std::cout << "QAssistantError: " << message.toStdString() << "\n";
591 QMessageBox::warning ( this, "Warning: Mussagl Manual", message,
593 QMessageBox::NoButton);
596 void MussaWindow::NotImplementedBox()
598 QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
601 void MussaWindow::viewMussaAlignment()
603 const set<int>& selected_paths = browser->selectedPaths();
604 if (selected_paths.size() == 0 ) {
605 QMessageBox::warning(this,
606 QObject::tr("mussa"),
607 QObject::tr("you should probably select some paths "
610 boost::shared_ptr<MussaAlignedWindow> ma_win(
611 new MussaAlignedWindow(*analysis, selected_paths)
614 aligned_windows.push_back(ma_win);
615 connect(this, SIGNAL(changedAnnotations()),
616 aligned_windows.back().get(), SLOT(update()));
617 aligned_windows.back()->show();
621 void MussaWindow::updateAnalysis()
623 const Mussa::vector_sequence_type& seqs = analysis->sequences();
624 browser->setSequences(seqs, analysis->colorMapper());
625 assert(browser->sequences().size() == analysis->size());
627 // setRange eventually emits something that causes updateLinks to be called
628 // but it's possible for us to not have had a chance to set out sequences
630 threshold->setRange(analysis->get_threshold(),analysis->get_window());
633 zoom->setValue(browser->zoom());
636 void MussaWindow::updateAnnotations()
638 // motifs were changed in the sequences by
639 // Mussa::update_sequences_motifs
640 emit changedAnnotations();
644 void MussaWindow::updateLinks()
646 if(browser->sequences().size() == 0) {
647 // we don't have any sequences load so we have no business setting links
651 browser->clear_links();
652 bool reversed = false;
653 const NwayPaths& nway = analysis->paths();
655 typedef list<ConservedPath> conserved_paths;
656 typedef conserved_paths::const_iterator const_conserved_paths_itor;
657 for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
658 path_itor != nway.refined_pathz.end();
661 // since we were drawing to the start of a window, and opengl lines
662 // are centered around the two connecting points our lines were slightly
663 // offset. the idea of window_offset is to adjust them to the
664 // right for forward compliment or left for reverse compliment
665 // FIXME: figure out how to unit test these computations
666 //GLfloat window_offset = (path_itor->window_size)/2.0;
668 size_t track_len = path_itor->track_indexes.size();
669 vector<int> normalized_path;
670 normalized_path.reserve(track_len);
671 vector<bool> rc_flags(false, track_len);
672 for (size_t track_i=0; track_i != track_len; ++track_i)
674 int x = path_itor->track_indexes[track_i];
675 // at some point when we modify the pathz data structure to keep
676 // track of the score we can put grab the depth here.
678 // are we reverse complimented?
686 normalized_path.push_back(x);
687 rc_flags.push_back(reversed);
689 browser->link(normalized_path, rc_flags, path_itor->window_size);
695 MussaWindow::updateProgress(const string& description, int current, int max)
698 if (current == max) {
699 if (progress_dialog != 0) {
700 progress_dialog->hide();
701 delete progress_dialog;
705 // if we're starting, create the dialog
706 if (progress_dialog == 0) {
707 QString desc(description.c_str());
708 QString cancel("Cancel");
709 progress_dialog = new QProgressDialog(desc, cancel, current, max, this);
710 progress_dialog->show();
712 // just update the dialog
713 progress_dialog->setValue(current);
716 qApp->processEvents();