Apparently I lost a } in a Mac OS X specific section of the code. Sowwy. Fixed.
[mussa.git] / qui / MussaWindow.cpp
index 6565aefbf4522b9bc1601e7b7895305c12f6748d..75da849d591416a25371c980e9454b8c46e737af 100644 (file)
+#include "qui/MussaWindow.hpp"
+#include "mussa_exceptions.hpp"
+#include "version.hpp"
+
 #include <QAction>
+#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 "qui/MussaWindow.hpp"
-#include "mussa_exceptions.hpp"
-
+#include <memory>
+#include <iterator>
 #include <iostream>
 
+#include <boost/filesystem/path.hpp>
+#include <boost/filesystem/operations.hpp>
+#include <boost/filesystem/convenience.hpp>
+namespace fs = boost::filesystem;
+#include <boost/bind.hpp>
+
 using namespace std;
 
-MussaWindow::MussaWindow(Mussa *analysis_, QWidget *parent) :
+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_),
   motif_editor(0),
-  browser(this),
-  mussaViewTB("Path Views"),
-  zoomBox(),
-  threshold(),
-  zoomLabel(),
-  closeAction(0) // initialize one of the pointers to null as a saftey flag
+  setup_analysis_dialog(0),
+  browser(0),
+  mussaViewTB(0),
+  zoom(0),
+  threshold(0),
+  progress_dialog(0),
+  aboutAction(0),
+  closeAction(0),
+  createNewAnalysisAction(0),
+  createSubAnalysisAction(0),
+  editMotifsAction(0),
+  loadMotifListAction(0),
+  loadMupaAction(0),
+  loadSavedAnalysisAction(0),
+  mussaManualAssistantAction(0),
+  newMussaWindowAction(0),
+  saveMotifListAction(0),
+  showMussaViewToolbarAction(0),
+  toggleMotifsAction(0),
+  saveBrowserPixmapAction(0),
+  whatsThisAction(0),
+  viewMussaAlignmentAction(0),
+  assistantProcess(0)
 {
+  init_resources();
+  
+  default_dir.reset(new QDir(QDir::home().absolutePath()));
+  
+  setupWidgets();
   setupActions();
+  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);
+  setCentralWidget(browser);
   // well updatePosition isn't quite right as we really just need
   // to call update()
-  connect(this, SIGNAL(changedAnnotations()), &browser, SLOT(update()));
-
-  mussaViewTB.addAction(toggleMotifsAction);
-  
-  zoomLabel.setPixmap(QIcon(":/icons/viewmag.png").pixmap(16, 16));
-  zoomLabel.setToolTip(tr("Zoom"));
-  zoomLabel.setWhatsThis(tr("Zoom magnification factor"));
-  mussaViewTB.addWidget(&zoomLabel);
-
-  zoomBox.setToolTip(tr("Zoom"));
-  zoomBox.setWhatsThis(tr("Zoom magnification factor"));
-  zoomBox.setRange(1,10000000);
-  mussaViewTB.addWidget(&zoomBox);
-  connect(&zoomBox, SIGNAL(valueChanged(int)), 
-          &browser, SLOT(setZoom(int)));
+  connect(this, SIGNAL(changedAnnotations()), browser, SLOT(update()));
+  connect(this, SIGNAL(changedMotifs()), this, SLOT(updateAnnotations()));
+  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)));
-  connect(&threshold, SIGNAL(thresholdChanged(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);
+  mussaViewTB->addWidget(threshold);
 
-  addToolBar(&mussaViewTB);
+  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 QString&, int, int)),
+            this, SLOT(updateProgress(const QString&, int, int)));
+    connect(analysis.get(), SIGNAL(isModified(bool)),
+            this, SLOT(updateAnalysisModified(bool)));        
+  }
+  updateTitle();
   updateAnalysis();
 }
 
