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) :
36 setup_analysis_dialog(this),
38 mussaViewTB("Path Views"),
44 createNewAnalysisAction(0),
45 createSubAnalysisAction(0),
47 loadMotifListAction(0),
49 loadSavedAnalysisAction(0),
50 mussaManualAssistantAction(0),
51 newMussaWindowAction(0),
52 saveMotifListAction(0),
53 showMussaViewToolbarAction(0),
54 toggleMotifsAction(0),
55 saveBrowserPixmapAction(0),
57 viewMussaAlignmentAction(0),
64 //This next setWhatsThis function prevents
65 // a segfault when using WhatsThis feature with
67 //scene->setWhatsThis(tr("Mussa in OpenGL!"));
68 setCentralWidget(&browser);
69 // well updatePosition isn't quite right as we really just need
71 connect(this, SIGNAL(changedAnnotations()), &browser, SLOT(update()));
73 //mussaViewTB.addAction(toggleMotifsAction);
74 mussaViewTB.addWidget(&zoom);
76 connect(&zoom, SIGNAL(valueChanged(double)),
77 &browser, SLOT(setZoom(double)));
79 // threshold range is set in updateAnalysis
81 //scene->setClipPlane(20);
82 // FIXME: for when we get the paths drawn at the appropriate depth
83 //connect(&threshold, SIGNAL(thresholdChanged(int)),
84 // this, SLOT(setClipPlane(int)));
85 connect(&threshold, SIGNAL(thresholdChanged(int)),
86 this, SLOT(setSoftThreshold(int)));
87 mussaViewTB.addWidget(&threshold);
89 addToolBar(&mussaViewTB);
91 statusBar()->showMessage("Welcome to mussa", 2000);
92 connect(analysis, SIGNAL(progress(const std::string&, int, int)),
93 this, SLOT(updateProgress(const std::string&, int, int)));
97 void MussaWindow::setAnalysis(Mussa *new_analysis)
99 if (new_analysis != 0) {
100 // only switch mussas if we loaded without error
103 analysis = new_analysis;
104 setWindowTitle(analysis->get_name().c_str());
109 void MussaWindow::setupActions()
111 // we really don't want to run this more than once.
112 assert (closeAction == 0);
114 // the ever popular about box
115 aboutAction = new QAction(tr("&About"), this);
116 connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
117 aboutAction->setIcon(QIcon(":/icons/info.png"));
120 closeAction = new QAction(tr("&Close"), this);
121 closeAction->setStatusTip(tr("Close this window"));
122 connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
123 closeAction->setIcon(QIcon(":/icons/exit.png"));
125 createNewAnalysisAction = new QAction(tr("Create Analysis"), this);
126 connect(createNewAnalysisAction, SIGNAL(triggered()),
127 this, SLOT(createNewAnalysis()));
128 createNewAnalysisAction->setIcon(QIcon(":/icons/filenew.png"));
130 createSubAnalysisAction = new QAction(tr("Add to Subanalysis"), this);
131 connect(createSubAnalysisAction, SIGNAL(triggered()),
132 this, SLOT(createSubAnalysis()));
134 saveAnalysisAction = new QAction(tr("&Save Analysis"), this);
135 connect(saveAnalysisAction, SIGNAL(triggered()),
136 this, SLOT(saveAnalysis()));
137 saveAnalysisAction->setIcon(QIcon(":/icons/filesave.png"));
139 saveAnalysisAsAction = new QAction(tr("Save Analysis &As"), this);
140 connect(saveAnalysisAsAction, SIGNAL(triggered()),
141 this, SLOT(saveAnalysisAs()));
142 saveAnalysisAsAction->setIcon(QIcon(":/icons/filesave.png"));
144 editMotifsAction = new QAction(tr("Edit Motifs"), this);;
145 connect(editMotifsAction, SIGNAL(triggered()), this, SLOT(editMotifs()));
147 loadMotifListAction = new QAction(tr("Load Motif List"), this);
148 connect(loadMotifListAction, SIGNAL(triggered()),
149 this, SLOT(loadMotifList()));
150 loadMotifListAction->setIcon(QIcon(":/icons/fileopen.png"));
152 loadMupaAction = new QAction(tr("Create Analysis from File"), this);
153 connect(loadMupaAction, SIGNAL(triggered()),
154 this, SLOT(loadMupa()));
155 loadMupaAction->setIcon(QIcon(":/icons/fileopen.png"));
157 loadSavedAnalysisAction = new QAction(tr("Load Existing &Analysis"), this);
158 connect(loadSavedAnalysisAction, SIGNAL(triggered()),
159 this, SLOT(loadSavedAnalysis()));
160 loadSavedAnalysisAction->setIcon(QIcon(":/icons/fileopen.png"));
162 mussaManualAssistantAction = new QAction(tr("Mussagl Manual..."), this);
163 mussaManualAssistantAction->setIcon(QIcon(":/icons/contents.png"));
164 connect(mussaManualAssistantAction, SIGNAL(triggered()),
165 this, SLOT(showManual()));
167 newMussaWindowAction = new QAction(tr("&New Mussa Window"), this);
168 newMussaWindowAction->setStatusTip("open another mussa window to allow comparing results");
169 connect(newMussaWindowAction, SIGNAL(triggered()),
170 this, SLOT(newMussaWindow()));
171 newMussaWindowAction->setIcon(QIcon(":/icons/window_new.png"));
173 saveMotifListAction = new QAction(tr("Save Motifs"), this);
174 connect(saveMotifListAction, SIGNAL(triggered()),
175 this, SLOT(saveMotifList()));
176 saveMotifListAction->setIcon(QIcon(":/icons/filesave.png"));
178 showMussaViewToolbarAction = new QAction(tr("Show Toolbar"), this);
179 connect(showMussaViewToolbarAction, SIGNAL(triggered()),
180 this, SLOT(showMussaToolbar()));
181 showMussaViewToolbarAction->setCheckable(true);
182 showMussaViewToolbarAction->setChecked(true);
184 toggleMotifsAction = new QAction(tr("Toggle Motifs"), this);
185 connect(toggleMotifsAction, SIGNAL(triggered()),
186 this, SLOT(toggleMotifs()));
187 toggleMotifsAction->setCheckable(true);
188 toggleMotifsAction->setIcon(QIcon(":/icons/motif_icon.png"));
189 toggleMotifsAction->setWhatsThis(tr("Toggle motif annotations on/off\n\n"
190 "You can load motif annotations via "
191 "'File->Load Motif List' menu option."));
193 //Save pixel map action
194 saveBrowserPixmapAction = new QAction(tr("Save to image..."), this);
195 connect(saveBrowserPixmapAction, (SIGNAL(triggered())),
196 &browser, SLOT(promptSaveBrowserPixmap()));
197 saveBrowserPixmapAction->setIcon(QIcon(":/icons/image2.png"));
199 viewMussaAlignmentAction = new QAction(tr("View sequence alignment"), this);
200 connect(viewMussaAlignmentAction, SIGNAL(triggered()),
201 this, SLOT(viewMussaAlignment() ));
202 viewMussaAlignmentAction->setWhatsThis(tr("Create a zoomed in window "
203 "showing alignment of the seqcomp "
206 whatsThisAction = QWhatsThis::createAction(this);
207 whatsThisAction->setIcon(QIcon(":/icons/help.png"));
212 void MussaWindow::closeEvent(QCloseEvent *event)
214 if(isClearingAnalysisSafe()) {
221 void MussaWindow::setupMainMenu()
223 // we need to run setupActions first
224 assert (closeAction != 0);
226 QMenu *newMenu = menuBar()->addMenu(tr("&File"));
228 newMenu->addAction(newMussaWindowAction);
229 newMenu->addAction(createNewAnalysisAction);
230 newMenu->addAction(loadMupaAction);
231 newMenu->addAction(loadSavedAnalysisAction);
232 newMenu->addAction(saveAnalysisAction);
233 newMenu->addAction(saveAnalysisAsAction);
234 newMenu->addSeparator();
235 newMenu->addAction(loadMotifListAction);
236 newMenu->addAction(saveMotifListAction);
237 newMenu->addSeparator();
238 newMenu->addAction(saveBrowserPixmapAction);
239 newMenu->addSeparator();
240 newMenu->addAction(closeAction);
242 newMenu = menuBar()->addMenu(tr("&Edit"));
243 newMenu->addAction(editMotifsAction);
244 newMenu->addAction(&browser.getCopySelectedSequenceAsFastaAction());
245 newMenu->addAction(createSubAnalysisAction);
247 newMenu = menuBar()->addMenu(tr("&View"));
248 newMenu->addAction(viewMussaAlignmentAction);
249 newMenu->addAction(showMussaViewToolbarAction);
251 newMenu = menuBar()->addMenu(tr("&Help"));
252 newMenu->addAction(mussaManualAssistantAction);
253 newMenu->addAction(whatsThisAction);
254 newMenu->addSeparator();
255 newMenu->addAction(aboutAction);
257 // add some extra features to the context menu
258 QMenu& popupMenu = browser.getPopupMenu();
259 popupMenu.addAction(viewMussaAlignmentAction);
260 popupMenu.addAction(createSubAnalysisAction);
263 void MussaWindow::setupAssistant()
265 #if defined(QT_ASSISTANT_FOUND)
266 QStringList manualAssistantArgs;
267 manualAssistantArgs = QStringList();
268 manualAssistantArgs << "-profile" << "./doc/manual/mussagl_manual.adp";
269 manualAssistant = new QAssistantClient("assistant", this);
270 manualAssistant->setArguments(manualAssistantArgs);
271 connect(manualAssistant, SIGNAL(error(QString)),
272 this, SLOT(assistantError(QString)));
276 void MussaWindow::about()
278 QString msg("Welcome to Multiple Species Sequence Analysis\n"
279 "(c) 2005-2006 California Institute of Technology\n"
280 "Tristan De Buysscher, Diane Trout\n");
283 msg += (char *)glGetString(GL_VERSION);
285 QMessageBox::about(this, tr("About mussa"), msg);
288 void MussaWindow::clear()
290 aligned_windows.clear();
294 void MussaWindow::createNewAnalysis()
297 // ideally we should open a new window if there's an analysis
298 // but this should work for the moment.
299 if (not isClearingAnalysisSafe()) return;
301 if (setup_analysis_dialog.exec()) {
303 m = setup_analysis_dialog.getMussa();
306 } catch(mussa_error e) {
307 QString msg(e.what());
308 QMessageBox::warning(this, tr("Create New Analysis"), msg);
312 void MussaWindow::createSubAnalysis()
314 list<SequenceLocation> result;
315 SequenceLocationModel& model = subanalysis_window.getModel();
316 browser.copySelectedTracksAsSeqLocation(result);
317 for(list<SequenceLocation>::iterator result_itor = result.begin();
318 result_itor != result.end();
321 model.push_back(*result_itor);
324 if (not subanalysis_window.isVisible()) {
325 subanalysis_window.show();
329 void MussaWindow::saveAnalysis()
331 // if we've got an analysis
332 if (analysis and not analysis->empty()) {
333 // if it doesn't have a name we need to pick one
334 if (analysis->get_analysis_path().empty()) {
337 // if we've got a name, has it changed any?
338 // this doesn't work when a sequence changes something (like its
340 //else if (analysis->is_dirty())
345 void MussaWindow::saveAnalysisAs()
347 std::auto_ptr<QFileDialog> dialog(new QFileDialog(this));
348 dialog->setAcceptMode(QFileDialog::AcceptSave);
349 dialog->setFileMode(QFileDialog::AnyFile);
350 dialog->setDirectory(QDir::home());
352 QStringList fileNames;
353 if (not dialog->exec()) {
356 fileNames = dialog->selectedFiles();
358 if (fileNames.size() != 1) {
362 fs::path save_path(fileNames[0].toStdString());
363 // do you want to overwrite?
364 if (fs::exists(save_path) and
365 QMessageBox::question(
367 tr("Overwrite File? -- Mussa"),
368 tr("A file called %1 already exists"
369 "do you want to overwrite it?")
371 tr("&Yes"), tr("&No"),
376 analysis->save(save_path);
379 bool MussaWindow::isClearingAnalysisSafe()
381 if (analysis and not analysis->empty() and analysis->is_dirty()) {
382 switch (QMessageBox::question(
384 tr("Save Unsaved Changes -- Mussa"),
385 tr("There are unsaved changes,\ndo you want to save?"),
386 tr("&Yes"), tr("&No"), tr("&Cancel"),
401 throw runtime_error("isClearingAnalysis QMesageBox failure");
404 // if we're here we've been saved and can replace
408 void MussaWindow::editMotifs()
410 if (motif_editor != 0) {
411 motif_editor->hide();
414 motif_editor = new MotifEditor(analysis);
415 connect(motif_editor, SIGNAL(changedMotifs()),
416 this, SLOT(updateAnnotations()));
417 motif_editor->show();
420 void MussaWindow::loadMotifList()
422 QString caption("Load a motif list");
423 QString filter("Motif list(*.txt *.mtl)");
424 QString path = QFileDialog::getOpenFileName(this,
431 // try to load safely
433 fs::path converted_path(path.toStdString(), fs::native);
434 analysis->load_motifs(converted_path);
435 } catch (runtime_error e) {
436 QString msg("Unable to load ");
440 QMessageBox::warning(this, "Load Motifs", msg);
442 assert (analysis != 0);
445 void MussaWindow::saveMotifList()
450 void MussaWindow::loadMupa()
452 QString caption("Load a mussa parameter file");
453 QString filter("Mussa Parameters (*.mupa)");
454 QString mupa_path = QFileDialog::getOpenFileName(this,
459 if (mupa_path.isNull())
461 // try to load safely
463 // ideally we should open a new window if there's an analysis
464 // but this should work for the moment.
465 if (not isClearingAnalysisSafe()) return;
467 Mussa *m = new Mussa;
468 fs::path converted_path(mupa_path.toStdString(), fs::native);
469 connect(m, SIGNAL(progress(const std::string&, int, int)),
470 this, SLOT(updateProgress(const std::string&, int, int)));
471 m->load_mupa_file(converted_path);
474 setWindowTitle(converted_path.native_file_string().c_str());
475 } catch (mussa_load_error e) {
476 QString msg("Unable to load ");
480 QMessageBox::warning(this, "Load Parameter", msg);
482 assert (analysis != 0);
485 void MussaWindow::loadSavedAnalysis()
487 QString caption("Load a previously run analysis");
488 QString muway_dir = QFileDialog::getExistingDirectory(this,
490 QDir::currentPath());
492 if (muway_dir.isNull())
494 // try to safely load
496 // ideally we should open a new window if there's an analysis
497 // but this should work for the moment.
498 if (not isClearingAnalysisSafe()) return;
500 Mussa *m = new Mussa;
501 fs::path converted_path(muway_dir.toStdString(), fs::native);
502 connect(m, SIGNAL(progress(const std::string&, int, int)),
503 this, SLOT(updateProgress(const std::string&, int, int)));
504 m->load(converted_path);
505 // only switch mussas if we loaded without error
507 setWindowTitle(converted_path.native_file_string().c_str());
508 } catch (mussa_load_error e) {
509 QString msg("Unable to load ");
513 QMessageBox::warning(this, "Load Parameter", msg);
515 assert (analysis != 0);
518 void MussaWindow::newMussaWindow()
520 Mussa *a = new Mussa();
521 MussaWindow *win = new MussaWindow(a);
525 void MussaWindow::setSoftThreshold(int threshold)
527 if (analysis->get_soft_threshold() != threshold) {
528 analysis->set_soft_threshold(threshold);
535 void MussaWindow::showMussaToolbar()
537 if (mussaViewTB.isVisible())
543 void MussaWindow::toggleMotifs()
548 void MussaWindow::showManual()
550 #if QT_QTASSISTANT_FOUND
551 manualAssistant->openAssistant();
554 boost::python::object webopen = get_py()["webbrowser.open"];
555 webopen("http://woldlab.caltech.edu/~king/mussagl_manual/");
556 } catch( boost::python::error_already_set ) {
559 #endif //QT_QTASSISTANT_FOUND
562 void MussaWindow::assistantError(QString message)
564 //std::cout << "QAssistantError: " << message.toStdString() << "\n";
565 QMessageBox::warning ( this, "Warning: Mussagl Manual", message,
567 QMessageBox::NoButton);
570 void MussaWindow::NotImplementedBox()
572 QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
575 void MussaWindow::viewMussaAlignment()
577 const set<int>& selected_paths = browser.selectedPaths();
578 if (selected_paths.size() == 0 ) {
579 QMessageBox::warning(this,
580 QObject::tr("mussa"),
581 QObject::tr("you should probably select some paths "
584 boost::shared_ptr<MussaAlignedWindow> ma_win(
585 new MussaAlignedWindow(*analysis, selected_paths)
588 aligned_windows.push_back(ma_win);
589 connect(this, SIGNAL(changedAnnotations()),
590 aligned_windows.back().get(), SLOT(update()));
591 aligned_windows.back()->show();
595 void MussaWindow::updateAnalysis()
597 threshold.setRange(analysis->get_threshold(),analysis->get_window());
599 const Mussa::vector_sequence_type& seqs = analysis->sequences();
600 browser.setSequences(seqs, analysis->colorMapper());
603 zoom.setValue(browser.zoom());
606 void MussaWindow::updateAnnotations()
608 // motifs were changed in the sequences by
609 // Mussa::update_sequences_motifs
610 emit changedAnnotations();
614 void MussaWindow::updateLinks()
616 browser.clear_links();
617 bool reversed = false;
618 const NwayPaths& nway = analysis->paths();
620 typedef list<ConservedPath> conserved_paths;
621 typedef conserved_paths::const_iterator const_conserved_paths_itor;
622 for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
623 path_itor != nway.refined_pathz.end();
626 // since we were drawing to the start of a window, and opengl lines
627 // are centered around the two connecting points our lines were slightly
628 // offset. the idea of window_offset is to adjust them to the
629 // right for forward compliment or left for reverse compliment
630 // FIXME: figure out how to unit test these computations
631 //GLfloat window_offset = (path_itor->window_size)/2.0;
633 size_t track_len = path_itor->track_indexes.size();
634 vector<int> normalized_path;
635 normalized_path.reserve(track_len);
636 vector<bool> rc_flags(false, track_len);
637 for (size_t track_i=0; track_i != track_len; ++track_i)
639 int x = path_itor->track_indexes[track_i];
640 // at some point when we modify the pathz data structure to keep
641 // track of the score we can put grab the depth here.
643 // are we reverse complimented?
651 normalized_path.push_back(x);
652 rc_flags.push_back(reversed);
654 browser.link(normalized_path, rc_flags, path_itor->window_size);
660 MussaWindow::updateProgress(const string& description, int current, int max)
663 if (current == max) {
664 if (progress_dialog != 0) {
665 progress_dialog->hide();
666 delete progress_dialog;
670 // if we're starting, create the dialog
671 if (progress_dialog == 0) {
672 QString desc(description.c_str());
673 QString cancel("Cancel");
674 progress_dialog = new QProgressDialog(desc, cancel, current, max, this);
675 progress_dialog->show();
677 // just update the dialog
678 progress_dialog->setValue(current);
681 qApp->processEvents();