Apparently I lost a } in a Mac OS X specific section of the code. Sowwy. Fixed.
[mussa.git] / qui / MussaWindow.cpp
index 2ab6299d925ac36226d56a69593b6bb13c8005e9..75da849d591416a25371c980e9454b8c46e737af 100644 (file)
@@ -1,4 +1,3 @@
-#include "py/python.hpp"
 #include "qui/MussaWindow.hpp"
 #include "mussa_exceptions.hpp"
 #include "version.hpp"
@@ -7,16 +6,21 @@
 #include <QApplication>
 #include <QAssistantClient>
 #include <QCloseEvent>
+#include <QDesktopServices>
 #include <QDir>
 #include <QFileDialog>
 #include <QHBoxLayout>
 #include <QIcon>
+#include <QLibraryInfo>
 #include <QMenuBar>
 #include <QMessageBox>
+#include <QProcess>
 #include <QScrollBar>
 #include <QStatusBar>
 #include <QString>
 #include <QStringList>
+#include <QTextStream>
+#include <QUrl>
 #include <QWhatsThis>
 
 #include <memory>
@@ -31,17 +35,23 @@ namespace fs = boost::filesystem;
 
 using namespace std;
 
+static void init_resources() {
+  static bool resources_loaded = false;
+  if (not resources_loaded) {
+    Q_INIT_RESOURCE(icons);
+    resources_loaded = true;
+  }
+}
+
 MussaWindow::MussaWindow(MussaRef analysis_, QWidget *parent) :
   QMainWindow(parent),
   analysis(analysis_),
-  default_dir(new QDir(QDir::home().absolutePath())),
   motif_editor(0),
-  setup_analysis_dialog(new MussaSetupDialog(this)),
-  subanalysis_window(new SubanalysisWindow(analysis)),
-  browser(new SequenceBrowserWidget(default_dir, this)),  
-  mussaViewTB(new QToolBar("Path Views")),
-  zoom(new ZoomWidget),
-  threshold(new ThresholdWidget),
+  setup_analysis_dialog(0),
+  browser(0),
+  mussaViewTB(0),
+  zoom(0),
+  threshold(0),
   progress_dialog(0),
   aboutAction(0),
   closeAction(0),
@@ -59,34 +69,35 @@ MussaWindow::MussaWindow(MussaRef analysis_, QWidget *parent) :
   saveBrowserPixmapAction(0),
   whatsThisAction(0),
   viewMussaAlignmentAction(0),