-void MussaWindow::setAnalysis(Mussa *new_analysis)
+void MussaWindow::setAnalysis(MussaRef new_analysis)
 {
   if (new_analysis != 0) {
     // only switch mussas if we loaded without error
-    delete analysis;
-    analysis = new_analysis;
+    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();
   }
 }
@@ -96,32 +155,56 @@ void MussaWindow::setupActions()
   connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
   closeAction->setIcon(QIcon(":/icons/exit.png"));
   
-  createNewAnalysisAction = new QAction(tr("Define 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("Define SubAnalysis"), this);
+  createSubAnalysisAction = new QAction(tr("Add to Subanalysis"), this);
   connect(createSubAnalysisAction, SIGNAL(triggered()), 
           this, SLOT(createSubAnalysis()));
 
+  saveAnalysisAction = new QAction(tr("&Save Analysis"), this);
+  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()), 
+          this, SLOT(saveAnalysisAs()));
+  saveAnalysisAsAction->setIcon(QIcon(":/icons/filesave.png"));
+
   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"));
   
-  loadMupaAction = new QAction(tr("Load Mussa Parameters"), this);
+  loadMupaAction = new QAction(tr("Create Analysis from File"), this);
   connect(loadMupaAction, SIGNAL(triggered()), 
           this, SLOT(loadMupa()));
   loadMupaAction->setIcon(QIcon(":/icons/fileopen.png"));
 
