1 #include "qui/MussaWindow.hpp"
2 #include "mussa_exceptions.hpp"
6 #include <QApplication>
7 #include <QAssistantClient>
9 #include <QDesktopServices>
11 #include <QFileDialog>
12 #include <QHBoxLayout>
15 #include <QMessageBox>
19 #include <QStringList>
27 #include <boost/filesystem/path.hpp>
28 #include <boost/filesystem/operations.hpp>
29 #include <boost/filesystem/convenience.hpp>
30 namespace fs = boost::filesystem;
31 #include <boost/bind.hpp>
35 MussaWindow::MussaWindow(MussaRef analysis_, QWidget *parent) :
38 default_dir(new QDir(QDir::home().absolutePath())),
40 setup_analysis_dialog(new MussaSetupDialog(this)),
41 subanalysis_window(new SubanalysisWindow(analysis)),
42 browser(new SequenceBrowserWidget(default_dir, this)),
43 mussaViewTB(new QToolBar("Path Views")),
45 threshold(new ThresholdWidget),
49 createNewAnalysisAction(0),
50 createSubAnalysisAction(0),
52 loadMotifListAction(0),
54 loadSavedAnalysisAction(0),
55 mussaManualAssistantAction(0),
56 newMussaWindowAction(0),
57 saveMotifListAction(0),
58 showMussaViewToolbarAction(0),
59 toggleMotifsAction(0),
60 saveBrowserPixmapAction(0),
62 viewMussaAlignmentAction(0),
69 //This next setWhatsThis function prevents
70 // a segfault when using WhatsThis feature with
72 //scene->setWhatsThis(tr("Mussa in OpenGL!"));
73 setCentralWidget(browser);
74 // well updatePosition isn't quite right as we really just need
76 connect(this, SIGNAL(changedAnnotations()), browser, SLOT(update()));
77 connect(this, SIGNAL(changedMotifs()), this, SLOT(updateAnnotations()));
78 connect(browser, SIGNAL(basepairsCopied(size_t)),
79 this, SLOT(showBasePairsCopied(size_t)));
81 //mussaViewTB->addAction(toggleMotifsAction);
82 mussaViewTB->addWidget(zoom);
84 connect(zoom, SIGNAL(valueChanged(double)),
85 browser, SLOT(setZoom(double)));
87 // threshold range is set in updateAnalysis
89 //scene->setClipPlane(20);
90 // FIXME: for when we get the paths drawn at the appropriate depth
91 //connect(threshold, SIGNAL(thresholdChanged(int)),
92 // this, SLOT(setClipPlane(int)));
93 connect(threshold, SIGNAL(thresholdChanged(int)),
94 this, SLOT(setSoftThreshold(int)));
95 mussaViewTB->addWidget(threshold);
97 addToolBar(mussaViewTB);
99 statusBar()->showMessage("Welcome to mussa", 2000);
100 // FIXME: we should start refactoring the connect call to updateAnalysis or something
102 connect(analysis.get(), SIGNAL(progress(const std::string&, int, int)),
103 this, SLOT(updateProgress(const std::string&, int, int)));
104 connect(analysis.get(), SIGNAL(isModified(bool)),
105 this, SLOT(updateAnalysisModified(bool)));
111 void MussaWindow::setAnalysis(MussaRef new_analysis)
113 if (new_analysis != 0) {
114 // only switch mussas if we loaded without error
116 analysis.swap(new_analysis);
122 void MussaWindow::setupActions()
124 // we really don't want to run this more than once.
125 assert (closeAction == 0);
127 // the ever popular about box
128 aboutAction = new QAction(tr("&About"), this);
129 connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
130 aboutAction->setIcon(QIcon(":/icons/info.png"));
133 closeAction = new QAction(tr("&Close"), this);
134 closeAction->setStatusTip(tr("Close this window"));
135 connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
136 closeAction->setIcon(QIcon(":/icons/exit.png"));
138 createNewAnalysisAction = new QAction(tr("Create &New Analysis"), this);
139 connect(createNewAnalysisAction, SIGNAL(triggered()),
140 this, SLOT(createNewAnalysis()));
141 createNewAnalysisAction->setIcon(QIcon(":/icons/filenew.png"));
142 createNewAnalysisAction->setShortcut(Qt::CTRL | Qt::Key_N);
144 createSubAnalysisAction = new QAction(tr("Add to Subanalysis"), this);
145 connect(createSubAnalysisAction, SIGNAL(triggered()),
146 this, SLOT(createSubAnalysis()));
148 saveAnalysisAction = new QAction(tr("&Save Analysis"), this);
149 connect(saveAnalysisAction, SIGNAL(triggered()),
150 this, SLOT(saveAnalysis()));
151 saveAnalysisAction->setIcon(QIcon(":/icons/filesave.png"));
152 saveAnalysisAction->setShortcut(Qt::CTRL | Qt::Key_S);
154 saveAnalysisAsAction = new QAction(tr("Save Analysis &As"), this);
155 connect(saveAnalysisAsAction, SIGNAL(triggered()),
156 this, SLOT(saveAnalysisAs()));
157 saveAnalysisAsAction->setIcon(QIcon(":/icons/filesave.png"));
159 editMotifsAction = new QAction(tr("Edit Motifs"), this);;
160 connect(editMotifsAction, SIGNAL(triggered()), this, SLOT(editMotifs()));
162 loadMotifListAction = new QAction(tr("Open Motif List"), this);
163 connect(loadMotifListAction, SIGNAL(triggered()),
164 this, SLOT(loadMotifList()));
165 loadMotifListAction->setIcon(QIcon(":/icons/fileopen.png"));
167 loadMupaAction = new QAction(tr("Create Analysis from File"), this);
168 connect(loadMupaAction, SIGNAL(triggered()),
169 this, SLOT(loadMupa()));
170 loadMupaAction->setIcon(QIcon(":/icons/fileopen.png"));
172 loadSavedAnalysisAction = new QAction(tr("&Open Existing &Analysis"), this);
173 connect(loadSavedAnalysisAction, SIGNAL(triggered()),
174 this, SLOT(loadSavedAnalysis()));
175 loadSavedAnalysisAction->setIcon(QIcon(":/icons/fileopen.png"));
176 loadSavedAnalysisAction->setShortcut(Qt::CTRL | Qt::Key_O);
178 mussaManualAssistantAction = new QAction(tr("Mussagl Manual..."), this);
179 mussaManualAssistantAction->setIcon(QIcon(":/icons/contents.png"));
180 connect(mussaManualAssistantAction, SIGNAL(triggered()),
181 this, SLOT(showManual()));
183 newMussaWindowAction = new QAction(tr("&New Mussa Window"), this);
184 newMussaWindowAction->setStatusTip("open another mussa window to allow comparing results");
185 connect(newMussaWindowAction, SIGNAL(triggered()),
186 this, SLOT(newMussaWindow()));
187 newMussaWindowAction->setIcon(QIcon(":/icons/window_new.png"));
189 saveMotifListAction = new QAction(tr("Save Motifs"), this);
190 connect(saveMotifListAction, SIGNAL(triggered()),
191 this, SLOT(saveMotifList()));
192 saveMotifListAction->setIcon(QIcon(":/icons/filesave.png"));
194 showMussaViewToolbarAction = new QAction(tr("Show Toolbar"), this);
195 connect(showMussaViewToolbarAction, SIGNAL(triggered()),
196 this, SLOT(showMussaToolbar()));
197 showMussaViewToolbarAction->setCheckable(true);
198 showMussaViewToolbarAction->setChecked(true);
200 toggleMotifsAction = new QAction(tr("Toggle Motifs"), this);
201 connect(toggleMotifsAction, SIGNAL(triggered()),
202 this, SLOT(toggleMotifs()));
203 toggleMotifsAction->setCheckable(true);
204 toggleMotifsAction->setIcon(QIcon(":/icons/motif_icon.png"));
205 toggleMotifsAction->setWhatsThis(tr("Toggle motif annotations on/off\n\n"
206 "You can load motif annotations via "
207 "'File->Load Motif List' menu option."));
209 //Save pixel map action
210 saveBrowserPixmapAction = new QAction(tr("Save to image..."), this);
211 connect(saveBrowserPixmapAction, (SIGNAL(triggered())),
212 browser, SLOT(promptSaveBrowserPixmap()));
213 saveBrowserPixmapAction->setIcon(QIcon(":/icons/image2.png"));
215 viewMussaAlignmentAction = new QAction(tr("View sequence alignment"), this);
216 connect(viewMussaAlignmentAction, SIGNAL(triggered()),
217 this, SLOT(viewMussaAlignment() ));
218 viewMussaAlignmentAction->setWhatsThis(tr("Create a zoomed in window "
219 "showing alignment of the seqcomp "
222 whatsThisAction = QWhatsThis::createAction(this);
223 whatsThisAction->setIcon(QIcon(":/icons/help.png"));
228 void MussaWindow::closeEvent(QCloseEvent *event)
230 if(isClearingAnalysisSafe()) {
237 void MussaWindow::setupMainMenu()
239 // we need to run setupActions first
240 assert (closeAction != 0);
242 QMenu *newMenu = menuBar()->addMenu(tr("&File"));
244 newMenu->addAction(newMussaWindowAction);
245 newMenu->addAction(createNewAnalysisAction);
246 newMenu->addAction(loadMupaAction);
247 newMenu->addAction(loadSavedAnalysisAction);
248 newMenu->addAction(saveAnalysisAction);
249 newMenu->addAction(saveAnalysisAsAction);
250 newMenu->addSeparator();
251 newMenu->addAction(loadMotifListAction);
252 newMenu->addAction(saveMotifListAction);
253 newMenu->addSeparator();
254 newMenu->addAction(saveBrowserPixmapAction);
255 newMenu->addSeparator();
256 newMenu->addAction(closeAction);
258 newMenu = menuBar()->addMenu(tr("&Edit"));
259 newMenu->addAction(editMotifsAction);
260 newMenu->addAction(browser->getCopySelectedSequenceAsStringAction());
261 newMenu->addAction(browser->getCopySelectedSequenceAsFastaAction());
262 newMenu->addAction(createSubAnalysisAction);
264 newMenu = menuBar()->addMenu(tr("&View"));
265 newMenu->addAction(viewMussaAlignmentAction);
266 newMenu->addAction(showMussaViewToolbarAction);
268 newMenu = menuBar()->addMenu(tr("&Help"));
269 newMenu->addAction(mussaManualAssistantAction);
270 newMenu->addAction(whatsThisAction);
271 newMenu->addSeparator();
272 newMenu->addAction(aboutAction);
274 // add some extra features to the context menu
275 QMenu *popupMenu = browser->getPopupMenu();
277 popupMenu->addAction(viewMussaAlignmentAction);
278 popupMenu->addAction(createSubAnalysisAction);
282 void MussaWindow::setupAssistant()
284 #if defined(QT_QTASSISTANT_FOUND)
285 QStringList manualAssistantArgs;
286 manualAssistantArgs = QStringList();
287 manualAssistantArgs << "-profile" << "./doc/manual/mussagl_manual.adp";
288 manualAssistant = new QAssistantClient("assistant", this);
289 manualAssistant->setArguments(manualAssistantArgs);
290 connect(manualAssistant, SIGNAL(error(QString)),
291 this, SLOT(assistantError(QString)));
295 void MussaWindow::about()
298 msg += "Welcome to Multiple Species Sequence Analysis\n";
299 msg += "(c) 2005-2006 California Institute of Technology\n";
300 msg += "Diane Trout, Tristan De Buysscher, Brandon King\n";
302 msg += mussa_version;
305 msg += (char *)glGetString(GL_VERSION);
307 QMessageBox::about(this, tr("About mussa"), msg);
310 void MussaWindow::clear()
312 aligned_windows.clear();
316 void MussaWindow::createNewAnalysis()
319 // ideally we should open a new window if there's an analysis
320 // but this should work for the moment.
321 if (not isClearingAnalysisSafe()) return;
323 if (setup_analysis_dialog->exec()) {
324 setAnalysis(setup_analysis_dialog->getMussa());
326 } catch(mussa_error e) {
327 QString msg(e.what());
328 QMessageBox::warning(this, tr("Create New Analysis"), msg);
332 void MussaWindow::createSubAnalysis()
334 list<SequenceLocation> result;
335 SequenceLocationModel& model = subanalysis_window->getModel();
336 browser->copySelectedTracksAsSeqLocation(result);
337 for(list<SequenceLocation>::iterator result_itor = result.begin();
338 result_itor != result.end();
341 model.push_back(*result_itor);
344 if (not subanalysis_window->isVisible()) {
345 subanalysis_window->show();
349 void MussaWindow::saveAnalysis()
351 // if we've got an analysis
352 if (analysis and not analysis->empty()) {
353 // if it doesn't have a name we need to pick one
354 if (analysis->get_analysis_path().empty()) {
357 // if we've got a name, has it changed any?
358 // this doesn't work when a sequence changes something (like its
360 //else if (analysis->is_dirty())
363 } catch (std::exception e) {
364 QMessageBox::critical(this,
365 tr("Mussa Save Error"),
367 QMessageBox::Ok, 0, 0);
374 void MussaWindow::saveAnalysisAs()
376 std::auto_ptr<QFileDialog> dialog(new QFileDialog(this));
377 dialog->setAcceptMode(QFileDialog::AcceptSave);
378 dialog->setFileMode(QFileDialog::AnyFile);
379 dialog->setDirectory(*default_dir);
381 QStringList fileNames;
382 if (not dialog->exec()) {
385 fileNames = dialog->selectedFiles();
387 if (fileNames.size() != 1) {
391 fs::path save_path(fileNames[0].toStdString(), fs::native);
392 // do you want to overwrite?
393 if (fs::exists(save_path) and
394 QMessageBox::question(
396 tr("Overwrite File? -- Mussa"),
397 tr("A file called %1 already exists"
398 "do you want to overwrite it?")
400 tr("&Yes"), tr("&No"),
405 analysis->save(save_path);
406 fs::path normalized_path = (save_path / "..").normalize();
407 default_dir->setPath(normalized_path.native_directory_string().c_str());
408 } catch (std::exception e) {
409 QMessageBox::critical(this,
410 tr("Mussa Save Error"),
412 QMessageBox::Ok, 0, 0);
416 bool MussaWindow::isClearingAnalysisSafe()
418 if (analysis and not analysis->empty() and analysis->is_dirty()) {
419 switch (QMessageBox::question(
421 tr("Save Unsaved Changes -- Mussa"),
422 tr("There are unsaved changes,\ndo you want to save?"),
423 tr("&Yes"), tr("&No"), tr("&Cancel"),
438 throw runtime_error("isClearingAnalysis QMesageBox failure");
441 // if we're here we've been saved and can replace
445 void MussaWindow::editMotifs()
447 if (motif_editor != 0) {
448 motif_editor->hide();
451 motif_editor = new MotifEditor(analysis);
452 connect(motif_editor, SIGNAL(changedMotifs()),
453 this, SLOT(updateAnnotations()));
454 motif_editor->show();
457 void MussaWindow::loadMotifList()
459 QString caption("Mussa Load Motifs");
460 QString filter("Motif list(*.txt *.mtl)");
461 QString path = QFileDialog::getOpenFileName(this,
463 default_dir->absolutePath(),
468 // try to load safely
470 fs::path converted_path(path.toStdString(), fs::native);
471 analysis->load_motifs(converted_path);
472 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
473 emit changedMotifs();
474 } catch (std::exception e) {
475 QString msg("Unable to load ");
479 QMessageBox::warning(this, caption, msg);
483 void MussaWindow::saveMotifList()
485 QString caption("Mussa Save Motifs");
486 QString filter("Motif list(*.txt *.mtl)");
487 QString path = QFileDialog::getSaveFileName(this,
489 default_dir->absolutePath(),
494 // try to load safely
496 fs::path converted_path(path.toStdString(), fs::native);
497 if (fs::extension(converted_path).size() == 0) {
498 // no extension, so add one
499 fs::path base_path = converted_path.branch_path();
500 fs::path filename(converted_path.leaf() + ".mtl", fs::native);
501 converted_path = base_path / filename;
503 analysis->save_motifs(converted_path);
504 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
505 } catch (std::exception e) {
506 QString msg("Unable to save ");
510 QMessageBox::warning(this, caption, msg);
513 void MussaWindow::loadMupa()
515 QString caption("Load a mussa parameter file");
516 QString filter("Mussa Parameters (*.mupa)");
517 QString mupa_path = QFileDialog::getOpenFileName(
520 default_dir->absolutePath(),
524 if (mupa_path.isNull())
526 // try to load safely
528 // ideally we should open a new window if there's an analysis
529 // but this should work for the moment.
530 if (not isClearingAnalysisSafe()) return;
532 MussaRef m(new Mussa);
533 fs::path converted_path(mupa_path.toStdString(), fs::native);
534 connect(m.get(), SIGNAL(progress(const std::string&, int, int)),
535 this, SLOT(updateProgress(const std::string&, int, int)));
536 m->load_mupa_file(converted_path);
540 // grab the path ignoring the mupa file portion
541 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
542 } catch (mussa_load_error e) {
543 QString msg("Unable to load ");
547 QMessageBox::warning(this, "Load Parameter", msg);
549 assert (analysis != 0);
552 void MussaWindow::loadSavedAnalysis()
554 QString caption("Load a previously run analysis");
555 QString muway_dir = QFileDialog::getExistingDirectory(
558 default_dir->absolutePath()
561 if (muway_dir.isNull())
563 // try to safely load
565 // ideally we should open a new window if there's an analysis
566 // but this should work for the moment.
567 if (not isClearingAnalysisSafe()) return;
569 MussaRef m(new Mussa);
570 fs::path converted_path(muway_dir.toStdString(), fs::native);
571 connect(m.get(), SIGNAL(progress(const std::string&, int, int)),
572 this, SLOT(updateProgress(const std::string&, int, int)));
573 m->load(converted_path);
574 // only switch mussas if we loaded without error
575 if (analysis->empty()) {
576 // our current window is empty so load and replace.
579 default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
581 MussaWindow *win = new MussaWindow(m);
583 win->default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
586 } catch (boost::filesystem::filesystem_error e) {
587 QString msg("Unable to load ");
591 QMessageBox::warning(this, "Load Parameter", msg);
592 } catch (mussa_load_error e) {
593 QString msg("Unable to load ");
597 QMessageBox::warning(this, "Load Parameter", msg);
599 assert (analysis != 0);
602 void MussaWindow::newMussaWindow()
604 MussaRef a(new Mussa);
605 MussaWindow *win = new MussaWindow(a);
606 win->default_dir = default_dir;
610 void MussaWindow::setSoftThreshold(int value)
612 if (analysis->get_soft_threshold() != value) {
613 threshold->setEnabled( false );
614 analysis->set_soft_threshold(value);
618 threshold->setEnabled( true );
622 void MussaWindow::showMussaToolbar()
624 if (mussaViewTB->isVisible())
630 void MussaWindow::showBasePairsCopied(size_t bp_copied)
632 QString msg("Copied ");
634 num.setNum(bp_copied);
635 msg += num + " base pairs";
636 statusBar()->showMessage(msg, 5000);
640 void MussaWindow::toggleMotifs()
645 void MussaWindow::showManual()
647 #if defined(QT_QTASSISTANT_FOUND)
648 if (manualAssistant) {
649 manualAssistant->openAssistant();
651 QMessageBox::warning(this,
652 tr("Mussa Help Error"),
653 tr("QtAssistant not setup correctly"),
655 QMessageBox::NoButton,
656 QMessageBox::NoButton);
659 QUrl manual_url("http://woldlab.caltech.edu/~king/mussagl_manual/");
660 if (not QDesktopServices::openUrl(manual_url)) {
661 QMessageBox::warning(this,
662 tr("Mussa Help Error"),
663 tr("Unable to launch webbrowser"),
665 QMessageBox::NoButton,
666 QMessageBox::NoButton);
668 #endif //QT_QTASSISTANT_FOUND
671 void MussaWindow::assistantError(QString message)
673 //std::cout << "QAssistantError: " << message.toStdString() << "\n";
674 QMessageBox::warning ( this, "Warning: Mussagl Manual", message,
676 QMessageBox::NoButton);
679 void MussaWindow::NotImplementedBox()
681 QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
684 void MussaWindow::viewMussaAlignment()
686 const set<int>& selected_paths = browser->selectedPaths();
687 if (selected_paths.size() == 0 ) {
688 QMessageBox::warning(this,
689 QObject::tr("mussa"),
690 QObject::tr("you should probably select some paths "
693 MussaAlignedWindowRef ma_win(
694 new MussaAlignedWindow(analysis, default_dir, selected_paths, subanalysis_window)
697 aligned_windows.push_back(ma_win);
698 connect(this, SIGNAL(changedAnnotations()),
699 aligned_windows.back().get(), SLOT(update()));
700 aligned_windows.back()->show();
704 void MussaWindow::updateAnalysis()
706 const Mussa::vector_sequence_type& seqs = analysis->sequences();
707 browser->setSequences(seqs, analysis->colorMapper());
708 assert(browser->sequences().size() == analysis->size());
710 // setRange eventually emits something that causes updateLinks to be called
711 // but it's possible for us to not have had a chance to set out sequences
713 threshold->setRange(analysis->get_threshold(),analysis->get_window());
716 zoom->setValue(browser->zoom());
719 void MussaWindow::updateAnnotations()
721 // motifs were changed in the sequences by
722 // Mussa::update_sequences_motifs
723 emit changedAnnotations();
727 void MussaWindow::updateLinks()
729 if(browser->sequences().size() == 0) {
730 // we don't have any sequences load so we have no business setting links
734 browser->clear_links();
735 bool reversed = false;
736 const NwayPaths& nway = analysis->paths();
738 typedef list<ConservedPath> conserved_paths;
739 typedef conserved_paths::const_iterator const_conserved_paths_itor;
740 for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
741 path_itor != nway.refined_pathz.end();
744 // since we were drawing to the start of a window, and opengl lines
745 // are centered around the two connecting points our lines were slightly
746 // offset. the idea of window_offset is to adjust them to the
747 // right for forward compliment or left for reverse compliment
748 // FIXME: figure out how to unit test these computations
749 //GLfloat window_offset = (path_itor->window_size)/2.0;
751 size_t track_len = path_itor->track_indexes.size();
752 vector<int> normalized_path;
753 normalized_path.reserve(track_len);
754 vector<bool> rc_flags(false, track_len);
755 for (size_t track_i=0; track_i != track_len; ++track_i)
757 int x = path_itor->track_indexes[track_i];
758 // at some point when we modify the pathz data structure to keep
759 // track of the score we can put grab the depth here.
761 // are we reverse complimented?
769 normalized_path.push_back(x);
770 rc_flags.push_back(reversed);
772 browser->link(normalized_path, rc_flags, path_itor->window_size);
778 MussaWindow::updateProgress(const string& description, int current, int max)
781 if (current == max) {
782 if (progress_dialog != 0) {
783 progress_dialog->hide();
784 delete progress_dialog;
788 // if we're starting, create the dialog
789 if (progress_dialog == 0) {
790 QString desc(description.c_str());
791 QString cancel("Cancel");
792 progress_dialog = new QProgressDialog(desc, cancel, current, max, this);
793 progress_dialog->show();
795 // just update the dialog
796 progress_dialog->setValue(current);
799 qApp->processEvents();
802 void MussaWindow::updateAnalysisModified(bool is_modified)
804 setWindowModified(is_modified);
807 void MussaWindow::updateTitle()
810 QString title(analysis->get_title().c_str());
812 setWindowTitle(title);