-  manualAssistant(0)
+  assistantProcess(0)
 {
+  init_resources();
+  
+  default_dir.reset(new QDir(QDir::home().absolutePath()));
+  
+  setupWidgets();
   setupActions();
-  setupMainMenu();
   setupAssistant();
+  setupMainMenu();
+  
+  setWindowIcon(QIcon(":/icons/mussa.png"));
 
-  //This next setWhatsThis function prevents
-  // a segfault when using WhatsThis feature with 
-  // opengl widget.
-  //scene->setWhatsThis(tr("Mussa in OpenGL!"));
   setCentralWidget(browser);
   // well updatePosition isn't quite right as we really just need
   // to call update()
   connect(this, SIGNAL(changedAnnotations()), browser, SLOT(update()));
   connect(this, SIGNAL(changedMotifs()), this, SLOT(updateAnnotations()));
-
-  //mussaViewTB->addAction(toggleMotifsAction);
-  mussaViewTB->addWidget(zoom);
-  
+  connect(browser, SIGNAL(basepairsCopied(size_t)), 
+          this, SLOT(showBasePairsCopied(size_t)));
   connect(zoom, SIGNAL(valueChanged(double)), 
           browser, SLOT(setZoom(double)));
+  mussaViewTB->addWidget(zoom);
   
-  // threshold range is set in updateAnalysis
-  
-  //scene->setClipPlane(20);
-  // FIXME: for when we get the paths drawn at the appropriate depth
-  //connect(threshold, SIGNAL(thresholdChanged(int)),
-  //        this, SLOT(setClipPlane(int)));
+  // Mouse Wheel triggered zooming
+  connect(browser, SIGNAL(mouseWheelZoom(double)),
+          zoom, SLOT(setValue(double)));
+
+  // threshold range is set in updateAnalysis  
   connect(threshold, SIGNAL(thresholdChanged(int)),
           this, SLOT(setSoftThreshold(int)));
   mussaViewTB->addWidget(threshold);
@@ -94,10 +105,13 @@ MussaWindow::MussaWindow(MussaRef analysis_, QWidget *parent) :
   addToolBar(mussaViewTB);
 
   statusBar()->showMessage("Welcome to mussa", 2000);
+
   // FIXME: we should start refactoring the connect call to updateAnalysis or something
   if (analysis) {
-    connect(analysis.get(), SIGNAL(progress(const std::string&, int, int)),
-            this, SLOT(updateProgress(const std::string&, int, int)));
+    connect(analysis.get(), SIGNAL(progress(const QString&, int, int)),
+            this, SLOT(updateProgress(const QString&, int, int)));
+    connect(analysis.get(), SIGNAL(isModified(bool)),
+            this, SLOT(updateAnalysisModified(bool)));        
   }
   updateTitle();
   updateAnalysis();
@@ -108,7 +122,18 @@ void MussaWindow::setAnalysis(MussaRef new_analysis)
   if (new_analysis != 0) {
     // only switch mussas if we loaded without error
     clear();
+    //std::cout << "analysis soft: " << analysis->get_soft_threshold()
+    //          << " new analysis soft: " << new_analysis->get_soft_threshold()
+    //          << "\n";
     analysis.swap(new_analysis);
+    //std::cout << "after swap soft thres: " << analysis->get_soft_threshold()
+    //          << "\n";
+    threshold->disconnect(this);
+    threshold->reset(analysis->get_threshold(),
+                     analysis->get_window(),
+                     analysis->get_soft_threshold());
+    connect(threshold, SIGNAL(thresholdChanged(int)),
+          this, SLOT(setSoftThreshold(int)));
     updateTitle();
     updateAnalysis();
   }
@@ -130,10 +155,11 @@ void MussaWindow::setupActions()
   connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
   closeAction->setIcon(QIcon(":/icons/exit.png"));
   
-  createNewAnalysisAction = new QAction(tr("Create Analysis"), this);
+  createNewAnalysisAction = new QAction(tr("Create &New Analysis"), this);
   connect(createNewAnalysisAction, SIGNAL(triggered()), 
           this, SLOT(createNewAnalysis()));
   createNewAnalysisAction->setIcon(QIcon(":/icons/filenew.png"));
+  createNewAnalysisAction->setShortcut(Qt::CTRL | Qt::Key_N);
   
   createSubAnalysisAction = new QAction(tr("Add to Subanalysis"), this);
   connect(createSubAnalysisAction, SIGNAL(triggered()), 
@@ -143,6 +169,7 @@ void MussaWindow::setupActions()
   connect(saveAnalysisAction, SIGNAL(triggered()), 
           this, SLOT(saveAnalysis()));
   saveAnalysisAction->setIcon(QIcon(":/icons/filesave.png"));
+  saveAnalysisAction->setShortcut(Qt::CTRL | Qt::Key_S);
 
   saveAnalysisAsAction = new QAction(tr("Save Analysis &As"), this);
   connect(saveAnalysisAsAction, SIGNAL(triggered()), 
@@ -152,7 +179,7 @@ void MussaWindow::setupActions()
   editMotifsAction = new QAction(tr("Edit Motifs"), this);;
   connect(editMotifsAction, SIGNAL(triggered()), this, SLOT(editMotifs()));
   
-  loadMotifListAction = new QAction(tr("Load Motif List"), this);
+  loadMotifListAction = new QAction(tr("Open Motif List"), this);
   connect(loadMotifListAction, SIGNAL(triggered()), 
           this, SLOT(loadMotifList()));
   loadMotifListAction->setIcon(QIcon(":/icons/fileopen.png"));
@@ -162,15 +189,16 @@ void MussaWindow::setupActions()
           this, SLOT(loadMupa()));
   loadMupaAction->setIcon(QIcon(":/icons/fileopen.png"));
 
-  loadSavedAnalysisAction = new QAction(tr("Load Existing &Analysis"), this);
+  loadSavedAnalysisAction = new QAction(tr("&Open Existing &Analysis"), this);
   connect(loadSavedAnalysisAction, SIGNAL(triggered()), 
           this, SLOT(loadSavedAnalysis()));
   loadSavedAnalysisAction->setIcon(QIcon(":/icons/fileopen.png"));
+  loadSavedAnalysisAction->setShortcut(Qt::CTRL | Qt::Key_O);
 
   mussaManualAssistantAction = new QAction(tr("Mussagl Manual..."), this);
   mussaManualAssistantAction->setIcon(QIcon(":/icons/contents.png"));
   connect(mussaManualAssistantAction, SIGNAL(triggered()),
-         this, SLOT(showManual()));
+               this, SLOT(showManual()));
 
   newMussaWindowAction = new QAction(tr("&New Mussa Window"), this);
   newMussaWindowAction->setStatusTip("open another mussa window to allow comparing results");
@@ -200,9 +228,11 @@ void MussaWindow::setupActions()
 
   //Save pixel map action
   saveBrowserPixmapAction = new QAction(tr("Save to image..."), this);
-  connect(saveBrowserPixmapAction, (SIGNAL(triggered())),
-         browser, SLOT(promptSaveBrowserPixmap()));
-  saveBrowserPixmapAction->setIcon(QIcon(":/icons/image2.png"));
+  if (browser) {
+    connect(saveBrowserPixmapAction, (SIGNAL(triggered())),
+            browser, SLOT(promptSaveBrowserPixmap()));
+    saveBrowserPixmapAction->setIcon(QIcon(":/icons/image2.png"));
+  }
 
   viewMussaAlignmentAction = new QAction(tr("View sequence alignment"), this);
   connect(viewMussaAlignmentAction, SIGNAL(triggered()),
@@ -249,8 +279,10 @@ void MussaWindow::setupMainMenu()
 
   newMenu = menuBar()->addMenu(tr("&Edit"));
   newMenu->addAction(editMotifsAction);
-  newMenu->addAction(browser->getCopySelectedSequenceAsFastaAction());
+  if (browser) newMenu->addAction(browser->getCopySelectedSequenceAsStringAction());
+  if (browser) newMenu->addAction(browser->getCopySelectedSequenceAsFastaAction());
   newMenu->addAction(createSubAnalysisAction);
+  if (browser) newMenu->addAction(browser->getEditSequencePropertiesAction());
  
   newMenu = menuBar()->addMenu(tr("&View"));
   newMenu->addAction(viewMussaAlignmentAction);
@@ -263,16 +295,29 @@ void MussaWindow::setupMainMenu()
   newMenu->addAction(aboutAction);
 
   // add some extra features to the context menu
-  QMenu *popupMenu = browser->getPopupMenu();
-  if (popupMenu) {
-    popupMenu->addAction(viewMussaAlignmentAction);
-    popupMenu->addAction(createSubAnalysisAction);
+  if (browser) {
+    QMenu *popupMenu = browser->getPopupMenu();
+    if (popupMenu) {
+      popupMenu->addAction(viewMussaAlignmentAction);
+      popupMenu->addAction(createSubAnalysisAction);
+    }
   }
 }
 
+void MussaWindow::setupWidgets()
+{
+  setup_analysis_dialog = new MussaSetupDialog;
+  subanalysis_window.reset(new SubanalysisWindow(analysis));
+  browser = new SequenceBrowserWidget(default_dir);
+  mussaViewTB = new QToolBar("Path Views", this);
+  zoom = new ZoomWidget(mussaViewTB);
+  threshold = new ThresholdWidget(mussaViewTB);
+}
+
 void MussaWindow::setupAssistant()
 {
 #if defined(QT_QTASSISTANT_FOUND)
+  /*
   QStringList manualAssistantArgs;
   manualAssistantArgs = QStringList();
   manualAssistantArgs << "-profile" << "./doc/manual/mussagl_manual.adp";
@@ -280,6 +325,7 @@ void MussaWindow::setupAssistant()
   manualAssistant->setArguments(manualAssistantArgs);
   connect(manualAssistant, SIGNAL(error(QString)),
          this, SLOT(assistantError(QString)));
+  */
 #endif
 }
   
@@ -292,6 +338,9 @@ void MussaWindow::about()
   msg += "Version: ";
   msg += mussa_version;
   msg += "\n";
+  msg += "Qt: ";
+  msg += qVersion();
+  msg += "\n";
   msg += "OpenGL: ";
   msg += (char *)glGetString(GL_VERSION);
   msg += "\n";
@@ -300,6 +349,11 @@ void MussaWindow::about()
 
 void MussaWindow::clear()
 {
+  if (motif_editor != 0) {
+    motif_editor->hide();
+    delete motif_editor;
+  }
+  
   aligned_windows.clear();
   browser->clear();
 }
@@ -349,7 +403,15 @@ void MussaWindow::saveAnalysis()
       // this doesn't work when a sequence changes something (like its
       // name)
       //else if (analysis->is_dirty())
-      analysis->save();
+      try { 
+        analysis->save();
+      } catch (std::exception e) {
+        QMessageBox::critical(this, 
+                              tr("Mussa Save Error"),
+                              tr(e.what()),
+                              QMessageBox::Ok, 0, 0);
+  }
+      
     }
   }
 }