-  loadSavedAnalysisAction = new QAction(tr("Load &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()));
+
+  newMussaWindowAction = new QAction(tr("&New Mussa Window"), this);
+  newMussaWindowAction->setStatusTip("open another mussa window to allow comparing results");
+  connect(newMussaWindowAction, SIGNAL(triggered()), 
+          this, SLOT(newMussaWindow()));
+  newMussaWindowAction->setIcon(QIcon(":/icons/window_new.png"));
 
   saveMotifListAction = new QAction(tr("Save Motifs"), this);
   connect(saveMotifListAction, SIGNAL(triggered()), 
@@ -145,11 +228,13 @@ 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 mussa alignment"), this);
+  viewMussaAlignmentAction = new QAction(tr("View sequence alignment"), this);
   connect(viewMussaAlignmentAction, SIGNAL(triggered()),
           this, SLOT(viewMussaAlignment() ));
   viewMussaAlignmentAction->setWhatsThis(tr("Create a zoomed in window "
@@ -159,19 +244,31 @@ void MussaWindow::setupActions()
   whatsThisAction = QWhatsThis::createAction(this);
   whatsThisAction->setIcon(QIcon(":/icons/help.png"));
 
+
+}
+
+void MussaWindow::closeEvent(QCloseEvent *event)
+{
+  if(isClearingAnalysisSafe()) {
+    event->accept();
+  } else {
+    event->ignore();
+  }
 }
 
 void MussaWindow::setupMainMenu()
 {
   // we need to run setupActions first
   assert (closeAction != 0);
+
+  QMenu *newMenu = menuBar()->addMenu(tr("&File"));
   
-  QMenu *newMenu;
-  newMenu = menuBar()->addMenu(tr("&File"));
+  newMenu->addAction(newMussaWindowAction);
   newMenu->addAction(createNewAnalysisAction);
   newMenu->addAction(loadMupaAction);
   newMenu->addAction(loadSavedAnalysisAction);
-  //newMenu->addAction(createSubAnalysisAction);
+  newMenu->addAction(saveAnalysisAction);
+  newMenu->addAction(saveAnalysisAsAction);
   newMenu->addSeparator();
   newMenu->addAction(loadMotifListAction);
   newMenu->addAction(saveMotifListAction);
@@ -180,41 +277,96 @@ void MussaWindow::setupMainMenu()
   newMenu->addSeparator();
   newMenu->addAction(closeAction);
 
-  newMenu = menuBar()->addMenu(tr("&View"));
+  newMenu = menuBar()->addMenu(tr("&Edit"));
   newMenu->addAction(editMotifsAction);
+  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);
   newMenu->addAction(showMussaViewToolbarAction);
 
   newMenu = menuBar()->addMenu(tr("&Help"));
+  newMenu->addAction(mussaManualAssistantAction);
   newMenu->addAction(whatsThisAction);
   newMenu->addSeparator();
   newMenu->addAction(aboutAction);
+
+  // add some extra features to the context menu
+  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";
+  manualAssistant = new QAssistantClient("assistant", this);
+  manualAssistant->setArguments(manualAssistantArgs);
+  connect(manualAssistant, SIGNAL(error(QString)),
+         this, SLOT(assistantError(QString)));
+  */
+#endif
 }
   
 void MussaWindow::about()
 {
-  QString msg("Welcome to Multiple Species Sequence Analysis\n"
-              "(c) 2005-2006 California Institute of Technology\n"
-              "Tristan De Buysscher, Diane Trout\n");
-   msg += "\n\r";
-   msg += "Path: ";
-   msg += QDir::currentPath();
-   msg += "\n";
-   msg += "OpenGL: ";
-   msg += (char *)glGetString(GL_VERSION);
-   msg += "\n";
-   QMessageBox::about(this, tr("About mussa"), msg);
+  QString msg;
+  msg += "Welcome to Multiple Species Sequence Analysis\n";
+  msg += "(c) 2005-2006 California Institute of Technology\n";
+  msg += "Diane Trout, Tristan De Buysscher, Brandon King\n";
+  msg += "Version: ";
+  msg += mussa_version;
+  msg += "\n";
+  msg += "Qt: ";
+  msg += qVersion();
+  msg += "\n";
+  msg += "OpenGL: ";
+  msg += (char *)glGetString(GL_VERSION);
+  msg += "\n";
+  QMessageBox::about(this, tr("About mussa"), msg);
+}
+
+void MussaWindow::clear()
+{
+  if (motif_editor != 0) {
+    motif_editor->hide();
+    delete motif_editor;
+  }
+  
+  aligned_windows.clear();
+  browser->clear();
 }
 
 void MussaWindow::createNewAnalysis()
 {
   try {
-    if (setup_analysis_dialog.exec()) {
-      Mussa *m = 0;
-      m = setup_analysis_dialog.getMussa();
-      setAnalysis(m);
-    } else {
-      std::cout << "New mussa exp. aborted!\n";
+    // ideally we should open a new window if there's an analysis
+    // but this should work for the moment.
+    if (not isClearingAnalysisSafe()) return;
+
+    if (setup_analysis_dialog->exec()) {
+      setAnalysis(setup_analysis_dialog->getMussa());
     }
   } catch(mussa_error e) {
     QString msg(e.what());
@@ -224,67 +376,212 @@ void MussaWindow::createNewAnalysis()
 
 void MussaWindow::createSubAnalysis()
 {
-  NotImplementedBox();
+  list<SequenceLocation> result;
+  SequenceLocationModel& model = subanalysis_window->getModel();
+  browser->copySelectedTracksAsSeqLocation(result);
+  for(list<SequenceLocation>::iterator result_itor = result.begin();
+      result_itor != result.end();
+      ++result_itor)
+  {
+    model.push_back(*result_itor);
+  }
+
+  if (not subanalysis_window->isVisible()) {
+    subanalysis_window->show();
+  }
+}
+
+void MussaWindow::saveAnalysis()
+{
+  // if we've got an analysis
+  if (analysis and not analysis->empty()) {
+    // if it doesn't have a name we need to pick one
+    if (analysis->get_analysis_path().empty()) {
+      saveAnalysisAs();
+    } else {     
+      // if we've got a name, has it changed any?
+      // this doesn't work when a sequence changes something (like its
+      // name)
+      //else if (analysis->is_dirty())
+      try { 
+        analysis->save();
+      } catch (std::exception e) {
+        QMessageBox::critical(this, 
+                              tr("Mussa Save Error"),
+                              tr(e.what()),
+                              QMessageBox::Ok, 0, 0);
+  }
+      
+    }
+  }
+}
+
+void MussaWindow::saveAnalysisAs()
+{
+  std::auto_ptr<QFileDialog> dialog(new QFileDialog(this));
+  dialog->setAcceptMode(QFileDialog::AcceptSave);
+  dialog->setFileMode(QFileDialog::AnyFile);
+  dialog->setDirectory(*default_dir);
+
+  QStringList fileNames;
+  if (not dialog->exec()) {
+    return;
+  }
+  fileNames = dialog->selectedFiles();
+   
+  if (fileNames.size() != 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);
+  }
+}
+
+bool MussaWindow::isClearingAnalysisSafe()
+{
+  if (analysis and not analysis->empty() and analysis->is_dirty()) {
+    switch (QMessageBox::question(
+      this,
+      tr("Save Unsaved Changes -- Mussa"),
+      tr("There are unsaved changes,\ndo you want to save?"),
+      tr("&Yes"), tr("&No"), tr("&Cancel"),
+      0, 2)) {
+    case 0:
+      // save
+      saveAnalysis();
+      break;
+    case 1:
+      // don't save
+      break;
+    case 2:
+      // don't replace 
+      return false;
+      break;
+    default:
+      // error
+      throw runtime_error("isClearingAnalysis QMesageBox failure");
+    }
+  } 
+  // if we're here we've been saved and can replace
+  return true;
 }
 
 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();
 }
 
 void MussaWindow::loadMotifList()
 {
-  QString caption("Load a motif list");
+  QString caption("Mussa Load Motifs");
   QString filter("Motif list(*.txt *.mtl)");
   QString path = QFileDialog::getOpenFileName(this,
-                                                   caption, 
-                                                   QDir::currentPath(),
-                                                   filter);
+                                              caption, 
+                                              default_dir->absolutePath(),
+                                              filter);
   // user hit cancel?
   if (path.isNull()) 
     return;
   // try to load safely
   try {
-    analysis->load_motifs(path.toStdString());
-  } catch (runtime_error e) {
+    fs::path converted_path(path.toStdString(), fs::native);
+    analysis->load_motifs(converted_path);
+    default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
+    emit changedMotifs();
+  } catch (std::exception e) {
     QString msg("Unable to load ");
     msg += path;
     msg += "\n";
     msg += e.what();
-    QMessageBox::warning(this, "Load Motifs", msg);
+    QMessageBox::warning(this, caption, msg);
   }
-  assert (analysis != 0);
 }
 
 void MussaWindow::saveMotifList()
 {
-  NotImplementedBox();
-}
+  QString caption("Mussa Save Motifs");
+  QString filter("Motif list(*.txt *.mtl)");
+  QString path = QFileDialog::getSaveFileName(this,
+                                              caption, 
+                                              default_dir->absolutePath(),
+                                              filter);
+  // user hit cancel?
+  if (path.isNull()) 
+    return;
+  // try to load safely
+  try {
+    fs::path converted_path(path.toStdString(), fs::native);
+    if (fs::extension(converted_path).size() == 0) {
+      // no extension, so add one
+      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 (std::exception e) {
+    QString msg("Unable to save ");
+    msg += path;
+    msg += "\n";
+    msg += e.what();
+    QMessageBox::warning(this, caption, msg);
+  }}
 
 void MussaWindow::loadMupa()
 {
   QString caption("Load a mussa parameter file");
   QString filter("Mussa Parameters (*.mupa)");
-  QString mupa_path = QFileDialog::getOpenFileName(this,
-                                                   caption, 
-                                                   QDir::currentPath(),
-                                                   filter);
+  QString mupa_path = QFileDialog::getOpenFileName(
+                         this,
+                         caption, 
+                         default_dir->absolutePath(),
+                         filter
+                      );
   // user hit cancel?
   if (mupa_path.isNull()) 
     return;
   // try to load safely
   try {
-    Mussa *m = new Mussa;
-    m->load_mupa_file(mupa_path.toStdString());
-    m->analyze(0, 0, Mussa::TransitiveNway, 0.0);
+    // ideally we should open a new window if there's an analysis
+    // but this should work for the moment.
+    if (not isClearingAnalysisSafe()) return;
+
+    MussaRef m = Mussa::init();
+    fs::path converted_path(mupa_path.toStdString(), fs::native);
+    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);
+    updateTitle();
+    // grab the path ignoring the mupa file portion
+    default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
   } catch (mussa_load_error e) {
     QString msg("Unable to load ");
     msg += mupa_path;
@@ -298,18 +595,43 @@ void MussaWindow::loadMupa()
 void MussaWindow::loadSavedAnalysis()
 {
   QString caption("Load a previously run analysis");
-  QString muway_dir = QFileDialog::getExistingDirectory(this,
-                                                        caption, 
-                                                        QDir::currentPath());
+  QString muway_dir = QFileDialog::getExistingDirectory(
+                        this,
+                        caption, 
+                        default_dir->absolutePath()
+                     );
   // user hit cancel?
   if (muway_dir.isNull()) 
     return;
   // try to safely load
   try {
-    Mussa *m = new Mussa;
-    m->load(muway_dir.toStdString());
+    // ideally we should open a new window if there's an analysis
+    // but this should work for the moment.
+    if (not isClearingAnalysisSafe()) return;
+
+    MussaRef m = Mussa::init();
+    fs::path converted_path(muway_dir.toStdString(), fs::native);
+    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
-    setAnalysis(m);
+    if (analysis->empty()) {
+      // our current window is empty so load and replace.
+      setAnalysis(m);
+      updateTitle();
+      default_dir->setPath(converted_path.branch_path().native_directory_string().c_str());
+    } else {
+      MussaWindow *win = new MussaWindow(m);
+      updateTitle();
+      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;
@@ -320,29 +642,105 @@ void MussaWindow::loadSavedAnalysis()
   assert (analysis != 0);
 }
 
-void MussaWindow::setSoftThreshold(int threshold)
+void MussaWindow::newMussaWindow()
 {
-  if (analysis->get_soft_thres() != threshold) {
-    analysis->set_soft_thres(threshold);
+  MussaWindow *win = new MussaWindow(Mussa::init());
+  win->default_dir = default_dir;
+  win->show();
+}
+
+void MussaWindow::setSoftThreshold(int value)
+{
+  //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()
 {
-  if (mussaViewTB.isVisible())
-    mussaViewTB.hide();
+  if (mussaViewTB->isVisible())
+    mussaViewTB->hide();
   else
-    mussaViewTB.show();
+    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();
 }
 
+void MussaWindow::showManual()
+{
+#if defined(QT_QTASSISTANT_FOUND)
+  
+  // 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
+  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"),
+                         QMessageBox::Ok,
+                         QMessageBox::NoButton,
+                         QMessageBox::NoButton);
+  }
+#endif //QT_QTASSISTANT_FOUND
+}
+
+void MussaWindow::assistantError(QString message)
+{
+  //std::cout << "QAssistantError: " << message.toStdString() << "\n";
+  QMessageBox::warning ( this, "Warning: Mussagl Manual", message, 
+                        QMessageBox::Ok, 
+                        QMessageBox::NoButton);
+}
+
 void MussaWindow::NotImplementedBox()
 {
   QMessageBox::warning(this, QObject::tr("mussa"), QObject::tr("Not implemented yet"));
@@ -350,40 +748,38 @@ void MussaWindow::NotImplementedBox()
 
 void MussaWindow::viewMussaAlignment()
 {
-  const set<int>& selected_paths = browser.selectedPaths();
+  const set<int>& selected_paths = browser->selectedPaths();
   if (selected_paths.size() == 0 ) {
     QMessageBox::warning(this, 
                          QObject::tr("mussa"),
                          QObject::tr("you should probably select some paths "
                                      "first"));
   } else {
-    MussaAlignedWindow *ma_win = new MussaAlignedWindow(*analysis, 
-                                                        selected_paths);
-    connect(this, SIGNAL(changedAnnotations()), 
-            ma_win, SLOT(update()));
+    MussaAlignedWindowRef ma_win( 
+      new MussaAlignedWindow(analysis, default_dir, selected_paths, subanalysis_window)
+    );
+
     aligned_windows.push_back(ma_win);
-    ma_win->show();
+    connect(this, SIGNAL(changedAnnotations()), 
+            aligned_windows.back().get(), SLOT(update()));
+    aligned_windows.back()->show();
   }
 }
                         
 void MussaWindow::updateAnalysis()
 {
-  threshold.setRange(analysis->get_threshold(),analysis->get_window());
-  for (list<MussaAlignedWindow *>::iterator maw_i = aligned_windows.begin();
-       maw_i != aligned_windows.end();
-       ++maw_i)
-  {
-    (*maw_i)->hide();
-    delete *maw_i;
-  }
-
-  browser.clear();
-  const vector<Sequence>& seqs = analysis->sequences();
-  browser.setSequences(seqs, analysis->colorMapper());
+  const Mussa::vector_sequence_type& seqs = analysis->sequences();
+  browser->setSequences(seqs, analysis->colorMapper());
+  assert(browser->sequences().size() == analysis->size());
+
+  // setRange eventually emits something that causes updateLinks to be called
+  // 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();
-  cout << "browser zoom " <<  browser.zoom() << endl;
-  zoomBox.setValue(browser.zoom());
+  browser->zoomOut();
+  zoom->setValue(browser->zoom());
 }
 
 void MussaWindow::updateAnnotations()
@@ -391,16 +787,21 @@ void MussaWindow::updateAnnotations()
   // motifs were changed in the sequences by 
   // Mussa::update_sequences_motifs
   emit changedAnnotations();
-  browser.update();
+  browser->update();
 }
 
 void MussaWindow::updateLinks()
 {
-  browser.clear_links();
+  if(browser->sequences().size() == 0) {
+    // we don't have any sequences load so we have no business setting links
+    return;
+  }
+
+  browser->clear_links();
   bool reversed = false;
   const NwayPaths& nway = analysis->paths();
 
-  typedef list<ExtendedConservedPath> conserved_paths;
+  typedef list<ConservedPath> conserved_paths;
   typedef conserved_paths::const_iterator const_conserved_paths_itor;
   for(const_conserved_paths_itor path_itor = nway.refined_pathz.begin();
       path_itor != nway.refined_pathz.end();
@@ -428,13 +829,51 @@ void MussaWindow::updateLinks()
         reversed = false;
       } else {
         reversed = true;
-        x = -x; // make positive
+        // make positive 
+        x = -x;
       }
       normalized_path.push_back(x);
       rc_flags.push_back(reversed);
     }
-    browser.link(normalized_path, rc_flags, path_itor->window_size);
+    browser->link(normalized_path, rc_flags, path_itor->window_size);
+  }
+  browser->update();
+}
+
+void 
+MussaWindow::updateProgress(const QString& description, int current, int max)
+{  
+  // if we're done  
+  if (current == max) {
+    if (progress_dialog != 0) {
+      progress_dialog->hide();
+      delete progress_dialog;
+      progress_dialog = 0;
+    }
+  } else {
+    // if we're starting, create the dialog
+    if (progress_dialog == 0) {
+      QString cancel("Cancel");
+      progress_dialog = new QProgressDialog(description, cancel, current, max, this);
+      progress_dialog->show();
+    } else {
+      // just update the dialog
+      progress_dialog->setValue(current);
+    }
   }
-  browser.update();
+  qApp->processEvents();
 }
 
+void MussaWindow::updateAnalysisModified(bool is_modified)
+{
+  setWindowModified(is_modified);
+}
+
+void MussaWindow::updateTitle()
+{
+  if (analysis) {
+    QString title(analysis->get_title().c_str());
+    title += "[*]";
+    setWindowTitle(title);
+  }
+}