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