@@ -370,23 +432,30 @@ void MussaWindow::saveAnalysisAs()
   if (fileNames.size() != 1) {
     return;
   }
-  fs::path save_path(fileNames[0].toStdString(), fs::native);
-  // do you want to overwrite?
-  if (fs::exists(save_path) and 
-      QMessageBox::question(
-        this,
-        tr("Overwrite File? -- Mussa"),
-        tr("A file called %1 already exists"
-           "do you want to overwrite it?")
-           .arg(fileNames[0]),
-        tr("&Yes"), tr("&No"),
-        QString(), 0, 1)
-      ) {
-    return;
+  try {
+    fs::path save_path(fileNames[0].toStdString(), fs::native);
+    // do you want to overwrite?
+    if (fs::exists(save_path) and 
+        QMessageBox::question(
+          this,
+          tr("Overwrite File? -- Mussa"),
+          tr("A file called %1 already exists"
+             "do you want to overwrite it?")
+             .arg(fileNames[0]),
+          tr("&Yes"), tr("&No"),
+          QString(), 0, 1)
+        ) {
+      return;
+    }
+    analysis->save(save_path);
+    fs::path normalized_path = (save_path / "..").normalize();   
+    default_dir->setPath(normalized_path.native_directory_string().c_str());
+  } catch (std::exception e) {
+    QMessageBox::critical(this, 
+                          tr("Mussa Save Error"),
+                          tr(e.what()),
+                          QMessageBox::Ok, 0, 0);
   }
-  analysis->save(save_path);
-  fs::path normalized_path = (save_path / "..").normalize();   
-  default_dir->setPath(normalized_path.native_directory_string().c_str());
 }
 
 bool MussaWindow::isClearingAnalysisSafe()
