provide a user interface to edit motifs
[mussa.git] / qui / MussaWindow.cpp
1 #include <QAction>
2 #include <QDir>
3 #include <QFileDialog>
4 #include <QHBoxLayout>
5 #include <QIcon>
6 #include <QMenuBar>
7 #include <QMessageBox>
8 #include <QScrollBar>
9 #include <QStatusBar>
10 #include <QString>
11 #include <QWhatsThis>
12
13 #include "qui/MussaWindow.hpp"
14 #include "mussa_exceptions.hpp"
15
16 #include <iostream>
17
18 using namespace std;
19
20 MussaWindow::MussaWindow(Mussa *analysis_, QWidget *parent) :
21   QMainWindow(parent),
22   analysis(analysis_),
23   motif_editor(0),
24   browser(this),
25   mussaViewTB("Path Views"),
26   zoomBox(),
27   threshold(),
28   zoomLabel(),
29   closeAction(0) // initialize one of the pointers to null as a saftey flag
30 {
31   setupActions();
32   setupMainMenu();
33
34   //This next setWhatsThis function prevents
35   // a segfault when using WhatsThis feature with 
36   // opengl widget.
37   //scene->setWhatsThis(tr("Mussa in OpenGL!"));
38   setCentralWidget(&browser);
39   // well updatePosition isn't quite right as we really just need
40   // to call update()
41   connect(this, SIGNAL(changedAnnotations()), &browser, SLOT(update()));
42
43   mussaViewTB.addAction(toggleMotifsAction);
44   
45   zoomLabel.setPixmap(QIcon("icons/viewmag.png").pixmap(16, 16));
46   zoomLabel.setToolTip(tr("Zoom"));
47   zoomLabel.setWhatsThis(tr("Zoom magnification factor"));
48   mussaViewTB.addWidget(&zoomLabel);
49
50   zoomBox.setToolTip(tr("Zoom"));
51   zoomBox.setWhatsThis(tr("Zoom magnification factor"));
52   zoomBox.setRange(1,10000000);
53   mussaViewTB.addWidget(&zoomBox);
54   connect(&zoomBox, SIGNAL(valueChanged(int)), 
55           &browser, SLOT(setZoom(int)));
56   
57   threshold.setRange(19, 30);
58   threshold.setThreshold(19);
59   //scene->setClipPlane(20);
60   // FIXME: for when we get the paths drawn at the appropriate depth
61   //connect(&threshold, SIGNAL(thresholdChanged(int)),
62   //        this, SLOT(setClipPlane(int)));
63   connect(&threshold, SIGNAL(thresholdChanged(int)),
64           this, SLOT(setSoftThreshold(int)));
65   mussaViewTB.addWidget(&threshold);
66
67   addToolBar(&mussaViewTB);
68
69   statusBar()->showMessage("Welcome to mussa", 2000);
70   updateAnalysis();
71 }
72
73 void MussaWindow::setupActions()
74 {
75   // we really don't want to run this more than once.
76   assert (closeAction == 0);
77
78   // the ever popular about box
79   aboutAction = new QAction(tr("&About"), this);
80   connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
81   aboutAction->setIcon(QIcon("icons/info.png"));
82
83   // add exit
84   closeAction = new QAction(tr("&Close"), this);
85   closeAction->setStatusTip(tr("Close this window"));
86   connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
87   closeAction->setIcon(QIcon("icons/exit.png"));
88   
89   createNewAnalysisAction = new QAction(tr("Define Analysis"), this);
90   connect(createNewAnalysisAction, SIGNAL(triggered()), 
91           this, SLOT(createNewAnalysis()));
92   createNewAnalysisAction->setIcon(QIcon("icons/filenew.png"));
93   
94   createSubAnalysisAction = new QAction(tr("Define SubAnalysis"), this);
95   connect(createSubAnalysisAction, SIGNAL(triggered()), 
96           this, SLOT(createSubAnalysis()));
97
98   editMotifsAction = new QAction(tr("Edit Motifs"), this);;
99   connect(editMotifsAction, SIGNAL(triggered()), this, SLOT(editMotifs()));
100   
101   loadMotifListAction = new QAction(tr("Load Motif List"), this);
102   connect(loadMotifListAction, SIGNAL(triggered()), 
103           this, SLOT(loadMotifList()));
104   
105   loadMupaAction = new QAction(tr("Load Mussa Parameters"), this);
106   connect(loadMupaAction, SIGNAL(triggered()), 
107           this, SLOT(loadMupa()));
108
109   loadSavedAnalysisAction = new QAction(tr("Load &Analysis"), this);
110   connect(loadSavedAnalysisAction, SIGNAL(triggered()), 
111           this, SLOT(loadSavedAnalysis()));
112   loadSavedAnalysisAction->setIcon(QIcon("icons/fileopen.png"));
113
114   saveMotifListAction = new QAction(tr("Save Motifs"), this);
115   connect(saveMotifListAction, SIGNAL(triggered()), 
116           this, SLOT(saveMotifList()));
117   saveMotifListAction->setIcon(QIcon("icons/filesave.png"));
118
119   showMussaViewToolbarAction = new QAction(tr("Show Toolbar"), this);
120   connect(showMussaViewToolbarAction, SIGNAL(triggered()), 
121           this, SLOT(showMussaToolbar()));
122   showMussaViewToolbarAction->setCheckable(true);
123   showMussaViewToolbarAction->setChecked(true);
124
125   toggleMotifsAction = new QAction(tr("Toggle Motifs"), this);
126   connect(toggleMotifsAction, SIGNAL(triggered()), 
127           this, SLOT(toggleMotifs()));
128   toggleMotifsAction->setCheckable(true);
129   toggleMotifsAction->setIcon(QIcon("icons/motif_icon.png"));
130   toggleMotifsAction->setWhatsThis(tr("Toggle motif annotations on/off\n\n"
131                                    "You can load motif annotations via "
132                                    "'File->Load Motif List' menu option."));
133
134   //Save pixel map action
135   saveBrowserPixmapAction = new QAction(tr("Save to image..."), this);
136   connect(saveBrowserPixmapAction, (SIGNAL(triggered())),
137           &browser, SLOT(promptSaveBrowserPixmap()));
138   saveBrowserPixmapAction->setIcon(QIcon("icons/image2.png"));
139
140   viewMussaAlignmentAction = new QAction(tr("View mussa alignment"), this);
141   connect(viewMussaAlignmentAction, SIGNAL(triggered()),
142           this, SLOT(viewMussaAlignment() ));
143   viewMussaAlignmentAction->setWhatsThis(tr("Create a zoomed in window "
144                                             "showing alignment of the seqcomp "
145                                             "defined paths"));
146
147   whatsThisAction = QWhatsThis::createAction(this);
148   whatsThisAction->setIcon(QIcon("icons/help.png"));
149
150 }
151
152 void MussaWindow::setupMainMenu()
153 {
154   // we need to run setupActions first
155   assert (closeAction != 0);
156   
157   QMenu *newMenu;
158   newMenu = menuBar()->addMenu(tr("&File"));
159   newMenu->addAction(createNewAnalysisAction);
160   newMenu->addAction(loadMupaAction);
161   newMenu->addAction(loadSavedAnalysisAction);
162   newMenu->addAction(createSubAnalysisAction);
163   newMenu->addSeparator();
164   newMenu->addAction(loadMotifListAction);
165   newMenu->addAction(saveMotifListAction);
166   newMenu->addSeparator();
167   newMenu->addAction(saveBrowserPixmapAction);
168   newMenu->addSeparator();
169   newMenu->addAction(closeAction);
170
171   newMenu = menuBar()->addMenu(tr("&View"));
172   newMenu->addAction(editMotifsAction);
173   newMenu->addAction(viewMussaAlignmentAction);
174   newMenu->addAction(showMussaViewToolbarAction);
175
176   newMenu = menuBar()->addMenu(tr("&Help"));
177   newMenu->addAction(whatsThisAction);
178   newMenu->addSeparator();
179   newMenu->addAction(aboutAction);
180 }
181   
182 void MussaWindow::about()
183 {
184   QMessageBox::about(this, tr("About mussa"),
185       tr("Welcome to Multiple Species Sequence Analysis\n"
186          "(c) 2005-2006 California Institute of Technology\n"
187          "Tristan De Buysscher, Diane Trout\n"));
188 }
189
190 void MussaWindow::createNewAnalysis()
191 {
192   NotImplementedBox();
193 }
194
195 void MussaWindow::createSubAnalysis()
196 {
197   NotImplementedBox();
198 }
199
200 void MussaWindow::editMotifs()
201 {
202   if (motif_editor != 0) {
203     motif_editor->hide();
204     delete motif_editor;
205   }
206   motif_editor = new MotifEditor(analysis);
207   connect(motif_editor, SIGNAL(changedMotifs()), 
208           this, SLOT(updateAnnotations()));
209   motif_editor->show();
210 }
211
212 void MussaWindow::loadMotifList()
213 {
214   QString caption("Load a motif list");
215   QString filter("Motif list(*.txt *.mtl)");
216   QString path = QFileDialog::getOpenFileName(this,
217                                                    caption, 
218                                                    QDir::currentPath(),
219                                                    filter);
220   // user hit cancel?
221   if (path.isNull()) 
222     return;
223   // try to load safely
224   try {
225     analysis->load_motifs(path.toStdString());
226   } catch (runtime_error e) {
227     QString msg("Unable to load ");
228     msg += path;
229     msg += "\n";
230     msg += e.what();
231     QMessageBox::warning(this, "Load Motifs", msg);
232   }
233   assert (analysis != 0);
234 }
235
236 void MussaWindow::saveMotifList()
237 {
238   NotImplementedBox();
239 }
240
241 void MussaWindow::loadMupa()
242 {
243   QString caption("Load a mussa parameter file");
244   QString filter("Mussa Parameters (*.mupa)");
245   QString mupa_path = QFileDialog::getOpenFileName(this,
246                                                    caption, 
247                                                    QDir::currentPath(),
248                                                    filter);
249   // user hit cancel?
250   if (mupa_path.isNull()) 
251     return;
252   // try to load safely
253   try {
254     Mussa *m = new Mussa;
255     m->load_mupa_file(mupa_path.toStdString());
256     m->analyze(0, 0, Mussa::TransitiveNway, 0.0);
257     // only switch mussas if we loaded without error
258     delete analysis;
259     analysis = m;
260     updateAnalysis();
261   } catch (mussa_load_error e) {
262     QString msg("Unable to load ");
263     msg += mupa_path;
264     msg += "\n";
265     msg += e.what();
266     QMessageBox::warning(this, "Load Parameter", msg);
267   }
268   assert (analysis != 0);
269 }
270
271 void MussaWindow::loadSavedAnalysis()
272 {
273   QString caption("Load a previously run analysis");
274   QString muway_dir = QFileDialog::getExistingDirectory(this,
275                                                         caption, 
276                                                         QDir::currentPath());
277   // user hit cancel?
278   if (muway_dir.isNull()) 
279     return;
280   // try to safely load
281   try {
282     Mussa *m = new Mussa;
283     m->load(muway_dir.toStdString());
284     // only switch mussas if we loaded without error
285     delete analysis;
286     analysis = m;
287     updateAnalysis();
288   } catch (mussa_load_error e) {
289     QString msg("Unable to load ");
290     msg += muway_dir;
291     msg += "\n";
292     msg += e.what();
293     QMessageBox::warning(this, "Load Parameter", msg);
294   }
295   assert (analysis != 0);
296 }
297
298 void MussaWindow::setSoftThreshold(int threshold)
299 {
300   if (analysis->get_soft_thres() != threshold) {
301     analysis->set_soft_thres(threshold);
302     analysis->nway();
303     updateLinks();
304     update();
305   }
306 }
307
308 void MussaWindow::showMussaToolbar()
309 {
310   if (mussaViewTB.isVisible())
311     mussaViewTB.hide();
312   else
313     mussaViewTB.show();
314 }
315
316 void MussaWindow::toggleMotifs()
317 {
318   NotImplementedBox();
319 }
320
321 void MussaWindow::NotImplementedBox()
322 {
323   QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
324 }      
325
326 void MussaWindow::viewMussaAlignment()
327 {
328   const set<int>& selected_paths = browser.selectedPaths();
329   if (selected_paths.size() == 0 ) {
330     QMessageBox::warning(this, 
331                          QObject::tr("mussa"),
332                          QObject::tr("you should probably select some paths "
333                                      "first"));
334   } else {
335     MussaAlignedWindow *ma_win = new MussaAlignedWindow(*analysis, 
336                                                         selected_paths);
337     connect(this, SIGNAL(changedAnnotations()), 
338             ma_win, SLOT(update()));
339     aligned_windows.push_back(ma_win);
340     ma_win->show();
341   }
342 }
343                         
344 void MussaWindow::updateAnalysis()
345 {
346   cout << "analysis updated" << endl;
347   for (list<MussaAlignedWindow *>::iterator maw_i = aligned_windows.begin();
348        maw_i != aligned_windows.end();
349        ++maw_i)
350   {
351     (*maw_i)->hide();
352     delete *maw_i;
353   }
354
355   browser.clear();
356   const vector<Sequence>& seqs = analysis->sequences();
357   browser.setSequences(seqs, analysis->colorMapper());
358   updateLinks();
359   browser.zoomOut();
360   cout << "browser zoom " <<  browser.zoom() << endl;
361   zoomBox.setValue(browser.zoom());
362 }
363
364 void MussaWindow::updateAnnotations()
365 {
366   // motifs were changed in the sequences by 
367   // Mussa::update_sequences_motifs
368   emit changedAnnotations();
369   browser.update();
370 }
371
372 void MussaWindow::updateLinks()
373 {
374   browser.clear_links();
375   bool reversed = false;
376   const NwayPaths& nway = analysis->paths();
377
378   typedef list<ExtendedConservedPath> conserved_paths;
379   typedef conserved_paths::const_iterator const_conserved_paths_itor;
380   for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
381       path_itor != nway.refined_pathz.end();
382       ++path_itor)
383   {
384     // since we were drawing to the start of a window, and opengl lines
385     // are centered around the two connecting points our lines were slightly
386     // offset. the idea of window_offset is to adjust them to the
387     // right for forward compliment or left for reverse compliment
388     // FIXME: figure out how to unit test these computations
389     //GLfloat window_offset = (path_itor->window_size)/2.0;
390
391     size_t track_len = path_itor->track_indexes.size();
392     vector<int> normalized_path;
393     normalized_path.reserve(track_len);
394     vector<bool> rc_flags(false, track_len);
395     for (size_t track_i=0; track_i != track_len; ++track_i)
396     {
397       int x = path_itor->track_indexes[track_i];
398       // at some point when we modify the pathz data structure to keep
399       // track of the score we can put grab the depth here.
400       //
401       // are we reverse complimented?
402       if ( x>=0) {
403         reversed = false;
404       } else {
405         reversed = true;
406         x = -x; // make positive
407       }
408       normalized_path.push_back(x);
409       rc_flags.push_back(reversed);
410     }
411     browser.link(normalized_path, rc_flags, path_itor->window_size);
412   }
413   browser.update();
414 }
415