if you need to do a save as, you really don't need to save too
[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     setAnalysis(m);
521     setWindowTitle(converted_path.native_file_string().c_str());
522     default_dir = converted_path.branch_path();
523   } catch (mussa_load_error e) {
524     QString msg("Unable to load ");
525     msg += muway_dir;
526     msg += "\n";
527     msg += e.what();
528     QMessageBox::warning(this, "Load Parameter", msg);
529   }
530   assert (analysis != 0);
531 }
532
533 void MussaWindow::newMussaWindow()
534 {
535   Mussa *a = new Mussa();
536   MussaWindow *win = new MussaWindow(a);
537   win->show();
538 }
539
540 void MussaWindow::setSoftThreshold(int threshold)
541 {
542   if (analysis->get_soft_threshold() != threshold) {
543     analysis->set_soft_threshold(threshold);
544     analysis->nway();
545     updateLinks();
546     update();
547   }
548 }
549
550 void MussaWindow::showMussaToolbar()
551 {
552   if (mussaViewTB.isVisible())
553     mussaViewTB.hide();
554   else
555     mussaViewTB.show();
556 }
557
558 void MussaWindow::toggleMotifs()
559 {
560   NotImplementedBox();
561 }
562
563 void MussaWindow::showManual()
564 {
565 #if QT_QTASSISTANT_FOUND
566   manualAssistant->openAssistant();
567 #else
568   try {
569     boost::python::object webopen = get_py()["webbrowser.open"];
570     webopen("http://woldlab.caltech.edu/~king/mussagl_manual/");
571   } catch( boost::python::error_already_set ) {
572     PyErr_Print();
573   }
574 #endif //QT_QTASSISTANT_FOUND
575 }
576
577 void MussaWindow::assistantError(QString message)
578 {
579   //std::cout << "QAssistantError: " << message.toStdString() << "\n";
580   QMessageBox::warning ( this, "Warning: Mussagl Manual", message, 
581                          QMessageBox::Ok, 
582                          QMessageBox::NoButton);
583 }
584
585 void MussaWindow::NotImplementedBox()
586 {
587   QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
588 }      
589
590 void MussaWindow::viewMussaAlignment()
591 {
592   const set<int>& selected_paths = browser.selectedPaths();
593   if (selected_paths.size() == 0 ) {
594     QMessageBox::warning(this, 
595                          QObject::tr("mussa"),
596                          QObject::tr("you should probably select some paths "
597                                      "first"));
598   } else {
599     boost::shared_ptr<MussaAlignedWindow> ma_win( 
600       new MussaAlignedWindow(*analysis, selected_paths)
601     );
602
603     aligned_windows.push_back(ma_win);
604     connect(this, SIGNAL(changedAnnotations()), 
605             aligned_windows.back().get(), SLOT(update()));
606     aligned_windows.back()->show();
607   }
608 }
609                         
610 void MussaWindow::updateAnalysis()
611 {
612   threshold.setRange(analysis->get_threshold(),analysis->get_window());
613
614   const Mussa::vector_sequence_type& seqs = analysis->sequences();
615   browser.setSequences(seqs, analysis->colorMapper());
616   updateLinks();
617   browser.zoomOut();
618   zoom.setValue(browser.zoom());
619 }
620
621 void MussaWindow::updateAnnotations()
622 {
623   // motifs were changed in the sequences by 
624   // Mussa::update_sequences_motifs
625   emit changedAnnotations();
626   browser.update();
627 }
628
629 void MussaWindow::updateLinks()
630 {
631   browser.clear_links();
632   bool reversed = false;
633   const NwayPaths& nway = analysis->paths();
634
635   typedef list<ConservedPath> conserved_paths;
636   typedef conserved_paths::const_iterator const_conserved_paths_itor;
637   for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
638       path_itor != nway.refined_pathz.end();
639       ++path_itor)
640   {
641     // since we were drawing to the start of a window, and opengl lines
642     // are centered around the two connecting points our lines were slightly
643     // offset. the idea of window_offset is to adjust them to the
644     // right for forward compliment or left for reverse compliment
645     // FIXME: figure out how to unit test these computations
646     //GLfloat window_offset = (path_itor->window_size)/2.0;
647
648     size_t track_len = path_itor->track_indexes.size();
649     vector<int> normalized_path;
650     normalized_path.reserve(track_len);
651     vector<bool> rc_flags(false, track_len);
652     for (size_t track_i=0; track_i != track_len; ++track_i)
653     {
654       int x = path_itor->track_indexes[track_i];
655       // at some point when we modify the pathz data structure to keep
656       // track of the score we can put grab the depth here.
657       //
658       // are we reverse complimented?
659       if ( x>=0) {
660         reversed = false;
661       } else {
662         reversed = true;
663         // make positive 
664         x = -x;
665       }
666       normalized_path.push_back(x);
667       rc_flags.push_back(reversed);
668     }
669     browser.link(normalized_path, rc_flags, path_itor->window_size);
670   }
671   browser.update();
672 }
673
674 void 
675 MussaWindow::updateProgress(const string& description, int current, int max)
676 {  
677   // if we're done  
678   if (current == max) {
679     if (progress_dialog != 0) {
680       progress_dialog->hide();
681       delete progress_dialog;
682       progress_dialog = 0;
683     }
684   } else {
685     // if we're starting, create the dialog
686     if (progress_dialog == 0) {
687       QString desc(description.c_str());
688       QString cancel("Cancel");
689       progress_dialog = new QProgressDialog(desc, cancel, current, max, this);
690       progress_dialog->show();
691     } else {
692       // just update the dialog
693       progress_dialog->setValue(current);
694     }
695   }
696   qApp->processEvents();
697 }