Disabled code to try and get bundle path on OS X
[mussa.git] / qui / MussaWindow.cpp
1 #include <QAction>
2 #include <QApplication>
3 #include <QAssistantClient>
4 #include <QDir>
5 #include <QFileDialog>
6 #include <QHBoxLayout>
7 #include <QIcon>
8 #include <QMenuBar>
9 #include <QMessageBox>
10 #include <QScrollBar>
11 #include <QStatusBar>
12 #include <QString>
13 #include <QStringList>
14 #include <QWhatsThis>
15
16 #if defined(Q_WS_MAC)
17 #include <CoreFoundation/CoreFoundation.h>
18 #endif 
19
20 #include "qui/MussaWindow.hpp"
21 #include "mussa_exceptions.hpp"
22
23 #include <iostream>
24
25 #include <boost/filesystem/path.hpp>
26 namespace fs = boost::filesystem;
27
28 using namespace std;
29
30 MussaWindow::MussaWindow(Mussa *analysis_, QWidget *parent) :
31   QMainWindow(parent),
32   analysis(analysis_),
33   motif_editor(0),
34   setup_analysis_dialog(this),
35   browser(this),
36   mussaViewTB("Path Views"),
37   zoom(),
38   threshold(),
39   progress_dialog(0),
40   aboutAction(0),
41   copySelectedSequenceAsFastaAction(0),
42   closeAction(0),
43   createNewAnalysisAction(0),
44   createSubAnalysisAction(0),
45   editMotifsAction(0),
46   loadMotifListAction(0),
47   loadMupaAction(0),
48   loadSavedAnalysisAction(0),
49   mussaManualAssistantAction(0),
50   newMussaWindowAction(0),
51   saveMotifListAction(0),
52   showMussaViewToolbarAction(0),
53   toggleMotifsAction(0),
54   saveBrowserPixmapAction(0),
55   whatsThisAction(0),
56   viewMussaAlignmentAction(0),
57   manualAssistant(0)
58 {
59
60   setupActions();
61   setupMainMenu();
62   setupAssistant();
63
64   //This next setWhatsThis function prevents
65   // a segfault when using WhatsThis feature with 
66   // opengl widget.
67   //scene->setWhatsThis(tr("Mussa in OpenGL!"));
68   setCentralWidget(&browser);
69   // well updatePosition isn't quite right as we really just need
70   // to call update()
71   connect(this, SIGNAL(changedAnnotations()), &browser, SLOT(update()));
72
73   mussaViewTB.addAction(toggleMotifsAction);
74   mussaViewTB.addWidget(&zoom);
75   
76   connect(&zoom, SIGNAL(valueChanged(double)), 
77           &browser, SLOT(setZoom(double)));
78   
79   // threshold range is set in updateAnalysis
80   
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);
88
89   addToolBar(&mussaViewTB);
90
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)));
94   updateAnalysis();
95 }
96
97 MussaWindow::~MussaWindow()
98 {
99   if (analysis != 0) delete analysis;
100   aligned_windows.clear();
101   if (motif_editor != 0) delete motif_editor;
102   if (progress_dialog != 0) delete progress_dialog;
103
104   if (aboutAction != 0) delete aboutAction;
105   if (copySelectedSequenceAsFastaAction != 0) 
106     delete copySelectedSequenceAsFastaAction;
107   if (closeAction != 0) delete closeAction;
108   if (createNewAnalysisAction != 0) delete createNewAnalysisAction;
109   if (createSubAnalysisAction != 0) delete createSubAnalysisAction;
110   if (editMotifsAction != 0) delete editMotifsAction;
111   if (loadMotifListAction != 0) delete loadMotifListAction;
112   if (loadMupaAction != 0) delete loadMupaAction;
113   if (loadSavedAnalysisAction != 0) delete loadSavedAnalysisAction;
114   if (mussaManualAssistantAction != 0) delete mussaManualAssistantAction;
115   if (newMussaWindowAction != 0) delete newMussaWindowAction;
116   if (saveBrowserPixmapAction != 0) delete saveBrowserPixmapAction;
117   if (saveMotifListAction != 0) delete saveMotifListAction;
118   if (showMussaViewToolbarAction != 0) delete showMussaViewToolbarAction;
119   if (toggleMotifsAction != 0) delete toggleMotifsAction;
120   if (whatsThisAction != 0) delete whatsThisAction;
121   if (viewMussaAlignmentAction != 0) delete viewMussaAlignmentAction;
122   
123   if (manualAssistant != 0) delete manualAssistant;
124   
125 }
126
127 void MussaWindow::setAnalysis(Mussa *new_analysis)
128 {
129   if (new_analysis != 0) {
130     // only switch mussas if we loaded without error
131     delete analysis;
132     analysis = new_analysis;
133     setWindowTitle(analysis->get_name().c_str());
134     updateAnalysis();
135   }
136 }
137
138 void MussaWindow::setupActions()
139 {
140   // we really don't want to run this more than once.
141   assert (closeAction == 0);
142
143   // the ever popular about box
144   aboutAction = new QAction(tr("&About"), this);
145   connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
146   aboutAction->setIcon(QIcon(":/icons/info.png"));
147
148   // copy sequence
149   copySelectedSequenceAsFastaAction = new QAction(tr("&Copy As Fasta"), this);
150   connect(copySelectedSequenceAsFastaAction, SIGNAL(triggered()), 
151           &browser, SLOT(copySelectedSequenceAsFasta()));
152
153   // add exit
154   closeAction = new QAction(tr("&Close"), this);
155   closeAction->setStatusTip(tr("Close this window"));
156   connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
157   closeAction->setIcon(QIcon(":/icons/exit.png"));
158   
159   createNewAnalysisAction = new QAction(tr("Create Analysis"), this);
160   connect(createNewAnalysisAction, SIGNAL(triggered()), 
161           this, SLOT(createNewAnalysis()));
162   createNewAnalysisAction->setIcon(QIcon(":/icons/filenew.png"));
163   
164   createSubAnalysisAction = new QAction(tr("Define SubAnalysis"), this);
165   connect(createSubAnalysisAction, SIGNAL(triggered()), 
166           this, SLOT(createSubAnalysis()));
167
168   editMotifsAction = new QAction(tr("Edit Motifs"), this);;
169   connect(editMotifsAction, SIGNAL(triggered()), this, SLOT(editMotifs()));
170   
171   loadMotifListAction = new QAction(tr("Load Motif List"), this);
172   connect(loadMotifListAction, SIGNAL(triggered()), 
173           this, SLOT(loadMotifList()));
174   loadMotifListAction->setIcon(QIcon(":/icons/fileopen.png"));
175   
176   loadMupaAction = new QAction(tr("Create Analysis from File"), this);
177   connect(loadMupaAction, SIGNAL(triggered()), 
178           this, SLOT(loadMupa()));
179   loadMupaAction->setIcon(QIcon(":/icons/fileopen.png"));
180
181   loadSavedAnalysisAction = new QAction(tr("Load Existing &Analysis"), this);
182   connect(loadSavedAnalysisAction, SIGNAL(triggered()), 
183           this, SLOT(loadSavedAnalysis()));
184   loadSavedAnalysisAction->setIcon(QIcon(":/icons/fileopen.png"));
185
186   mussaManualAssistantAction = new QAction(tr("Mussagl Manual..."), this);
187   mussaManualAssistantAction->setIcon(QIcon(":/icons/contents.png"));
188   connect(mussaManualAssistantAction, SIGNAL(triggered()),
189           this, SLOT(showManual()));
190
191   newMussaWindowAction = new QAction(tr("&New Mussa Window"), this);
192   newMussaWindowAction->setStatusTip("open another mussa window to allow comparing results");
193   connect(newMussaWindowAction, SIGNAL(triggered()), 
194           this, SLOT(newMussaWindow()));
195   newMussaWindowAction->setIcon(QIcon(":/icons/window_new.png"));
196
197   saveMotifListAction = new QAction(tr("Save Motifs"), this);
198   connect(saveMotifListAction, SIGNAL(triggered()), 
199           this, SLOT(saveMotifList()));
200   saveMotifListAction->setIcon(QIcon(":/icons/filesave.png"));
201
202   showMussaViewToolbarAction = new QAction(tr("Show Toolbar"), this);
203   connect(showMussaViewToolbarAction, SIGNAL(triggered()), 
204           this, SLOT(showMussaToolbar()));
205   showMussaViewToolbarAction->setCheckable(true);
206   showMussaViewToolbarAction->setChecked(true);
207
208   toggleMotifsAction = new QAction(tr("Toggle Motifs"), this);
209   connect(toggleMotifsAction, SIGNAL(triggered()), 
210           this, SLOT(toggleMotifs()));
211   toggleMotifsAction->setCheckable(true);
212   toggleMotifsAction->setIcon(QIcon(":/icons/motif_icon.png"));
213   toggleMotifsAction->setWhatsThis(tr("Toggle motif annotations on/off\n\n"
214                                    "You can load motif annotations via "
215                                    "'File->Load Motif List' menu option."));
216
217   //Save pixel map action
218   saveBrowserPixmapAction = new QAction(tr("Save to image..."), this);
219   connect(saveBrowserPixmapAction, (SIGNAL(triggered())),
220           &browser, SLOT(promptSaveBrowserPixmap()));
221   saveBrowserPixmapAction->setIcon(QIcon(":/icons/image2.png"));
222
223   viewMussaAlignmentAction = new QAction(tr("View mussa alignment"), this);
224   connect(viewMussaAlignmentAction, SIGNAL(triggered()),
225           this, SLOT(viewMussaAlignment() ));
226   viewMussaAlignmentAction->setWhatsThis(tr("Create a zoomed in window "
227                                             "showing alignment of the seqcomp "
228                                             "defined paths"));
229
230   whatsThisAction = QWhatsThis::createAction(this);
231   whatsThisAction->setIcon(QIcon(":/icons/help.png"));
232
233
234 }
235
236 void MussaWindow::setupMainMenu()
237 {
238   // we need to run setupActions first
239   assert (closeAction != 0);
240
241   QMenu *newMenu = menuBar()->addMenu(tr("&File"));
242   
243   newMenu->addAction(newMussaWindowAction);
244   newMenu->addAction(createNewAnalysisAction);
245   newMenu->addAction(loadMupaAction);
246   newMenu->addAction(loadSavedAnalysisAction);
247   //newMenu->addAction(createSubAnalysisAction);
248   newMenu->addSeparator();
249   newMenu->addAction(loadMotifListAction);
250   newMenu->addAction(saveMotifListAction);
251   newMenu->addSeparator();
252   newMenu->addAction(saveBrowserPixmapAction);
253   newMenu->addSeparator();
254   newMenu->addAction(closeAction);
255
256   newMenu = menuBar()->addMenu(tr("&Edit"));
257   newMenu->addAction(copySelectedSequenceAsFastaAction);
258  
259   newMenu = menuBar()->addMenu(tr("&View"));
260   newMenu->addAction(editMotifsAction);
261   newMenu->addAction(viewMussaAlignmentAction);
262   newMenu->addAction(showMussaViewToolbarAction);
263
264   newMenu = menuBar()->addMenu(tr("&Help"));
265 #if defined(QT_ASSISTANT_LIB)
266   newMenu->addAction(mussaManualAssistantAction);
267 #endif
268   newMenu->addAction(whatsThisAction);
269   newMenu->addSeparator();
270   newMenu->addAction(aboutAction);
271 }
272
273 void MussaWindow::setupAssistant()
274 {
275 #if 0 && defined(Q_WS_MAC)
276     CFURLRef pluginRef = CFBundleCopyBundleURL(CFBundleGetMainBundle());
277     CFStringRef macPath = CFURLCopyFileSystemPath(pluginRef,
278                                            kCFURLPOSIXPathStyle);
279     const char *pathPtr = CFStringGetCStringPtr(macPath,
280                                            CFStringGetSystemEncoding());
281     qDebug("Path = %s", pathPtr);
282     CFRelease(pluginRef);
283     CFRelease(macPath);
284 #endif
285 #if defined(QT_ASSISTANT_LIB)
286   QStringList manualAssistantArgs;
287   manualAssistantArgs = QStringList();
288   manualAssistantArgs << "-profile" << "./doc/manual/mussagl_manual.adp";
289   manualAssistant = new QAssistantClient("assistant", this);
290   manualAssistant->setArguments(manualAssistantArgs);
291   connect(manualAssistant, SIGNAL(error(QString)),
292           this, SLOT(assistantError(QString)));
293 #endif
294 }
295   
296 void MussaWindow::about()
297 {
298   QString msg("Welcome to Multiple Species Sequence Analysis\n"
299               "(c) 2005-2006 California Institute of Technology\n"
300               "Tristan De Buysscher, Diane Trout\n");
301    msg += "\n\r";
302    msg += "Path: ";
303    msg += QDir::currentPath();
304    msg += "\n";
305    msg += "OpenGL: ";
306    msg += (char *)glGetString(GL_VERSION);
307    msg += "\n";
308    QMessageBox::about(this, tr("About mussa"), msg);
309 }
310
311 void MussaWindow::createNewAnalysis()
312 {
313   try {
314     if (setup_analysis_dialog.exec()) {
315       Mussa *m = 0;
316       m = setup_analysis_dialog.getMussa();
317       setAnalysis(m);
318     } else {
319       std::cout << "New mussa exp. aborted!\n";
320     }
321   } catch(mussa_error e) {
322     QString msg(e.what());
323     QMessageBox::warning(this, tr("Create New Analysis"), msg);
324   }
325 }
326
327 void MussaWindow::createSubAnalysis()
328 {
329   NotImplementedBox();
330 }
331
332 void MussaWindow::editMotifs()
333 {
334   if (motif_editor != 0) {
335     motif_editor->hide();
336     delete motif_editor;
337   }
338   motif_editor = new MotifEditor(analysis);
339   connect(motif_editor, SIGNAL(changedMotifs()), 
340           this, SLOT(updateAnnotations()));
341   motif_editor->show();
342 }
343
344 void MussaWindow::loadMotifList()
345 {
346   QString caption("Load a motif list");
347   QString filter("Motif list(*.txt *.mtl)");
348   QString path = QFileDialog::getOpenFileName(this,
349                                                    caption, 
350                                                    QDir::currentPath(),
351                                                    filter);
352   // user hit cancel?
353   if (path.isNull()) 
354     return;
355   // try to load safely
356   try {
357     fs::path converted_path(path.toStdString(), fs::native);
358     analysis->load_motifs(converted_path);
359   } catch (runtime_error e) {
360     QString msg("Unable to load ");
361     msg += path;
362     msg += "\n";
363     msg += e.what();
364     QMessageBox::warning(this, "Load Motifs", msg);
365   }
366   assert (analysis != 0);
367 }
368
369 void MussaWindow::saveMotifList()
370 {
371   NotImplementedBox();
372 }
373
374 void MussaWindow::loadMupa()
375 {
376   QString caption("Load a mussa parameter file");
377   QString filter("Mussa Parameters (*.mupa)");
378   QString mupa_path = QFileDialog::getOpenFileName(this,
379                                                    caption, 
380                                                    QDir::currentPath(),
381                                                    filter);
382   // user hit cancel?
383   if (mupa_path.isNull()) 
384     return;
385   // try to load safely
386   try {
387     Mussa *m = new Mussa;
388     fs::path converted_path(mupa_path.toStdString(), fs::native);
389     connect(m, SIGNAL(progress(const std::string&, int, int)),
390             this, SLOT(updateProgress(const std::string&, int, int)));
391     m->load_mupa_file(converted_path);
392     m->analyze();
393     setAnalysis(m);
394     setWindowTitle(converted_path.native_file_string().c_str());
395   } catch (mussa_load_error e) {
396     QString msg("Unable to load ");
397     msg += mupa_path;
398     msg += "\n";
399     msg += e.what();
400     QMessageBox::warning(this, "Load Parameter", msg);
401   }
402   assert (analysis != 0);
403 }
404
405 void MussaWindow::loadSavedAnalysis()
406 {
407   QString caption("Load a previously run analysis");
408   QString muway_dir = QFileDialog::getExistingDirectory(this,
409                                                         caption, 
410                                                         QDir::currentPath());
411   // user hit cancel?
412   if (muway_dir.isNull()) 
413     return;
414   // try to safely load
415   try {
416     Mussa *m = new Mussa;
417     fs::path converted_path(muway_dir.toStdString(), fs::native);
418     connect(m, SIGNAL(progress(const std::string&, int, int)),
419             this, SLOT(updateProgress(const std::string&, int, int)));
420     m->load(converted_path);
421     // only switch mussas if we loaded without error
422     setAnalysis(m);
423     setWindowTitle(converted_path.native_file_string().c_str());
424   } catch (mussa_load_error e) {
425     QString msg("Unable to load ");
426     msg += muway_dir;
427     msg += "\n";
428     msg += e.what();
429     QMessageBox::warning(this, "Load Parameter", msg);
430   }
431   assert (analysis != 0);
432 }
433
434 void MussaWindow::newMussaWindow()
435 {
436   Mussa *a = new Mussa();
437   MussaWindow *win = new MussaWindow(a);
438   win->show();
439 }
440
441 void MussaWindow::setSoftThreshold(int threshold)
442 {
443   if (analysis->get_soft_threshold() != threshold) {
444     analysis->set_soft_threshold(threshold);
445     analysis->nway();
446     updateLinks();
447     update();
448   }
449 }
450
451 void MussaWindow::showMussaToolbar()
452 {
453   if (mussaViewTB.isVisible())
454     mussaViewTB.hide();
455   else
456     mussaViewTB.show();
457 }
458
459 void MussaWindow::toggleMotifs()
460 {
461   NotImplementedBox();
462 }
463
464 void MussaWindow::showManual()
465 {
466   manualAssistant->openAssistant();
467 }
468
469 void MussaWindow::assistantError(QString message)
470 {
471   //std::cout << "QAssistantError: " << message.toStdString() << "\n";
472   QMessageBox::warning ( this, "Warning: Mussagl Manual", message, 
473                          QMessageBox::Ok, 
474                          QMessageBox::NoButton);
475 }
476
477 void MussaWindow::NotImplementedBox()
478 {
479   QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
480 }      
481
482 void MussaWindow::viewMussaAlignment()
483 {
484   const set<int>& selected_paths = browser.selectedPaths();
485   if (selected_paths.size() == 0 ) {
486     QMessageBox::warning(this, 
487                          QObject::tr("mussa"),
488                          QObject::tr("you should probably select some paths "
489                                      "first"));
490   } else {
491     MussaAlignedWindow *ma_win = new MussaAlignedWindow(*analysis, 
492                                                         selected_paths);
493     connect(this, SIGNAL(changedAnnotations()), 
494             ma_win, SLOT(update()));
495     aligned_windows.push_back(ma_win);
496     ma_win->show();
497   }
498 }
499                         
500 void MussaWindow::updateAnalysis()
501 {
502   threshold.setRange(analysis->get_threshold(),analysis->get_window());
503   for (list<MussaAlignedWindow *>::iterator maw_i = aligned_windows.begin();
504        maw_i != aligned_windows.end();
505        ++maw_i)
506   {
507     (*maw_i)->hide();
508     delete *maw_i;
509   }
510
511   browser.clear();
512   const vector<Sequence>& seqs = analysis->sequences();
513   browser.setSequences(seqs, analysis->colorMapper());
514   updateLinks();
515   browser.zoomOut();
516   zoom.setValue(browser.zoom());
517 }
518
519 void MussaWindow::updateAnnotations()
520 {
521   // motifs were changed in the sequences by 
522   // Mussa::update_sequences_motifs
523   emit changedAnnotations();
524   browser.update();
525 }
526
527 void MussaWindow::updateLinks()
528 {
529   browser.clear_links();
530   bool reversed = false;
531   const NwayPaths& nway = analysis->paths();
532
533   typedef list<ConservedPath> conserved_paths;
534   typedef conserved_paths::const_iterator const_conserved_paths_itor;
535   for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
536       path_itor != nway.refined_pathz.end();
537       ++path_itor)
538   {
539     // since we were drawing to the start of a window, and opengl lines
540     // are centered around the two connecting points our lines were slightly
541     // offset. the idea of window_offset is to adjust them to the
542     // right for forward compliment or left for reverse compliment
543     // FIXME: figure out how to unit test these computations
544     //GLfloat window_offset = (path_itor->window_size)/2.0;
545
546     size_t track_len = path_itor->track_indexes.size();
547     vector<int> normalized_path;
548     normalized_path.reserve(track_len);
549     vector<bool> rc_flags(false, track_len);
550     for (size_t track_i=0; track_i != track_len; ++track_i)
551     {
552       int x = path_itor->track_indexes[track_i];
553       // at some point when we modify the pathz data structure to keep
554       // track of the score we can put grab the depth here.
555       //
556       // are we reverse complimented?
557       if ( x>=0) {
558         reversed = false;
559       } else {
560         reversed = true;
561         // make positive 
562         x = -x;
563       }
564       normalized_path.push_back(x);
565       rc_flags.push_back(reversed);
566     }
567     browser.link(normalized_path, rc_flags, path_itor->window_size);
568   }
569   browser.update();
570 }
571
572 void 
573 MussaWindow::updateProgress(const string& description, int current, int max)
574 {  
575   // if we're done  
576   if (current == max) {
577     if (progress_dialog != 0) {
578       progress_dialog->hide();
579       delete progress_dialog;
580       progress_dialog = 0;
581     }
582   } else {
583     // if we're starting, create the dialog
584     if (progress_dialog == 0) {
585       QString desc(description.c_str());
586       QString cancel("Cancel");
587       progress_dialog = new QProgressDialog(desc, cancel, current, max, this);
588       progress_dialog->show();
589     } else {
590       // just update the dialog
591       progress_dialog->setValue(current);
592     }
593   }
594   qApp->processEvents();
595 }