@@ -420,13 +489,11 @@ bool MussaWindow::isClearingAnalysisSafe()
 
 void MussaWindow::editMotifs()
 {
-  if (motif_editor != 0) {
-    motif_editor->hide();
-    delete motif_editor;
+  if (not motif_editor) {
+    motif_editor = new MotifEditor(analysis);
+    connect(motif_editor, SIGNAL(changedMotifs()), 
+            this, SLOT(updateAnnotations()));
   }
-  motif_editor = new MotifEditor(analysis);
-  connect(motif_editor, SIGNAL(changedMotifs()), 
-          this, SLOT(updateAnnotations()));
   motif_editor->show();
 }
 
@@ -447,7 +514,7 @@ void MussaWindow::loadMotifList()
     analysis->load_motifs(converted_path);
     default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
     emit changedMotifs();
-  } catch (runtime_error e) {
+  } catch (std::exception e) {
     QString msg("Unable to load ");
     msg += path;
     msg += "\n";
@@ -472,11 +539,13 @@ void MussaWindow::saveMotifList()
     fs::path converted_path(path.toStdString(), fs::native);
     if (fs::extension(converted_path).size() == 0) {
       // no extension, so add one
-      converted_path = converted_path.string() + ".mtl";
+      fs::path base_path = converted_path.branch_path();
+      fs::path filename(converted_path.leaf() + ".mtl", fs::native);
+      converted_path = base_path / filename;
     }
     analysis->save_motifs(converted_path);
     default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
-  } catch (runtime_error e) {
+  } catch (std::exception e) {
     QString msg("Unable to save ");
     msg += path;
     msg += "\n";
@@ -503,10 +572,10 @@ void MussaWindow::loadMupa()
     // but this should work for the moment.
     if (not isClearingAnalysisSafe()) return;
 
-    MussaRef m(new Mussa);
+    MussaRef m = Mussa::init();
     fs::path converted_path(mupa_path.toStdString(), fs::native);
-    connect(m.get(), SIGNAL(progress(const std::string&, int, int)),
-            this, SLOT(updateProgress(const std::string&, int, int)));
+    connect(m.get(), SIGNAL(progress(const QString&, int, int)),
+            this, SLOT(updateProgress(const QString&, int, int)));
     m->load_mupa_file(converted_path);
     m->analyze();
     setAnalysis(m);
@@ -540,10 +609,10 @@ void MussaWindow::loadSavedAnalysis()
     // but this should work for the moment.
     if (not isClearingAnalysisSafe()) return;
 
-    MussaRef m(new Mussa);
+    MussaRef m = Mussa::init();
     fs::path converted_path(muway_dir.toStdString(), fs::native);
-    connect(m.get(), SIGNAL(progress(const std::string&, int, int)),
-            this, SLOT(updateProgress(const std::string&, int, int)));
+    connect(m.get(), SIGNAL(progress(const QString&, int, int)),
+            this, SLOT(updateProgress(const QString&, int, int)));
     m->load(converted_path);
     // only switch mussas if we loaded without error
     if (analysis->empty()) {
@@ -557,6 +626,12 @@ void MussaWindow::loadSavedAnalysis()
       win->default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
       win->show();
     }
+  } catch (boost::filesystem::filesystem_error e) {
+    QString msg("Unable to load ");
+    msg += muway_dir;
+    msg += "\n";
+    msg += e.what();
+    QMessageBox::warning(this, "Load Parameter", msg);    
   } catch (mussa_load_error e) {
     QString msg("Unable to load ");
     msg += muway_dir;
@@ -569,20 +644,28 @@ void MussaWindow::loadSavedAnalysis()
 
 void MussaWindow::newMussaWindow()
 {
-  MussaRef a(new Mussa);
-  MussaWindow *win = new MussaWindow(a);
+  MussaWindow *win = new MussaWindow(Mussa::init());
   win->default_dir = default_dir;
   win->show();
 }
 
-void MussaWindow::setSoftThreshold(int threshold)
+void MussaWindow::setSoftThreshold(int value)
 {
-  if (analysis->get_soft_threshold() != threshold) {
-    analysis->set_soft_threshold(threshold);
+  //std::cout << "Soft: " << analysis->get_soft_threshold()
+  //          << " Value: " << value << "\n";
+  if (analysis->get_soft_threshold() != value) {
+    threshold->setEnabled( false );
+    //std::cout << "Updating!!!!\n";
+    analysis->set_soft_threshold(value);
     analysis->nway();
     updateLinks();
     update();
+    threshold->setEnabled( true );
   }
+  //else
+  //{
+  //  std::cout << "NOT Updating!!!!\n";
+  //}
 }
 
 void MussaWindow::showMussaToolbar()
@@ -593,6 +676,16 @@ void MussaWindow::showMussaToolbar()
     mussaViewTB->show();
 }
 
+void MussaWindow::showBasePairsCopied(size_t bp_copied)
+{
+  QString msg("Copied ");
+  QString num;
+  num.setNum(bp_copied);
+  msg += num + " base pairs";
+  statusBar()->showMessage(msg, 5000);
+}
+
+
 void MussaWindow::toggleMotifs()
 {
   NotImplementedBox();
@@ -601,22 +694,35 @@ void MussaWindow::toggleMotifs()
 void MussaWindow::showManual()
 {
 #if defined(QT_QTASSISTANT_FOUND)
-  if (manualAssistant) { 
-    manualAssistant->openAssistant();
-  } else {
-    QMessageBox::warning(this,
-                         tr("Mussa Help Error"),
-                         tr("QtAssistant not setup correctly"),
-                         QMessageBox::Ok,
-                         QMessageBox::NoButton,
-                         QMessageBox::NoButton);
+  
+  // Only define the process once.
+  if (!assistantProcess)
+    assistantProcess = new QProcess(this);
+  
+  // No need to fire up the process again if it is already running.
+  if (assistantProcess->state() == QProcess::Running)
+    return;
+  
+  QString app = QLibraryInfo::location(QLibraryInfo::BinariesPath)
+      + QLatin1String("/assistant");
+
+  assistantProcess->start(app, QStringList() << QLatin1String("-enableRemoteControl")
+                           << QLatin1String("-collectionFile") << QLatin1String("mussagl_manual.qhc"));
+  if (!assistantProcess->waitForStarted()) {
+      QMessageBox::critical(this, tr("Remote Control"),
+         tr("Could not start Qt Assistant from %1.").arg(app));
+      return;
   }
+
+  // show index page
+  QTextStream str(assistantProcess);
+  str << QLatin1String("SetSource qthelp://edu.caltech.woldlab.mussagl.1_0_0/doc/mussagl_manual.html;")
+      << QLatin1String("expandToc 0")
+      << QLatin1Char('\0') << endl;
+
 #else
-  try {
-    boost::python::object webopen = get_py()["webbrowser.open"];
-    webopen("http://woldlab.caltech.edu/~king/mussagl_manual/");
-  } catch( boost::python::error_already_set ) {
-    PyErr_Print();
+  QUrl manual_url("http://woldlab.caltech.edu/~king/mussagl_manual/");
+  if (not QDesktopServices::openUrl(manual_url)) {
     QMessageBox::warning(this,
                          tr("Mussa Help Error"),
                          tr("Unable to launch webbrowser"),
@@ -670,6 +776,7 @@ void MussaWindow::updateAnalysis()
   // but it's possible for us to not have had a chance to set out sequences
   // yet.
   threshold->setRange(analysis->get_threshold(),analysis->get_window());
+  threshold->setBasepairThreshold(analysis->get_soft_threshold());
   updateLinks();
   browser->zoomOut();
   zoom->setValue(browser->zoom());
@@ -734,7 +841,7 @@ void MussaWindow::updateLinks()
 }
 
 void 
-MussaWindow::updateProgress(const string& description, int current, int max)
+MussaWindow::updateProgress(const QString& description, int current, int max)
 {  
   // if we're done  
   if (current == max) {
@@ -746,9 +853,8 @@ MussaWindow::updateProgress(const string& description, int current, int max)
   } else {
     // if we're starting, create the dialog
     if (progress_dialog == 0) {
-      QString desc(description.c_str());
       QString cancel("Cancel");
-      progress_dialog = new QProgressDialog(desc, cancel, current, max, this);
+      progress_dialog = new QProgressDialog(description, cancel, current, max, this);
       progress_dialog->show();
     } else {
       // just update the dialog
@@ -758,9 +864,16 @@ MussaWindow::updateProgress(const string& description, int current, int max)
   qApp->processEvents();
 }
 
+void MussaWindow::updateAnalysisModified(bool is_modified)
+{
+  setWindowModified(is_modified);
+}
+
 void MussaWindow::updateTitle()
 {
   if (analysis) {
-    setWindowTitle(analysis->get_title().c_str());
+    QString title(analysis->get_title().c_str());
+    title += "[*]";
+    setWindowTitle(title);
   }
 }