ticket:104 fix some pointer problems with MussaAlignedWindow
[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 <memory>
24 #include <iostream>
25
26 #include <boost/filesystem/path.hpp>
27 namespace fs = boost::filesystem;
28 #include <boost/bind.hpp>
29
30 using namespace std;
31
32 MussaWindow::MussaWindow(Mussa *analysis_, QWidget *parent) :
33   QMainWindow(parent),
34   analysis(analysis_),
35   motif_editor(0),
36   setup_analysis_dialog(this),
37   browser(this),
38   mussaViewTB("Path Views"),
39   zoom(),
40   threshold(),
41   progress_dialog(0),
42   aboutAction(0),
43   closeAction(0),
44   createNewAnalysisAction(0),
45   createSubAnalysisAction(0),
46   editMotifsAction(0),
47   loadMotifListAction(0),
48   loadMupaAction(0),
49   loadSavedAnalysisAction(0),
50   mussaManualAssistantAction(0),
51   newMussaWindowAction(0),
52   saveMotifListAction(0),
53   showMussaViewToolbarAction(0),
54   toggleMotifsAction(0),
55   saveBrowserPixmapAction(0),
56   whatsThisAction(0),
57   viewMussaAlignmentAction(0),
58   manualAssistant(0)
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 (closeAction != 0) delete closeAction;
106   if (createNewAnalysisAction != 0) delete createNewAnalysisAction;
107   if (createSubAnalysisAction != 0) delete createSubAnalysisAction;
108   if (editMotifsAction != 0) delete editMotifsAction;
109   if (loadMotifListAction != 0) delete loadMotifListAction;
110   if (loadMupaAction != 0) delete loadMupaAction;
111   if (loadSavedAnalysisAction != 0) delete loadSavedAnalysisAction;
112   if (mussaManualAssistantAction != 0) delete mussaManualAssistantAction;
113   if (newMussaWindowAction != 0) delete newMussaWindowAction;
114   if (saveBrowserPixmapAction != 0) delete saveBrowserPixmapAction;
115   if (saveMotifListAction != 0) delete saveMotifListAction;
116   if (showMussaViewToolbarAction != 0) delete showMussaViewToolbarAction;
117   if (toggleMotifsAction != 0) delete toggleMotifsAction;
118   if (whatsThisAction != 0) delete whatsThisAction;
119   if (viewMussaAlignmentAction != 0) delete viewMussaAlignmentAction;
120   
121   if (manualAssistant != 0) delete manualAssistant;
122   
123 }
124
125 void MussaWindow::setAnalysis(Mussa *new_analysis)
126 {
127   if (new_analysis != 0) {
128     // only switch mussas if we loaded without error
129     clear();
130     delete analysis;
131     analysis = new_analysis;
132     setWindowTitle(analysis->get_name().c_str());
133     updateAnalysis();
134   }
135 }
136
137 void MussaWindow::setupActions()
138 {
139   // we really don't want to run this more than once.
140   assert (closeAction == 0);
141
142   // the ever popular about box
143   aboutAction = new QAction(tr("&About"), this);
144   connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
145   aboutAction->setIcon(QIcon(":/icons/info.png"));
146
147   // add exit
148   closeAction = new QAction(tr("&Close"), this);
149   closeAction->setStatusTip(tr("Close this window"));
150   connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
151   closeAction->setIcon(QIcon(":/icons/exit.png"));
152   
153   createNewAnalysisAction = new QAction(tr("Create Analysis"), this);
154   connect(createNewAnalysisAction, SIGNAL(triggered()), 
155           this, SLOT(createNewAnalysis()));
156   createNewAnalysisAction->setIcon(QIcon(":/icons/filenew.png"));
157   
158   createSubAnalysisAction = new QAction(tr("Define SubAnalysis"), this);
159   connect(createSubAnalysisAction, SIGNAL(triggered()), 
160           this, SLOT(createSubAnalysis()));
161
162   editMotifsAction = new QAction(tr("Edit Motifs"), this);;
163   connect(editMotifsAction, SIGNAL(triggered()), this, SLOT(editMotifs()));
164   
165   loadMotifListAction = new QAction(tr("Load Motif List"), this);
166   connect(loadMotifListAction, SIGNAL(triggered()), 
167           this, SLOT(loadMotifList()));
168   loadMotifListAction->setIcon(QIcon(":/icons/fileopen.png"));
169   
170   loadMupaAction = new QAction(tr("Create Analysis from File"), this);
171   connect(loadMupaAction, SIGNAL(triggered()), 
172           this, SLOT(loadMupa()));
173   loadMupaAction->setIcon(QIcon(":/icons/fileopen.png"));
174
175   loadSavedAnalysisAction = new QAction(tr("Load Existing &Analysis"), this);
176   connect(loadSavedAnalysisAction, SIGNAL(triggered()), 
177           this, SLOT(loadSavedAnalysis()));
178   loadSavedAnalysisAction->setIcon(QIcon(":/icons/fileopen.png"));
179
180   mussaManualAssistantAction = new QAction(tr("Mussagl Manual..."), this);
181   mussaManualAssistantAction->setIcon(QIcon(":/icons/contents.png"));
182   connect(mussaManualAssistantAction, SIGNAL(triggered()),
183           this, SLOT(showManual()));
184
185   newMussaWindowAction = new QAction(tr("&New Mussa Window"), this);
186   newMussaWindowAction->setStatusTip("open another mussa window to allow comparing results");
187   connect(newMussaWindowAction, SIGNAL(triggered()), 
188           this, SLOT(newMussaWindow()));
189   newMussaWindowAction->setIcon(QIcon(":/icons/window_new.png"));
190
191   saveMotifListAction = new QAction(tr("Save Motifs"), this);
192   connect(saveMotifListAction, SIGNAL(triggered()), 
193           this, SLOT(saveMotifList()));
194   saveMotifListAction->setIcon(QIcon(":/icons/filesave.png"));
195
196   showMussaViewToolbarAction = new QAction(tr("Show Toolbar"), this);
197   connect(showMussaViewToolbarAction, SIGNAL(triggered()), 
198           this, SLOT(showMussaToolbar()));
199   showMussaViewToolbarAction->setCheckable(true);
200   showMussaViewToolbarAction->setChecked(true);
201
202   toggleMotifsAction = new QAction(tr("Toggle Motifs"), this);
203   connect(toggleMotifsAction, SIGNAL(triggered()), 
204           this, SLOT(toggleMotifs()));
205   toggleMotifsAction->setCheckable(true);
206   toggleMotifsAction->setIcon(QIcon(":/icons/motif_icon.png"));
207   toggleMotifsAction->setWhatsThis(tr("Toggle motif annotations on/off\n\n"
208                                    "You can load motif annotations via "
209                                    "'File->Load Motif List' menu option."));
210
211   //Save pixel map action
212   saveBrowserPixmapAction = new QAction(tr("Save to image..."), this);
213   connect(saveBrowserPixmapAction, (SIGNAL(triggered())),
214           &browser, SLOT(promptSaveBrowserPixmap()));
215   saveBrowserPixmapAction->setIcon(QIcon(":/icons/image2.png"));
216
217   viewMussaAlignmentAction = new QAction(tr("View mussa alignment"), this);
218   connect(viewMussaAlignmentAction, SIGNAL(triggered()),
219           this, SLOT(viewMussaAlignment() ));
220   viewMussaAlignmentAction->setWhatsThis(tr("Create a zoomed in window "
221                                             "showing alignment of the seqcomp "
222                                             "defined paths"));
223
224   whatsThisAction = QWhatsThis::createAction(this);
225   whatsThisAction->setIcon(QIcon(":/icons/help.png"));
226
227
228 }
229
230 void MussaWindow::setupMainMenu()
231 {
232   // we need to run setupActions first
233   assert (closeAction != 0);
234
235   QMenu *newMenu = menuBar()->addMenu(tr("&File"));
236   
237   newMenu->addAction(newMussaWindowAction);
238   newMenu->addAction(createNewAnalysisAction);
239   newMenu->addAction(loadMupaAction);
240   newMenu->addAction(loadSavedAnalysisAction);
241   //newMenu->addAction(createSubAnalysisAction);
242   newMenu->addSeparator();
243   newMenu->addAction(loadMotifListAction);
244   newMenu->addAction(saveMotifListAction);
245   newMenu->addSeparator();
246   newMenu->addAction(saveBrowserPixmapAction);
247   newMenu->addSeparator();
248   newMenu->addAction(closeAction);
249
250   newMenu = menuBar()->addMenu(tr("&Edit"));
251   newMenu->addAction(&browser.getCopySelectedSequenceAsFastaAction());
252  
253   newMenu = menuBar()->addMenu(tr("&View"));
254   newMenu->addAction(editMotifsAction);
255   newMenu->addAction(viewMussaAlignmentAction);
256   newMenu->addAction(showMussaViewToolbarAction);
257
258   newMenu = menuBar()->addMenu(tr("&Help"));
259 #if defined(QT_ASSISTANT_LIB)
260   newMenu->addAction(mussaManualAssistantAction);
261 #endif
262   newMenu->addAction(whatsThisAction);
263   newMenu->addSeparator();
264   newMenu->addAction(aboutAction);
265 }
266
267 void MussaWindow::setupAssistant()
268 {
269 #if 0 && defined(Q_WS_MAC)
270     CFURLRef pluginRef = CFBundleCopyBundleURL(CFBundleGetMainBundle());
271     CFStringRef macPath = CFURLCopyFileSystemPath(pluginRef,
272                                            kCFURLPOSIXPathStyle);
273     const char *pathPtr = CFStringGetCStringPtr(macPath,
274                                            CFStringGetSystemEncoding());
275     qDebug("Path = %s", pathPtr);
276     CFRelease(pluginRef);
277     CFRelease(macPath);
278 #endif
279 #if defined(QT_ASSISTANT_LIB)
280   QStringList manualAssistantArgs;
281   manualAssistantArgs = QStringList();
282   manualAssistantArgs << "-profile" << "./doc/manual/mussagl_manual.adp";
283   manualAssistant = new QAssistantClient("assistant", this);
284   manualAssistant->setArguments(manualAssistantArgs);
285   connect(manualAssistant, SIGNAL(error(QString)),
286           this, SLOT(assistantError(QString)));
287 #endif
288 }
289   
290 void MussaWindow::about()
291 {
292   QString msg("Welcome to Multiple Species Sequence Analysis\n"
293               "(c) 2005-2006 California Institute of Technology\n"
294               "Tristan De Buysscher, Diane Trout\n");
295    msg += "\n\r";
296    msg += "Path: ";
297    msg += QDir::currentPath();
298    msg += "\n";
299    msg += "OpenGL: ";
300    msg += (char *)glGetString(GL_VERSION);
301    msg += "\n";
302    QMessageBox::about(this, tr("About mussa"), msg);
303 }
304
305 void MussaWindow::clear()
306 {
307   aligned_windows.clear();
308   browser.clear();
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     boost::shared_ptr<MussaAlignedWindow> ma_win( 
492       new MussaAlignedWindow(*analysis, selected_paths)
493     );
494
495     aligned_windows.push_back(ma_win);
496     connect(this, SIGNAL(changedAnnotations()), 
497             aligned_windows.back().get(), SLOT(update()));
498     aligned_windows.back()->show();
499   }
500 }
501                         
502 void MussaWindow::updateAnalysis()
503 {
504   threshold.setRange(analysis->get_threshold(),analysis->get_window());
505
506   const vector<Sequence>& seqs = analysis->sequences();
507   browser.setSequences(seqs, analysis->colorMapper());
508   updateLinks();
509   browser.zoomOut();
510   zoom.setValue(browser.zoom());
511 }
512
513 void MussaWindow::updateAnnotations()
514 {
515   // motifs were changed in the sequences by 
516   // Mussa::update_sequences_motifs
517   emit changedAnnotations();
518   browser.update();
519 }
520
521 void MussaWindow::updateLinks()
522 {
523   browser.clear_links();
524   bool reversed = false;
525   const NwayPaths& nway = analysis->paths();
526
527   typedef list<ConservedPath> conserved_paths;
528   typedef conserved_paths::const_iterator const_conserved_paths_itor;
529   for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
530       path_itor != nway.refined_pathz.end();
531       ++path_itor)
532   {
533     // since we were drawing to the start of a window, and opengl lines
534     // are centered around the two connecting points our lines were slightly
535     // offset. the idea of window_offset is to adjust them to the
536     // right for forward compliment or left for reverse compliment
537     // FIXME: figure out how to unit test these computations
538     //GLfloat window_offset = (path_itor->window_size)/2.0;
539
540     size_t track_len = path_itor->track_indexes.size();
541     vector<int> normalized_path;
542     normalized_path.reserve(track_len);
543     vector<bool> rc_flags(false, track_len);
544     for (size_t track_i=0; track_i != track_len; ++track_i)
545     {
546       int x = path_itor->track_indexes[track_i];
547       // at some point when we modify the pathz data structure to keep
548       // track of the score we can put grab the depth here.
549       //
550       // are we reverse complimented?
551       if ( x>=0) {
552         reversed = false;
553       } else {
554         reversed = true;
555         // make positive 
556         x = -x;
557       }
558       normalized_path.push_back(x);
559       rc_flags.push_back(reversed);
560     }
561     browser.link(normalized_path, rc_flags, path_itor->window_size);
562   }
563   browser.update();
564 }
565
566 void 
567 MussaWindow::updateProgress(const string& description, int current, int max)
568 {  
569   // if we're done  
570   if (current == max) {
571     if (progress_dialog != 0) {
572       progress_dialog->hide();
573       delete progress_dialog;
574       progress_dialog = 0;
575     }
576   } else {
577     // if we're starting, create the dialog
578     if (progress_dialog == 0) {
579       QString desc(description.c_str());
580       QString cancel("Cancel");
581       progress_dialog = new QProgressDialog(desc, cancel, current, max, this);
582       progress_dialog->show();
583     } else {
584       // just update the dialog
585       progress_dialog->setValue(current);
586     }
587   }
588   qApp->processEvents();
589 }