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