1 #include "py/python.hpp"
2 #include "qui/MussaWindow.hpp"
3 #include "mussa_exceptions.hpp"
6 #include <QApplication>
7 #include <QAssistantClient>
10 #include <QHBoxLayout>
13 #include <QMessageBox>
17 #include <QStringList>
24 #include <boost/filesystem/path.hpp>
25 #include <boost/filesystem/operations.hpp>
26 namespace fs = boost::filesystem;
27 #include <boost/bind.hpp>
31 MussaWindow::MussaWindow(Mussa *analysis_, QWidget *parent) :
35 setup_analysis_dialog(this),
37 mussaViewTB("Path Views"),
43 createNewAnalysisAction(0),
44 createSubAnalysisAction(0),
46 loadMotifListAction(0),
48 loadSavedAnalysisAction(0),
49 mussaManualAssistantAction(0),
50 newMussaWindowAction(0),
51 saveMotifListAction(0),
52 showMussaViewToolbarAction(0),
53 toggleMotifsAction(0),
54 saveBrowserPixmapAction(0),
56 viewMussaAlignmentAction(0),
63 //This next setWhatsThis function prevents
64 // a segfault when using WhatsThis feature with
66 //scene->setWhatsThis(tr("Mussa in OpenGL!"));
67 setCentralWidget(&browser);
68 // well updatePosition isn't quite right as we really just need
70 connect(this, SIGNAL(changedAnnotations()), &browser, SLOT(update()));
72 //mussaViewTB.addAction(toggleMotifsAction);
73 mussaViewTB.addWidget(&zoom);
75 connect(&zoom, SIGNAL(valueChanged(double)),
76 &browser, SLOT(setZoom(double)));
78 // threshold range is set in updateAnalysis
80 //scene->setClipPlane(20);
81 // FIXME: for when we get the paths drawn at the appropriate depth
82 //connect(&threshold, SIGNAL(thresholdChanged(int)),
83 // this, SLOT(setClipPlane(int)));
84 connect(&threshold, SIGNAL(thresholdChanged(int)),
85 this, SLOT(setSoftThreshold(int)));
86 mussaViewTB.addWidget(&threshold);
88 addToolBar(&mussaViewTB);
90 statusBar()->showMessage("Welcome to mussa", 2000);
91 connect(analysis, SIGNAL(progress(const std::string&, int, int)),
92 this, SLOT(updateProgress(const std::string&, int, int)));
96 void MussaWindow::setAnalysis(Mussa *new_analysis)
98 if (new_analysis != 0) {
99 // only switch mussas if we loaded without error
102 analysis = new_analysis;
103 setWindowTitle(analysis->get_name().c_str());
108 void MussaWindow::setupActions()
110 // we really don't want to run this more than once.
111 assert (closeAction == 0);
113 // the ever popular about box
114 aboutAction = new QAction(tr("&About"), this);
115 connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
116 aboutAction->setIcon(QIcon(":/icons/info.png"));
119 closeAction = new QAction(tr("&Close"), this);
120 closeAction->setStatusTip(tr("Close this window"));
121 connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
122 closeAction->setIcon(QIcon(":/icons/exit.png"));
124 createNewAnalysisAction = new QAction(tr("Create Analysis"), this);
125 connect(createNewAnalysisAction, SIGNAL(triggered()),
126 this, SLOT(createNewAnalysis()));
127 createNewAnalysisAction->setIcon(QIcon(":/icons/filenew.png"));
129 createSubAnalysisAction = new QAction(tr("Add to Subanalysis"), this);
130 connect(createSubAnalysisAction, SIGNAL(triggered()),
131 this, SLOT(createSubAnalysis()));
133 saveAnalysisAction = new QAction(tr("Save Analysis"), this);
134 connect(saveAnalysisAction, SIGNAL(triggered()),
135 this, SLOT(saveAnalysis()));
137 editMotifsAction = new QAction(tr("Edit Motifs"), this);;
138 connect(editMotifsAction, SIGNAL(triggered()), this, SLOT(editMotifs()));
140 loadMotifListAction = new QAction(tr("Load Motif List"), this);
141 connect(loadMotifListAction, SIGNAL(triggered()),
142 this, SLOT(loadMotifList()));
143 loadMotifListAction->setIcon(QIcon(":/icons/fileopen.png"));
145 loadMupaAction = new QAction(tr("Create Analysis from File"), this);
146 connect(loadMupaAction, SIGNAL(triggered()),
147 this, SLOT(loadMupa()));
148 loadMupaAction->setIcon(QIcon(":/icons/fileopen.png"));
150 loadSavedAnalysisAction = new QAction(tr("Load Existing &Analysis"), this);
151 connect(loadSavedAnalysisAction, SIGNAL(triggered()),
152 this, SLOT(loadSavedAnalysis()));
153 loadSavedAnalysisAction->setIcon(QIcon(":/icons/fileopen.png"));
155 mussaManualAssistantAction = new QAction(tr("Mussagl Manual..."), this);
156 mussaManualAssistantAction->setIcon(QIcon(":/icons/contents.png"));
157 connect(mussaManualAssistantAction, SIGNAL(triggered()),
158 this, SLOT(showManual()));
160 newMussaWindowAction = new QAction(tr("&New Mussa Window"), this);
161 newMussaWindowAction->setStatusTip("open another mussa window to allow comparing results");
162 connect(newMussaWindowAction, SIGNAL(triggered()),
163 this, SLOT(newMussaWindow()));
164 newMussaWindowAction->setIcon(QIcon(":/icons/window_new.png"));
166 saveMotifListAction = new QAction(tr("Save Motifs"), this);
167 connect(saveMotifListAction, SIGNAL(triggered()),
168 this, SLOT(saveMotifList()));
169 saveMotifListAction->setIcon(QIcon(":/icons/filesave.png"));
171 showMussaViewToolbarAction = new QAction(tr("Show Toolbar"), this);
172 connect(showMussaViewToolbarAction, SIGNAL(triggered()),
173 this, SLOT(showMussaToolbar()));
174 showMussaViewToolbarAction->setCheckable(true);
175 showMussaViewToolbarAction->setChecked(true);
177 toggleMotifsAction = new QAction(tr("Toggle Motifs"), this);
178 connect(toggleMotifsAction, SIGNAL(triggered()),
179 this, SLOT(toggleMotifs()));
180 toggleMotifsAction->setCheckable(true);
181 toggleMotifsAction->setIcon(QIcon(":/icons/motif_icon.png"));
182 toggleMotifsAction->setWhatsThis(tr("Toggle motif annotations on/off\n\n"
183 "You can load motif annotations via "
184 "'File->Load Motif List' menu option."));
186 //Save pixel map action
187 saveBrowserPixmapAction = new QAction(tr("Save to image..."), this);
188 connect(saveBrowserPixmapAction, (SIGNAL(triggered())),
189 &browser, SLOT(promptSaveBrowserPixmap()));
190 saveBrowserPixmapAction->setIcon(QIcon(":/icons/image2.png"));
192 viewMussaAlignmentAction = new QAction(tr("View sequence alignment"), this);
193 connect(viewMussaAlignmentAction, SIGNAL(triggered()),
194 this, SLOT(viewMussaAlignment() ));
195 viewMussaAlignmentAction->setWhatsThis(tr("Create a zoomed in window "
196 "showing alignment of the seqcomp "
199 whatsThisAction = QWhatsThis::createAction(this);
200 whatsThisAction->setIcon(QIcon(":/icons/help.png"));
205 void MussaWindow::setupMainMenu()
207 // we need to run setupActions first
208 assert (closeAction != 0);
210 QMenu *newMenu = menuBar()->addMenu(tr("&File"));
212 newMenu->addAction(newMussaWindowAction);
213 newMenu->addAction(createNewAnalysisAction);
214 newMenu->addAction(loadMupaAction);
215 newMenu->addAction(loadSavedAnalysisAction);
216 newMenu->addAction(saveAnalysisAction);
217 newMenu->addSeparator();
218 newMenu->addAction(loadMotifListAction);
219 newMenu->addAction(saveMotifListAction);
220 newMenu->addSeparator();
221 newMenu->addAction(saveBrowserPixmapAction);
222 newMenu->addSeparator();
223 newMenu->addAction(closeAction);
225 newMenu = menuBar()->addMenu(tr("&Edit"));
226 newMenu->addAction(editMotifsAction);
227 newMenu->addAction(&browser.getCopySelectedSequenceAsFastaAction());
228 newMenu->addAction(createSubAnalysisAction);
230 newMenu = menuBar()->addMenu(tr("&View"));
231 newMenu->addAction(viewMussaAlignmentAction);
232 newMenu->addAction(showMussaViewToolbarAction);
234 newMenu = menuBar()->addMenu(tr("&Help"));
235 newMenu->addAction(mussaManualAssistantAction);
236 newMenu->addAction(whatsThisAction);
237 newMenu->addSeparator();
238 newMenu->addAction(aboutAction);
240 // add some extra features to the context menu
241 QMenu& popupMenu = browser.getPopupMenu();
242 popupMenu.addAction(viewMussaAlignmentAction);
243 popupMenu.addAction(createSubAnalysisAction);
246 void MussaWindow::setupAssistant()
248 #if defined(QT_ASSISTANT_FOUND)
249 QStringList manualAssistantArgs;
250 manualAssistantArgs = QStringList();
251 manualAssistantArgs << "-profile" << "./doc/manual/mussagl_manual.adp";
252 manualAssistant = new QAssistantClient("assistant", this);
253 manualAssistant->setArguments(manualAssistantArgs);
254 connect(manualAssistant, SIGNAL(error(QString)),
255 this, SLOT(assistantError(QString)));
259 void MussaWindow::about()
261 QString msg("Welcome to Multiple Species Sequence Analysis\n"
262 "(c) 2005-2006 California Institute of Technology\n"
263 "Tristan De Buysscher, Diane Trout\n");
266 msg += QDir::currentPath();
269 msg += (char *)glGetString(GL_VERSION);
271 QMessageBox::about(this, tr("About mussa"), msg);
274 void MussaWindow::clear()
276 aligned_windows.clear();
280 void MussaWindow::createNewAnalysis()
283 if (setup_analysis_dialog.exec()) {
285 m = setup_analysis_dialog.getMussa();
288 std::cout << "New mussa exp. aborted!\n";
290 } catch(mussa_error e) {
291 QString msg(e.what());
292 QMessageBox::warning(this, tr("Create New Analysis"), msg);
296 void MussaWindow::createSubAnalysis()
298 list<SequenceLocation> result;
299 SequenceLocationModel& model = subanalysis_window.getModel();
300 browser.copySelectedTracksAsSeqLocation(result);
301 for(list<SequenceLocation>::iterator result_itor = result.begin();
302 result_itor != result.end();
305 model.push_back(*result_itor);
308 if (not subanalysis_window.isVisible()) {
309 subanalysis_window.show();
313 void MussaWindow::saveAnalysis()
315 std::auto_ptr<QFileDialog> dialog(new QFileDialog(this));
316 dialog->setAcceptMode(QFileDialog::AcceptSave);
317 dialog->setFileMode(QFileDialog::AnyFile);
319 QStringList fileNames;
320 if (not dialog->exec()) {
323 fileNames = dialog->selectedFiles();
325 if (fileNames.size() != 1) {
329 fs::path save_path(fileNames[0].toStdString());
330 // do you want to overwrite?
331 if (fs::exists(save_path) and
332 QMessageBox::question(
334 tr("Overwrite File? -- Mussa"),
335 tr("A file called %1 already exists"
336 "do you want to overwrite it?")
338 tr("&Yes"), tr("&No"),
343 analysis->save(save_path);
346 void MussaWindow::editMotifs()
348 if (motif_editor != 0) {
349 motif_editor->hide();
352 motif_editor = new MotifEditor(analysis);
353 connect(motif_editor, SIGNAL(changedMotifs()),
354 this, SLOT(updateAnnotations()));
355 motif_editor->show();
358 void MussaWindow::loadMotifList()
360 QString caption("Load a motif list");
361 QString filter("Motif list(*.txt *.mtl)");
362 QString path = QFileDialog::getOpenFileName(this,
369 // try to load safely
371 fs::path converted_path(path.toStdString(), fs::native);
372 analysis->load_motifs(converted_path);
373 } catch (runtime_error e) {
374 QString msg("Unable to load ");
378 QMessageBox::warning(this, "Load Motifs", msg);
380 assert (analysis != 0);
383 void MussaWindow::saveMotifList()
388 void MussaWindow::loadMupa()
390 QString caption("Load a mussa parameter file");
391 QString filter("Mussa Parameters (*.mupa)");
392 QString mupa_path = QFileDialog::getOpenFileName(this,
397 if (mupa_path.isNull())
399 // try to load safely
401 Mussa *m = new Mussa;
402 fs::path converted_path(mupa_path.toStdString(), fs::native);
403 connect(m, SIGNAL(progress(const std::string&, int, int)),
404 this, SLOT(updateProgress(const std::string&, int, int)));
405 m->load_mupa_file(converted_path);
408 setWindowTitle(converted_path.native_file_string().c_str());
409 } catch (mussa_load_error e) {
410 QString msg("Unable to load ");
414 QMessageBox::warning(this, "Load Parameter", msg);
416 assert (analysis != 0);
419 void MussaWindow::loadSavedAnalysis()
421 QString caption("Load a previously run analysis");
422 QString muway_dir = QFileDialog::getExistingDirectory(this,
424 QDir::currentPath());
426 if (muway_dir.isNull())
428 // try to safely load
430 Mussa *m = new Mussa;
431 fs::path converted_path(muway_dir.toStdString(), fs::native);
432 connect(m, SIGNAL(progress(const std::string&, int, int)),
433 this, SLOT(updateProgress(const std::string&, int, int)));
434 m->load(converted_path);
435 // only switch mussas if we loaded without error
437 setWindowTitle(converted_path.native_file_string().c_str());
438 } catch (mussa_load_error e) {
439 QString msg("Unable to load ");
443 QMessageBox::warning(this, "Load Parameter", msg);
445 assert (analysis != 0);
448 void MussaWindow::newMussaWindow()
450 Mussa *a = new Mussa();
451 MussaWindow *win = new MussaWindow(a);
455 void MussaWindow::setSoftThreshold(int threshold)
457 if (analysis->get_soft_threshold() != threshold) {
458 analysis->set_soft_threshold(threshold);
465 void MussaWindow::showMussaToolbar()
467 if (mussaViewTB.isVisible())
473 void MussaWindow::toggleMotifs()
478 void MussaWindow::showManual()
480 #if QT_QTASSISTANT_FOUND
481 manualAssistant->openAssistant();
484 boost::python::object webopen = get_py()["webbrowser.open"];
485 webopen("http://woldlab.caltech.edu/~king/mussagl_manual/");
486 } catch( boost::python::error_already_set ) {
489 #endif //QT_QTASSISTANT_FOUND
492 void MussaWindow::assistantError(QString message)
494 //std::cout << "QAssistantError: " << message.toStdString() << "\n";
495 QMessageBox::warning ( this, "Warning: Mussagl Manual", message,
497 QMessageBox::NoButton);
500 void MussaWindow::NotImplementedBox()
502 QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
505 void MussaWindow::viewMussaAlignment()
507 const set<int>& selected_paths = browser.selectedPaths();
508 if (selected_paths.size() == 0 ) {
509 QMessageBox::warning(this,
510 QObject::tr("mussa"),
511 QObject::tr("you should probably select some paths "
514 boost::shared_ptr<MussaAlignedWindow> ma_win(
515 new MussaAlignedWindow(*analysis, selected_paths)
518 aligned_windows.push_back(ma_win);
519 connect(this, SIGNAL(changedAnnotations()),
520 aligned_windows.back().get(), SLOT(update()));
521 aligned_windows.back()->show();
525 void MussaWindow::updateAnalysis()
527 threshold.setRange(analysis->get_threshold(),analysis->get_window());
529 const Mussa::vector_sequence_type& seqs = analysis->sequences();
530 browser.setSequences(seqs, analysis->colorMapper());
533 zoom.setValue(browser.zoom());
536 void MussaWindow::updateAnnotations()
538 // motifs were changed in the sequences by
539 // Mussa::update_sequences_motifs
540 emit changedAnnotations();
544 void MussaWindow::updateLinks()
546 browser.clear_links();
547 bool reversed = false;
548 const NwayPaths& nway = analysis->paths();
550 typedef list<ConservedPath> conserved_paths;
551 typedef conserved_paths::const_iterator const_conserved_paths_itor;
552 for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
553 path_itor != nway.refined_pathz.end();
556 // since we were drawing to the start of a window, and opengl lines
557 // are centered around the two connecting points our lines were slightly
558 // offset. the idea of window_offset is to adjust them to the
559 // right for forward compliment or left for reverse compliment
560 // FIXME: figure out how to unit test these computations
561 //GLfloat window_offset = (path_itor->window_size)/2.0;
563 size_t track_len = path_itor->track_indexes.size();
564 vector<int> normalized_path;
565 normalized_path.reserve(track_len);
566 vector<bool> rc_flags(false, track_len);
567 for (size_t track_i=0; track_i != track_len; ++track_i)
569 int x = path_itor->track_indexes[track_i];
570 // at some point when we modify the pathz data structure to keep
571 // track of the score we can put grab the depth here.
573 // are we reverse complimented?
581 normalized_path.push_back(x);
582 rc_flags.push_back(reversed);
584 browser.link(normalized_path, rc_flags, path_itor->window_size);
590 MussaWindow::updateProgress(const string& description, int current, int max)
593 if (current == max) {
594 if (progress_dialog != 0) {
595 progress_dialog->hide();
596 delete progress_dialog;
600 // if we're starting, create the dialog
601 if (progress_dialog == 0) {
602 QString desc(description.c_str());
603 QString cancel("Cancel");
604 progress_dialog = new QProgressDialog(desc, cancel, current, max, this);
605 progress_dialog->show();
607 // just update the dialog
608 progress_dialog->setValue(current);
611 qApp->processEvents();