Load saved analysis can open new windows
[mussa.git] / qui / MussaWindow.cpp
1 #include "py/python.hpp"
2 #include "qui/MussaWindow.hpp"
3 #include "mussa_exceptions.hpp"
4
5 #include <QAction>
6 #include <QApplication>
7 #include <QAssistantClient>
8 #include <QCloseEvent>
9 #include <QDir>
10 #include <QFileDialog>
11 #include <QHBoxLayout>
12 #include <QIcon>
13 #include <QMenuBar>
14 #include <QMessageBox>
15 #include <QScrollBar>
16 #include <QStatusBar>
17 #include <QString>
18 #include <QStringList>
19 #include <QWhatsThis>
20
21 #include <memory>
22 #include <iterator>
23 #include <iostream>
24
25 #include <boost/filesystem/path.hpp>
26 #include <boost/filesystem/operations.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   default_dir(QDir::home().absolutePath().toStdString(), fs::native),
36   motif_editor(0),
37   setup_analysis_dialog(this),
38   browser(this),
39   mussaViewTB("Path Views"),
40   zoom(),
41   threshold(),
42   progress_dialog(0),
43   aboutAction(0),
44   closeAction(0),
45   createNewAnalysisAction(0),
46   createSubAnalysisAction(0),
47   editMotifsAction(0),
48   loadMotifListAction(0),
49   loadMupaAction(0),
50   loadSavedAnalysisAction(0),
51   mussaManualAssistantAction(0),
52   newMussaWindowAction(0),
53   saveMotifListAction(0),
54   showMussaViewToolbarAction(0),
55   toggleMotifsAction(0),
56   saveBrowserPixmapAction(0),
57   whatsThisAction(0),
58   viewMussaAlignmentAction(0),
59   manualAssistant(0)
60 {
61   setupActions();
62   setupMainMenu();
63   setupAssistant();
64
65   //This next setWhatsThis function prevents
66   // a segfault when using WhatsThis feature with 
67   // opengl widget.
68   //scene->setWhatsThis(tr("Mussa in OpenGL!"));
69   setCentralWidget(&browser);
70   // well updatePosition isn't quite right as we really just need
71   // to call update()
72   connect(this, SIGNAL(changedAnnotations()), &browser, SLOT(update()));
73
74   //mussaViewTB.addAction(toggleMotifsAction);
75   mussaViewTB.addWidget(&zoom);
76   
77   connect(&zoom, SIGNAL(valueChanged(double)), 
78           &browser, SLOT(setZoom(double)));
79   
80   // threshold range is set in updateAnalysis
81   
82   //scene->setClipPlane(20);
83   // FIXME: for when we get the paths drawn at the appropriate depth
84   //connect(&threshold, SIGNAL(thresholdChanged(int)),
85   //        this, SLOT(setClipPlane(int)));
86   connect(&threshold, SIGNAL(thresholdChanged(int)),
87           this, SLOT(setSoftThreshold(int)));
88   mussaViewTB.addWidget(&threshold);
89
90   addToolBar(&mussaViewTB);
91
92   statusBar()->showMessage("Welcome to mussa", 2000);
93   connect(analysis, SIGNAL(progress(const std::string&, int, int)),
94           this, SLOT(updateProgress(const std::string&, int, int)));
95   updateAnalysis();
96 }
97
98 void MussaWindow::setAnalysis(Mussa *new_analysis)
99 {
100   if (new_analysis != 0) {
101     // only switch mussas if we loaded without error
102     clear();
103     delete analysis;
104     analysis = new_analysis;
105     setWindowTitle(analysis->get_name().c_str());
106     updateAnalysis();
107   }
108 }
109
110 void MussaWindow::setupActions()
111 {
112   // we really don't want to run this more than once.
113   assert (closeAction == 0);
114
115   // the ever popular about box
116   aboutAction = new QAction(tr("&About"), this);
117   connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
118   aboutAction->setIcon(QIcon(":/icons/info.png"));
119
120   // add exit
121   closeAction = new QAction(tr("&Close"), this);
122   closeAction->setStatusTip(tr("Close this window"));
123   connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
124   closeAction->setIcon(QIcon(":/icons/exit.png"));
125   
126   createNewAnalysisAction = new QAction(tr("Create Analysis"), this);
127   connect(createNewAnalysisAction, SIGNAL(triggered()), 
128           this, SLOT(createNewAnalysis()));
129   createNewAnalysisAction->setIcon(QIcon(":/icons/filenew.png"));
130   
131   createSubAnalysisAction = new QAction(tr("Add to Subanalysis"), this);
132   connect(createSubAnalysisAction, SIGNAL(triggered()), 
133           this, SLOT(createSubAnalysis()));
134
135   saveAnalysisAction = new QAction(tr("&Save Analysis"), this);
136   connect(saveAnalysisAction, SIGNAL(triggered()), 
137           this, SLOT(saveAnalysis()));
138   saveAnalysisAction->setIcon(QIcon(":/icons/filesave.png"));
139
140   saveAnalysisAsAction = new QAction(tr("Save Analysis &As"), this);
141   connect(saveAnalysisAsAction, SIGNAL(triggered()), 
142           this, SLOT(saveAnalysisAs()));
143   saveAnalysisAsAction->setIcon(QIcon(":/icons/filesave.png"));
144
145   editMotifsAction = new QAction(tr("Edit Motifs"), this);;
146   connect(editMotifsAction, SIGNAL(triggered()), this, SLOT(editMotifs()));
147   
148   loadMotifListAction = new QAction(tr("Load Motif List"), this);
149   connect(loadMotifListAction, SIGNAL(triggered()), 
150           this, SLOT(loadMotifList()));
151   loadMotifListAction->setIcon(QIcon(":/icons/fileopen.png"));
152   
153   loadMupaAction = new QAction(tr("Create Analysis from File"), this);
154   connect(loadMupaAction, SIGNAL(triggered()), 
155           this, SLOT(loadMupa()));
156   loadMupaAction->setIcon(QIcon(":/icons/fileopen.png"));
157
158   loadSavedAnalysisAction = new QAction(tr("Load Existing &Analysis"), this);
159   connect(loadSavedAnalysisAction, SIGNAL(triggered()), 
160           this, SLOT(loadSavedAnalysis()));
161   loadSavedAnalysisAction->setIcon(QIcon(":/icons/fileopen.png"));
162
163   mussaManualAssistantAction = new QAction(tr("Mussagl Manual..."), this);
164   mussaManualAssistantAction->setIcon(QIcon(":/icons/contents.png"));
165   connect(mussaManualAssistantAction, SIGNAL(triggered()),
166           this, SLOT(showManual()));
167
168   newMussaWindowAction = new QAction(tr("&New Mussa Window"), this);
169   newMussaWindowAction->setStatusTip("open another mussa window to allow comparing results");
170   connect(newMussaWindowAction, SIGNAL(triggered()), 
171           this, SLOT(newMussaWindow()));
172   newMussaWindowAction->setIcon(QIcon(":/icons/window_new.png"));
173
174   saveMotifListAction = new QAction(tr("Save Motifs"), this);
175   connect(saveMotifListAction, SIGNAL(triggered()), 
176           this, SLOT(saveMotifList()));
177   saveMotifListAction->setIcon(QIcon(":/icons/filesave.png"));
178
179   showMussaViewToolbarAction = new QAction(tr("Show Toolbar"), this);
180   connect(showMussaViewToolbarAction, SIGNAL(triggered()), 
181           this, SLOT(showMussaToolbar()));
182   showMussaViewToolbarAction->setCheckable(true);
183   showMussaViewToolbarAction->setChecked(true);
184
185   toggleMotifsAction = new QAction(tr("Toggle Motifs"), this);
186   connect(toggleMotifsAction, SIGNAL(triggered()), 
187           this, SLOT(toggleMotifs()));
188   toggleMotifsAction->setCheckable(true);
189   toggleMotifsAction->setIcon(QIcon(":/icons/motif_icon.png"));
190   toggleMotifsAction->setWhatsThis(tr("Toggle motif annotations on/off\n\n"
191                                    "You can load motif annotations via "
192                                    "'File->Load Motif List' menu option."));
193
194   //Save pixel map action
195   saveBrowserPixmapAction = new QAction(tr("Save to image..."), this);
196   connect(saveBrowserPixmapAction, (SIGNAL(triggered())),
197           &browser, SLOT(promptSaveBrowserPixmap()));
198   saveBrowserPixmapAction->setIcon(QIcon(":/icons/image2.png"));
199
200   viewMussaAlignmentAction = new QAction(tr("View sequence alignment"), this);
201   connect(viewMussaAlignmentAction, SIGNAL(triggered()),
202           this, SLOT(viewMussaAlignment() ));
203   viewMussaAlignmentAction->setWhatsThis(tr("Create a zoomed in window "
204                                             "showing alignment of the seqcomp "
205                                             "defined paths"));
206
207   whatsThisAction = QWhatsThis::createAction(this);
208   whatsThisAction->setIcon(QIcon(":/icons/help.png"));
209
210
211 }
212
213 void MussaWindow::closeEvent(QCloseEvent *event)
214 {
215   if(isClearingAnalysisSafe()) {
216     event->accept();
217   } else {
218     event->ignore();
219   }
220 }
221
222 void MussaWindow::setupMainMenu()
223 {
224   // we need to run setupActions first
225   assert (closeAction != 0);
226
227   QMenu *newMenu = menuBar()->addMenu(tr("&File"));
228   
229   newMenu->addAction(newMussaWindowAction);
230   newMenu->addAction(createNewAnalysisAction);
231   newMenu->addAction(loadMupaAction);
232   newMenu->addAction(loadSavedAnalysisAction);
233   newMenu->addAction(saveAnalysisAction);
234   newMenu->addAction(saveAnalysisAsAction);
235   newMenu->addSeparator();
236   newMenu->addAction(loadMotifListAction);
237   newMenu->addAction(saveMotifListAction);
238   newMenu->addSeparator();
239   newMenu->addAction(saveBrowserPixmapAction);
240   newMenu->addSeparator();
241   newMenu->addAction(closeAction);
242
243   newMenu = menuBar()->addMenu(tr("&Edit"));
244   newMenu->addAction(editMotifsAction);
245   newMenu->addAction(&browser.getCopySelectedSequenceAsFastaAction());
246   newMenu->addAction(createSubAnalysisAction);
247  
248   newMenu = menuBar()->addMenu(tr("&View"));
249   newMenu->addAction(viewMussaAlignmentAction);
250   newMenu->addAction(showMussaViewToolbarAction);
251
252   newMenu = menuBar()->addMenu(tr("&Help"));
253   newMenu->addAction(mussaManualAssistantAction);
254   newMenu->addAction(whatsThisAction);
255   newMenu->addSeparator();
256   newMenu->addAction(aboutAction);
257
258   // add some extra features to the context menu
259   QMenu& popupMenu = browser.getPopupMenu();
260   popupMenu.addAction(viewMussaAlignmentAction);
261   popupMenu.addAction(createSubAnalysisAction);
262 }
263
264 void MussaWindow::setupAssistant()
265 {
266 #if defined(QT_ASSISTANT_FOUND)
267   QStringList manualAssistantArgs;
268   manualAssistantArgs = QStringList();
269   manualAssistantArgs << "-profile" << "./doc/manual/mussagl_manual.adp";
270   manualAssistant = new QAssistantClient("assistant", this);
271   manualAssistant->setArguments(manualAssistantArgs);
272   connect(manualAssistant, SIGNAL(error(QString)),
273           this, SLOT(assistantError(QString)));
274 #endif
275 }
276   
277 void MussaWindow::about()
278 {
279   QString msg("Welcome to Multiple Species Sequence Analysis\n"
280               "(c) 2005-2006 California Institute of Technology\n"
281               "Tristan De Buysscher, Diane Trout\n");
282    msg += "\n\r";
283    msg += "OpenGL: ";
284    msg += (char *)glGetString(GL_VERSION);
285    msg += "\n";
286    QMessageBox::about(this, tr("About mussa"), msg);
287 }
288
289 void MussaWindow::clear()
290 {
291   aligned_windows.clear();
292   browser.clear();
293 }
294
295 void MussaWindow::createNewAnalysis()
296 {
297   try {
298     // ideally we should open a new window if there's an analysis
299     // but this should work for the moment.
300     if (not isClearingAnalysisSafe()) return;
301
302     if (setup_analysis_dialog.exec()) {
303       Mussa *m = 0;
304       m = setup_analysis_dialog.getMussa();
305       setAnalysis(m);
306     }
307   } catch(mussa_error e) {
308     QString msg(e.what());
309     QMessageBox::warning(this, tr("Create New Analysis"), msg);
310   }
311 }
312
313 void MussaWindow::createSubAnalysis()
314 {
315   list<SequenceLocation> result;
316   SequenceLocationModel& model = subanalysis_window.getModel();
317   browser.copySelectedTracksAsSeqLocation(result);
318   for(list<SequenceLocation>::iterator result_itor = result.begin();
319       result_itor != result.end();
320       ++result_itor)
321   {
322     model.push_back(*result_itor);
323   }
324
325   if (not subanalysis_window.isVisible()) {
326     subanalysis_window.show();
327   }
328 }
329
330 void MussaWindow::saveAnalysis()
331 {
332   // if we've got an analysis
333   if (analysis and not analysis->empty()) {
334     // if it doesn't have a name we need to pick one
335     if (analysis->get_analysis_path().empty()) {
336       saveAnalysisAs();
337     } else {     
338       // if we've got a name, has it changed any?
339       // this doesn't work when a sequence changes something (like its
340       // name)
341       //else if (analysis->is_dirty())
342       analysis->save();
343     }
344   }
345 }
346
347 void MussaWindow::saveAnalysisAs()
348 {
349   std::auto_ptr<QFileDialog> dialog(new QFileDialog(this));
350   dialog->setAcceptMode(QFileDialog::AcceptSave);
351   dialog->setFileMode(QFileDialog::AnyFile);
352   dialog->setDirectory(QDir(default_dir.native_directory_string().c_str()));
353
354   QStringList fileNames;
355   if (not dialog->exec()) {
356     return;
357   }
358   fileNames = dialog->selectedFiles();
359    
360   if (fileNames.size() != 1) {
361     return;
362   }
363
364   fs::path save_path(fileNames[0].toStdString());
365   // do you want to overwrite?
366   if (fs::exists(save_path) and 
367       QMessageBox::question(
368         this,
369         tr("Overwrite File? -- Mussa"),
370         tr("A file called %1 already exists"
371            "do you want to overwrite it?")
372            .arg(fileNames[0]),
373         tr("&Yes"), tr("&No"),
374         QString(), 0, 1)
375       ) {
376     return;
377   }
378   analysis->save(save_path);
379   default_dir = (save_path / "..").normalize();
380 }
381
382 bool MussaWindow::isClearingAnalysisSafe()
383 {
384   if (analysis and not analysis->empty() and analysis->is_dirty()) {
385     switch (QMessageBox::question(
386       this,
387       tr("Save Unsaved Changes -- Mussa"),
388       tr("There are unsaved changes,\ndo you want to save?"),
389       tr("&Yes"), tr("&No"), tr("&Cancel"),
390       0, 2)) {
391     case 0:
392       // save
393       saveAnalysis();
394       break;
395     case 1:
396       // don't save
397       break;
398     case 2:
399       // don't replace 
400       return false;
401       break;
402     default:
403       // error
404       throw runtime_error("isClearingAnalysis QMesageBox failure");
405     }
406   } 
407   // if we're here we've been saved and can replace
408   return true;
409 }
410
411 void MussaWindow::editMotifs()
412 {
413   if (motif_editor != 0) {
414     motif_editor->hide();
415     delete motif_editor;
416   }
417   motif_editor = new MotifEditor(analysis);
418   connect(motif_editor, SIGNAL(changedMotifs()), 
419           this, SLOT(updateAnnotations()));
420   motif_editor->show();
421 }
422
423 void MussaWindow::loadMotifList()
424 {
425   QString caption("Load a motif list");
426   QString filter("Motif list(*.txt *.mtl)");
427   QDir default_qdir(default_dir.native_directory_string().c_str());
428   QString path = QFileDialog::getOpenFileName(this,
429                                               caption, 
430                                               default_qdir.absolutePath(),
431                                               filter);
432   // user hit cancel?
433   if (path.isNull()) 
434     return;
435   // try to load safely
436   try {
437     fs::path converted_path(path.toStdString(), fs::native);
438     analysis->load_motifs(converted_path);
439     default_dir = converted_path.branch_path();
440   } catch (runtime_error e) {
441     QString msg("Unable to load ");
442     msg += path;
443     msg += "\n";
444     msg += e.what();
445     QMessageBox::warning(this, "Load Motifs", msg);
446   }
447   assert (analysis != 0);
448 }
449
450 void MussaWindow::saveMotifList()
451 {
452   NotImplementedBox();
453 }
454
455 void MussaWindow::loadMupa()
456 {
457   QString caption("Load a mussa parameter file");
458   QString filter("Mussa Parameters (*.mupa)");
459   QDir default_qdir(QDir(default_dir.native_directory_string().c_str()));
460
461   QString mupa_path = QFileDialog::getOpenFileName(
462                          this,
463                          caption, 
464                          default_qdir.absolutePath(),
465                          filter
466                       );
467   // user hit cancel?
468   if (mupa_path.isNull()) 
469     return;
470   // try to load safely
471   try {
472     // ideally we should open a new window if there's an analysis
473     // but this should work for the moment.
474     if (not isClearingAnalysisSafe()) return;
475
476     Mussa *m = new Mussa;
477     fs::path converted_path(mupa_path.toStdString(), fs::native);
478     connect(m, SIGNAL(progress(const std::string&, int, int)),
479             this, SLOT(updateProgress(const std::string&, int, int)));
480     m->load_mupa_file(converted_path);
481     m->analyze();
482     setAnalysis(m);
483     setWindowTitle(converted_path.native_file_string().c_str());
484     // grab the path ignoring the mupa file portion
485     default_dir = converted_path.branch_path();
486   } catch (mussa_load_error e) {
487     QString msg("Unable to load ");
488     msg += mupa_path;
489     msg += "\n";
490     msg += e.what();
491     QMessageBox::warning(this, "Load Parameter", msg);
492   }
493   assert (analysis != 0);
494 }
495
496 void MussaWindow::loadSavedAnalysis()
497 {
498   QDir default_qdir(QDir(default_dir.native_directory_string().c_str()));
499   QString caption("Load a previously run analysis");
500   QString muway_dir = QFileDialog::getExistingDirectory(
501                         this,
502                         caption, 
503                         default_qdir.absolutePath()
504                       );
505   // user hit cancel?
506   if (muway_dir.isNull()) 
507     return;
508   // try to safely load
509   try {
510     // ideally we should open a new window if there's an analysis
511     // but this should work for the moment.
512     if (not isClearingAnalysisSafe()) return;
513
514     Mussa *m = new Mussa;
515     fs::path converted_path(muway_dir.toStdString(), fs::native);
516     connect(m, SIGNAL(progress(const std::string&, int, int)),
517             this, SLOT(updateProgress(const std::string&, int, int)));
518     m->load(converted_path);
519     // only switch mussas if we loaded without error
520     if (analysis->empty()) {
521       // our current window is empty so load and replace.
522       setAnalysis(m);
523       setWindowTitle(converted_path.native_file_string().c_str());
524       default_dir = converted_path.branch_path();
525     } else {
526       MussaWindow *win = new MussaWindow(m);
527       win->setWindowTitle(converted_path.native_file_string().c_str());
528       win->default_dir = converted_path.branch_path();
529       win->show();
530     }
531   } catch (mussa_load_error e) {
532     QString msg("Unable to load ");
533     msg += muway_dir;
534     msg += "\n";
535     msg += e.what();
536     QMessageBox::warning(this, "Load Parameter", msg);
537   }
538   assert (analysis != 0);
539 }
540
541 void MussaWindow::newMussaWindow()
542 {
543   Mussa *a = new Mussa();
544   MussaWindow *win = new MussaWindow(a);
545   win->default_dir = default_dir;
546   win->show();
547 }
548
549 void MussaWindow::setSoftThreshold(int threshold)
550 {
551   if (analysis->get_soft_threshold() != threshold) {
552     analysis->set_soft_threshold(threshold);
553     analysis->nway();
554     updateLinks();
555     update();
556   }
557 }
558
559 void MussaWindow::showMussaToolbar()
560 {
561   if (mussaViewTB.isVisible())
562     mussaViewTB.hide();
563   else
564     mussaViewTB.show();
565 }
566
567 void MussaWindow::toggleMotifs()
568 {
569   NotImplementedBox();
570 }
571
572 void MussaWindow::showManual()
573 {
574 #if QT_QTASSISTANT_FOUND
575   manualAssistant->openAssistant();
576 #else
577   try {
578     boost::python::object webopen = get_py()["webbrowser.open"];
579     webopen("http://woldlab.caltech.edu/~king/mussagl_manual/");
580   } catch( boost::python::error_already_set ) {
581     PyErr_Print();
582   }
583 #endif //QT_QTASSISTANT_FOUND
584 }
585
586 void MussaWindow::assistantError(QString message)
587 {
588   //std::cout << "QAssistantError: " << message.toStdString() << "\n";
589   QMessageBox::warning ( this, "Warning: Mussagl Manual", message, 
590                          QMessageBox::Ok, 
591                          QMessageBox::NoButton);
592 }
593
594 void MussaWindow::NotImplementedBox()
595 {
596   QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
597 }      
598
599 void MussaWindow::viewMussaAlignment()
600 {
601   const set<int>& selected_paths = browser.selectedPaths();
602   if (selected_paths.size() == 0 ) {
603     QMessageBox::warning(this, 
604                          QObject::tr("mussa"),
605                          QObject::tr("you should probably select some paths "
606                                      "first"));
607   } else {
608     boost::shared_ptr<MussaAlignedWindow> ma_win( 
609       new MussaAlignedWindow(*analysis, selected_paths)
610     );
611
612     aligned_windows.push_back(ma_win);
613     connect(this, SIGNAL(changedAnnotations()), 
614             aligned_windows.back().get(), SLOT(update()));
615     aligned_windows.back()->show();
616   }
617 }
618                         
619 void MussaWindow::updateAnalysis()
620 {
621   threshold.setRange(analysis->get_threshold(),analysis->get_window());
622
623   const Mussa::vector_sequence_type& seqs = analysis->sequences();
624   browser.setSequences(seqs, analysis->colorMapper());
625   updateLinks();
626   browser.zoomOut();
627   zoom.setValue(browser.zoom());
628 }
629
630 void MussaWindow::updateAnnotations()
631 {
632   // motifs were changed in the sequences by 
633   // Mussa::update_sequences_motifs
634   emit changedAnnotations();
635   browser.update();
636 }
637
638 void MussaWindow::updateLinks()
639 {
640   browser.clear_links();
641   bool reversed = false;
642   const NwayPaths& nway = analysis->paths();
643
644   typedef list<ConservedPath> conserved_paths;
645   typedef conserved_paths::const_iterator const_conserved_paths_itor;
646   for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
647       path_itor != nway.refined_pathz.end();
648       ++path_itor)
649   {
650     // since we were drawing to the start of a window, and opengl lines
651     // are centered around the two connecting points our lines were slightly
652     // offset. the idea of window_offset is to adjust them to the
653     // right for forward compliment or left for reverse compliment
654     // FIXME: figure out how to unit test these computations
655     //GLfloat window_offset = (path_itor->window_size)/2.0;
656
657     size_t track_len = path_itor->track_indexes.size();
658     vector<int> normalized_path;
659     normalized_path.reserve(track_len);
660     vector<bool> rc_flags(false, track_len);
661     for (size_t track_i=0; track_i != track_len; ++track_i)
662     {
663       int x = path_itor->track_indexes[track_i];
664       // at some point when we modify the pathz data structure to keep
665       // track of the score we can put grab the depth here.
666       //
667       // are we reverse complimented?
668       if ( x>=0) {
669         reversed = false;
670       } else {
671         reversed = true;
672         // make positive 
673         x = -x;
674       }
675       normalized_path.push_back(x);
676       rc_flags.push_back(reversed);
677     }
678     browser.link(normalized_path, rc_flags, path_itor->window_size);
679   }
680   browser.update();
681 }
682
683 void 
684 MussaWindow::updateProgress(const string& description, int current, int max)
685 {  
686   // if we're done  
687   if (current == max) {
688     if (progress_dialog != 0) {
689       progress_dialog->hide();
690       delete progress_dialog;
691       progress_dialog = 0;
692     }
693   } else {
694     // if we're starting, create the dialog
695     if (progress_dialog == 0) {
696       QString desc(description.c_str());
697       QString cancel("Cancel");
698       progress_dialog = new QProgressDialog(desc, cancel, current, max, this);
699       progress_dialog->show();
700     } else {
701       // just update the dialog
702       progress_dialog->setValue(current);
703     }
704   }
705   qApp->processEvents();
706 }