provide python interpreter for mussa qui via a seperate thread
authorDiane Trout <diane@caltech.edu>
Tue, 5 Dec 2006 00:17:22 +0000 (00:17 +0000)
committerDiane Trout <diane@caltech.edu>
Tue, 5 Dec 2006 00:17:22 +0000 (00:17 +0000)
helps ticket:201
Since both the python read-eval-print loop and Qt have their own
event loops one can't easily have a single threaded application
provide GUI code while still having an interpreter sitting around.
Additionally due to the limitations of OS X and Windows, the main
thread needs to be the GUI thread, so I needed to start the python
interpreter in a seperate thread.

I implemented several helper classes in qui/threading to help manage
creating threads. Currently it just provides a singleton interpreter
thread. The interpreter when started in its subthread will try to
initialize ipython, and if that fails will try InteractiveConsole
(I suppose I should've caught that failing, and then finally fell back
to the fgets read-eval-looop).

The way access to the GUI layer is handled is via a signal/slot thread
specific proxy class which holds a mussa analysis object and notifies
the copy in the main thread via a signal to create a new window using
the provided mussa object.

Access to some parts of the mussa object are thread safe, because I was
so enthusiastic about marking functions as const. All the other parts
are, shall we say, "up for grabs".

The other issue is I really need to improve the CMake scripts to be
a bit more robust about deteriming if python should be linked in.
(And one of my tests breaks because of this patch).

But since it works on OS X I want to know what it'll do on linux.
I'm pretty sure since a windows GUI app doesn't have a console that
it won't work on windows. (To say nothing of the command line
argument parser crashing under windows).

28 files changed:
CMakeLists.txt
alg/mussa.cpp
alg/mussa.hpp
alg/nway_paths.cpp
alg/nway_paths.hpp
alg/parse_options.cpp
py/CMakeLists.txt
py/MussaWindow.cpp
py/module_qui.cpp [new file with mode: 0644]
py/mussa.cpp
py/nway_paths.cpp
py/python.cpp
py/python.hpp
py/test/CMakeLists.txt
py/test/TestMussa.py
qui/CMakeLists.txt
qui/MussaWindow.cpp
qui/MussaWindow.hpp
qui/mussa_setup_dialog/MussaSetupWidget.cpp
qui/mussagl.cpp
qui/subanalysis/SubanalysisWindow.cpp
qui/subanalysis/SubanalysisWindow.hpp
qui/threading/GuiProxy.cpp [new file with mode: 0644]
qui/threading/GuiProxy.hpp [new file with mode: 0644]
qui/threading/InterpreterThread.cpp [new file with mode: 0644]
qui/threading/InterpreterThread.hpp [new file with mode: 0644]
qui/threading/ThreadManager.cpp [new file with mode: 0644]
qui/threading/ThreadManager.hpp [new file with mode: 0644]

index 6a3125991b83a5a4656c436ce6e17baff5922c02..51249ceaf70bbbce1523091eb0e44031eac594e3 100644 (file)
@@ -24,6 +24,8 @@ FIND_PACKAGE(OpenGL)
 FIND_PACKAGE(Boost)
 FIND_PACKAGE(PythonLibs)
 
+SET(USE_PYTHON 1)
+
 INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_PATH} 
                     ${QT_INCLUDES}
                     ${BOOST_INCLUDE_DIR} )
@@ -60,6 +62,8 @@ IF(USE_PYTHON)
   SET(PYTHON_CFLAGS "-DUSE_PYTHON")
   TARGET_LINK_LIBRARIES(mussagl
         mussa_py
+        ${QT_LIBRARIES}
+        mussa_qui_py
         ${BOOST_PYTHON_LIBRARY}
         ${PYTHON_LIBRARIES}
         ${PYTHON_LINK_LIBRARIES})
index f205fe4d0ab01b98019ea110f4f94a808e3d5a02..295fd49be50b76ea04411d551cb4ffbbb602cf30 100644 (file)
@@ -32,8 +32,8 @@ Mussa::Mussa()
   : color_mapper(new AnnotationColors)
 {
   clear();
-  connect(&the_paths, SIGNAL(progress(const std::string&, int, int)), 
-          this, SIGNAL(progress(const std::string&, int, int)));
+  connect(&the_paths, SIGNAL(progress(const QString&, int, int)), 
+          this, SIGNAL(progress(const QString&, int, int)));
 }
 
 Mussa::Mussa(const Mussa& m)
@@ -49,8 +49,14 @@ Mussa::Mussa(const Mussa& m)
     analysis_path(m.analysis_path),
     dirty(m.dirty)
 {
-  connect(&the_paths, SIGNAL(progress(const std::string&, int, int)), 
-          this, SIGNAL(progress(const std::string&, int, int)));
+  connect(&the_paths, SIGNAL(progress(const QString&, int, int)), 
+          this, SIGNAL(progress(const QString&, int, int)));
+}
+
+MussaRef Mussa::init() 
+{
+  boost::shared_ptr<Mussa> m(new Mussa());
+  return m;
 }
 
 boost::filesystem::path Mussa::get_analysis_path() const
index e85055d3bf6acb32338341909d422ec3e912e7a3..83e08cba7f7e42e8a32c9d092d2a516020d6a700 100644 (file)
@@ -13,7 +13,8 @@
 //                        ----------------------------------------
 //                          ---------- mussa_class.hh -----------
 //                        ----------------------------------------
-#include <QObject> 
+#include <QObject>
+#include <QString> 
 
 #include <boost/filesystem/path.hpp>
 #include <boost/shared_ptr.hpp>
 
 std::string int_to_str(int an_int);
 
+class Mussa;
+//! provide a simple name to point to our Mussa shared_ptr
+typedef boost::shared_ptr<Mussa> MussaRef;
+
 class Mussa : public QObject
 {
     Q_OBJECT 
 
 signals:
     //! call whatever signaling system we want
-    void progress(const std::string& description, int cur, int max);
+    void progress(const QString& description, int cur, int max);
     //! triggered when our state changes between unsaved(true) and saved(false)
     void isModified(bool);
 
@@ -49,6 +54,9 @@ public:
     Mussa();
     Mussa(const Mussa &);
 
+    //! dynamically construct a new Mussa object and return a reference to it
+    static MussaRef init();
+    
     //! save all of mussa
     void save(boost::filesystem::path save_path="");
     //! save the nway comparison
@@ -238,6 +246,4 @@ public:
     void seqcomp();
 
 };
-//! provide a simple name to point to our Mussa shared_ptr
-typedef boost::shared_ptr<Mussa> MussaRef;
 #endif
index 7b085a0844860164b78ca8520b4294b5ffe14f63..df2cf2e0d9794e788341a7c3c6da10d2e7596d58 100644 (file)
@@ -65,6 +65,11 @@ NwayPaths::set_soft_threshold(int sft_thres)
   soft_thres = sft_thres;
 }
 
+int NwayPaths::get_soft_threshold() const
+{
+  return soft_thres;
+}
+
 int NwayPaths::get_threshold() const
 {
   return threshold;
index 3259116675aed4fff16796fd10d781a51f14073f..8725432a7880f78f95d0777651183fc59b4b4e34 100644 (file)
@@ -14,6 +14,7 @@
 //                         ----------  mussa_nway.hh  -----------
 //                        ----------------------------------------
 #include <QObject>
+#include <QString>
 
 #include <boost/filesystem/path.hpp>
 
@@ -31,9 +32,11 @@ class NwayPaths : public QObject
 
 signals:
   //! emit to indicate how much progress we've made
-  void progress(const std::string& description, int cur, int max);
+  void progress(const QString& description, int cur, int max);
 
 public:
+    typedef std::list<ConservedPath> ConservedPaths;
+    
     NwayPaths();
     NwayPaths(const NwayPaths&);
 
@@ -43,6 +46,8 @@ public:
     void setup_ent(double new_entropy_thres, std::vector<Sequence> some_Seqs);
     //! clear out our path
     void clear();
+    //! get the "soft" threshold (between the hard threshold and window size)
+    int get_soft_threshold() const;
     //! set the score that a match must exceed inorder to be recorded as a path
     void set_soft_threshold(int soft_thres);
     //! return minimum threshold for this analysis
@@ -76,17 +81,17 @@ public:
 
     // The following iterator functions are mostly for the python interface
     // they'll have problems when being called from within a const object
-    std::list<ConservedPath>::iterator pbegin() { return pathz.begin() ; }
-    std::list<ConservedPath>::iterator pend() { return pathz.end() ; }
+    ConservedPaths::iterator pbegin() { return pathz.begin() ; }
+    ConservedPaths::iterator pend() { return pathz.end() ; }
     size_t path_size() const { return refined_pathz.size(); }
-    std::list<ConservedPath>::iterator rpbegin() { return refined_pathz.begin() ; }
-    std::list<ConservedPath>::iterator rpend() { return refined_pathz.end() ; }
+    ConservedPaths::iterator rpbegin() { return refined_pathz.begin() ; }
+    ConservedPaths::iterator rpend() { return refined_pathz.end() ; }
     size_t refined_path_size() const { return refined_pathz.size(); }
 
     // these probably shouldn't be public, but lets start 
     // simple
-    std::list<ConservedPath> pathz;
-    std::list<ConservedPath > refined_pathz;
+    ConservedPaths pathz;
+    ConservedPaths refined_pathz;
 
 protected:
     int threshold;
index b911bbc6479cf2bfcef2b1c9f4a19d6fc9aab6ca..a6126c7c0c95b9676938358f9212d8e61a0d80fc 100644 (file)
@@ -42,8 +42,7 @@ void initialize_mussa(MussaOptions& opts, int argc, char **argv)
     return;
   }
   
-  MussaRef new_mussa(new Mussa);
-  opts.analysis = new_mussa;
+  opts.analysis = Mussa::init();
 
   // currently we can only have one analysis loaded, so 
   // running trumps viewing.
index 00a18b5969a069f2699c6c70874cc8cfcea81eaf..869faad95388fed5f2384c858d1470ed8c13a210 100644 (file)
@@ -21,9 +21,13 @@ IF(BOOST_PYTHON_LIBRARY)
         nway_paths.cpp
         sequence.cpp
         )
-  
+  SET(QUI_SOURCES
+    module_qui.cpp
+    MussaWindow.cpp
+  )
   ADD_LIBRARY(mussa MODULE ${SOURCES})
   ADD_LIBRARY(mussa_py STATIC ${SOURCES})
+  ADD_LIBRARY(mussa_qui_py STATIC ${QUI_SOURCES})
   # Any suggestions for a more elegant solution to this? -diane
   IF(WIN32)
     ADD_CUSTOM_TARGET(mussa.dll ALL
@@ -53,7 +57,7 @@ IF(BOOST_PYTHON_LIBRARY)
     COMPILE_FLAGS "${PY_CFLAGS}"
   )
   SET_TARGET_PROPERTIES(
-    mussa mussa_py PROPERTIES
+    mussa mussa_py mussa_qui_py PROPERTIES
     COMPILE_FLAGS "${PY_CFLAGS}"
     LINK_FLAGS "${PY_LDFLAGS}"
   )
index 9fe6b77afd69fe2d5eb71b60785ee3fcbf14650a..7d6fa8149c71a06fe5ec03346d66b44f18602799 100644 (file)
@@ -1,49 +1,12 @@
-
 #include <boost/python.hpp>
 
-#include <QApplication>
-#include <QPushButton>
-#include "alg/mussa.hpp"
-#include "qui/MussaWindow.hpp"
-
-struct gui {
-  QApplication *app;
-  QPushButton *b;
-  MussaWindow *mw;
-
-  gui() : b(0), mw(0) { 
-    char *argv[] = {"mussagl"};
-    int argc = 1;
-    app = new QApplication(argc, (char **)argv);
-    Q_INIT_RESOURCE(icons);
-  }
-
-  void button() {
-    b = new QPushButton("hi");
-    b->show();
-  }
-
-  void mussa() {
-    Mussa *analysis = new Mussa();
-    mw = new MussaWindow(analysis);
-    mw->show();
-  }
+#include "qui/threading/GuiProxy.hpp"
 
-  int run() {
-    return app->exec();
-  }
-};
+using namespace boost::python;
 
-void export_mussa_window()
+void export_gui_proxy()
 {
-  /*
-  class_<MussaWindow>("MussaWindow", init<Mussa *, QObject *>)
-    .def("show", &MussaWindow::show)
-  ;
-  */
-  boost::python::class_<gui>("gui")
-    .def("button", &gui::button)
-    .def("mussa", &gui::mussa)
-    .def("run", &gui::run)
-  ;
+  def("MussaWindow", &MussaWindowProxy, 
+      "Create mussa window, DO NOT access the mussa analysis after passing it to this function "
+      "the mussa analysis object is NOT thread safe.");
 }
diff --git a/py/module_qui.cpp b/py/module_qui.cpp
new file mode 100644 (file)
index 0000000..2d039d3
--- /dev/null
@@ -0,0 +1,9 @@
+#include <boost/python.hpp>
+using namespace boost::python;
+
+void export_gui_proxy();
+
+BOOST_PYTHON_MODULE(mussaqui)
+{
+  export_gui_proxy();
+}
index a203c7a709706c2421797f76db4f3988109f440e..5f42599fb78111ea323fda0813c1dc3f43598c0f 100644 (file)
@@ -25,14 +25,14 @@ void export_mussa()
     .def("__delitem__", &std_item<Mussa::vector_sequence_type>::del)
   ;
  
-  py::class_<Mussa>("Mussa")
+  py::class_<Mussa>("_Mussa", py::no_init)
     .def("save", &Mussa::save)
     .def("load", &Mussa::load, "Load previous run analysis")
     .def("load_mupa", load_mupa_string, "Load mussa parameter file")
     .def("clear", &Mussa::clear, "Clear the current analysis")
-    .add_property("name", &Mussa::get_name, &Mussa::set_name)
-    .def("size", &Mussa::size, "Number of sequences to be used "
-                               "in this analysis")
+    .add_property("name", &Mussa::get_name, &Mussa::set_name, "Set the name of the analysis")
+    .def("size", &Mussa::size, "Number of sequences attached to "
+                               "this analysis")
     .add_property("window", &Mussa::get_window, &Mussa::set_window,
                   "the number of base pairs in the sliding window")
     .add_property("threshold", &Mussa::get_threshold, &Mussa::set_threshold,
@@ -42,11 +42,13 @@ void export_mussa()
                                    &Mussa::set_analysis_mode)
     .add_property("analysisModeName", &Mussa::get_analysis_mode_name)
     .def("analyze", &Mussa::analyze, "Run the analysis")
-    .def("paths", &Mussa::paths, py::return_internal_reference<>())
-    .def("sequences", &Mussa::sequences, py::return_internal_reference<>())
-    .def("add_sequence", append_sequence_ref)  
+    .def("paths", &Mussa::paths, py::return_internal_reference<>(), "return list of paths")
+    .def("sequences", &Mussa::sequences, py::return_internal_reference<>(), "return list of sequences")
+    .def("add_sequence", append_sequence_ref, "attach a sequence to the analysis")  
   ;
-
+  py::def("Mussa", &Mussa::init, "Construct a new Mussa object");
+  py::register_ptr_to_python< boost::shared_ptr<Mussa> >();
+  
   py::enum_<Mussa::analysis_modes>("analysis_modes")
     .value("TransitiveNway", Mussa::TransitiveNway )
     .value("RadialNway", Mussa::RadialNway )
index 1ba44500a099bbee6982773b558bc20d4609a4d5..6ca2f20e0a7ecce7507c49f15045ca61be353faa 100644 (file)
@@ -5,8 +5,28 @@ namespace py = boost::python;
 
 void export_nway_paths()
 {
-  py::class_<NwayPaths>("NwayPaths")
+  /*py::class_<NwayPaths::ConservedPaths>("ConservedPaths")
+    .def("__len__", &NwayPaths::ConservedPaths::size, "return length of paths")
+    .def("__contains__", &std_item<NwayPaths::ConservedPaths>::in)
+    .def("__iter__", py::iterator<NwayPaths::ConservedPaths>())
+    .def("clear", &NwayPaths::ConservedPaths::clear, "remove all the paths")
+    .def("append", &std_item<NwayPaths::ConservedPaths>::add,
+        py::with_custodian_and_ward<1,2>()) // to let container keep value
+    .def("__getitem__", &std_item<NwayPaths::ConservedPaths>::get,
+        py::return_value_policy<py::copy_non_const_reference>())
+    .def("__setitem__", &std_item<NwayPaths::ConservedPaths>::set,
+        py::with_custodian_and_ward<1,2>()) // to let container keep value
+    .def("__delitem__", &std_item<NwayPaths::ConservedPaths>::del)
+  ;*/
+   py::class_<NwayPaths>("NwayPaths")
+    .def("__len__", &NwayPaths::sequence_count)
+    .def("clear", &NwayPaths::clear, "remove all paths")
+    .add_property("threshold", &NwayPaths::get_threshold, "Get hard threshold")
+    .add_property("soft_threshold", &NwayPaths::get_soft_threshold, &NwayPaths::set_soft_threshold)
+    .add_property("window_size", &NwayPaths::get_window)
     .add_property("pathz", py::range(&NwayPaths::pbegin, &NwayPaths::pend))
-    .add_property("refinedPathz", py::range(&NwayPaths::rpbegin, &NwayPaths::rpend)) ;
+    .add_property("refinedPathz", py::range(&NwayPaths::rpbegin, &NwayPaths::rpend))
+   ;
 }
 
index fe2364eb529df5c44b24ae95bfd1381a841a5108..d43ee2f74334b1f5f396aaa47a01123944d42405 100644 (file)
@@ -14,6 +14,7 @@ MussaPython::MussaPython()
   try {
     // add our mussa module to our environment
     PyImport_AppendInittab("mussa", &initmussa);
+    PyImport_AppendInittab("mussaqui", &initmussaqui);
     // go ahead and finish initalizing python
     Py_Initialize();
     // get reference to the global namespace
@@ -24,7 +25,7 @@ MussaPython::MussaPython()
     // FIXME: this really should be a configuration file?
     run("import __main__\n"
         "import mussa\n"
-        "import webbrowser\n");
+        "import mussaqui");       
   } catch (py::error_already_set e) {
     PyErr_Print();
   }
@@ -58,7 +59,26 @@ py::object MussaPython::eval(std::string code)
 }
 
 
-void MussaPython::interpreter(FILE *fp)
+void MussaPython::interpreter() 
+{
+  try {
+    run("import sys\n"
+        "sys.argv = ['Mussa']\n"
+        "banner='Welcome to Mussa'\n"
+        "try:\n"
+        "  from IPython.Shell import IPShellEmbed\n"
+        "  ipshell = IPShellEmbed(banner=banner)\n"
+        "  ipshell()\n"
+        "except ImportError, e:\n"
+        "  import code\n"
+        "  code.interact(banner, local=globals())\n"
+        "print 'exiting interpreter'\n"
+    );
+  } catch (py::error_already_set e) {
+    PyErr_Print();
+  }
+}
+void MussaPython::simple_interpreter(FILE *fp)
 {
   try {
     PyRun_InteractiveLoop(fp, "mussa");
@@ -87,6 +107,9 @@ py::object MussaPython::operator[](std::string name)
 //! return a reference to a single mussa python interpreter
 MussaPython& get_py()
 {
-  static MussaPython py;
-  return py;
+  static MussaPython *py;
+  if (!py) {
+    py = new MussaPython;
+  }
+  return *py;
 }
\ No newline at end of file
index fc210c33369ba71e36e56525e2605a1b93014d2a..a9fc02534e5efd02073cbc1e2e70a7638defd453 100644 (file)
@@ -4,6 +4,7 @@
 #include <string>
 
 extern "C" void initmussa();
+extern "C" void initmussaqui();
 
 //! Create a singleton class to manage our reference to the python interpreter
 class MussaPython {
@@ -14,8 +15,10 @@ class MussaPython {
     void run(std::string);
     //! pass single expression to the python interpreter and return the result
     boost::python::object eval(std::string);
-    //! launch read-eval-print loop tied to the provided FILE pointer
-    void interpreter(FILE *fp=stdin);
+    //! use InteractiveConsole for readloop
+    void interpreter();
+    //! launch fgets based read-eval-print loop tied to the provided FILE pointer
+    void simple_interpreter(FILE *fp=stdin);
     //! return an object in the python namespace
     boost::python::object operator[](std::string);
 
index 15c679910f4a88d2899acac1e4b70ef845315467..f797b0b913278a8eb6ec0236db262d21d06e357a 100644 (file)
@@ -22,6 +22,7 @@ TARGET_LINK_LIBRARIES(mussa_python_test
                         mussa_py
                         mussa_core
                         ${QT_QTCORE_LIBRARY}
+                        mussa_qui_py
                         ${OPENGL_gl_LIBRARY}
                         ${BOOST_PROGRAM_OPTIONS_LIBRARY}
                         ${BOOST_FILESYSTEM_LIBRARY}
index d5d91cba7d95d6b28aa0565880f2d1c5b79b7176..84062ae747eda7b5722219e688281df71cea55ca 100644 (file)
@@ -19,7 +19,8 @@ class TestMussa(unittest.TestCase):
     m.add_sequence(s2)
     m.add_sequence(s3)
     m.analyze()
-    print m.paths()
+    # this could probably be a more thorough test
+    self.failUnless( len(m.paths()), 3 )
 
 def suite():
   return unittest.makeSuite(TestMussa, 'test')
index 5e89814db6e14515e5e21b250d559ceefbd0f900..914678be963cf9e8467c0a2b053e39b1f59403bb 100644 (file)
@@ -40,6 +40,8 @@ SET(MOC_HEADERS
       seqbrowser/seqproperties/PropertiesWindow.hpp
       subanalysis/SequenceLocationModel.hpp
       subanalysis/SubanalysisWindow.hpp
+      threading/GuiProxy.hpp
+      threading/InterpreterThread.hpp
     )
 SET(GUI_SOURCES 
       ImageSaveDialog.cpp      
@@ -68,6 +70,9 @@ SET(GUI_SOURCES
       seqbrowser/seqproperties/PropertiesWindow.cpp
       subanalysis/SequenceLocationModel.cpp
       subanalysis/SubanalysisWindow.cpp
+      threading/GuiProxy.cpp
+      threading/InterpreterThread.cpp
+      threading/ThreadManager.cpp
      )
 SET(PY_SOURCES ../py/python.cpp)
 SET(RCCS ../icons.qrc)
index 9325a8201e5a069304eed8c3b2ed0cee7f831c19..11eb02f4ac21392e71466adec05ad26263902b05 100644 (file)
@@ -99,8 +99,8 @@ MussaWindow::MussaWindow(MussaRef analysis_, QWidget *parent) :
   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)));        
   }
@@ -530,10 +530,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);
@@ -567,10 +567,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()) {
@@ -602,8 +602,7 @@ 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();
 }
@@ -776,7 +775,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) {
@@ -788,9 +787,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
index b538cc0faf353a9f51e7f4d426f784cd7ce18f99..1d6e2746d4da9e8353affa18532500be2b914770 100644 (file)
@@ -26,6 +26,9 @@ class QLabel;
 class QStringList;
 class Mussa;
 class QAssistantClient;
+class MussaWindow;
+
+typedef boost::shared_ptr<MussaWindow> MussaWindowRef;
 
 class MussaWindow : public QMainWindow
 {
@@ -34,6 +37,7 @@ class MussaWindow : public QMainWindow
 public: 
   MussaWindow(MussaRef analysis, QWidget *parent=0);
 
+public:
   //! reset any attached window
   void clear();
 
@@ -80,7 +84,7 @@ public slots:
   //! set the soft threshold used by the Nway_Path algorithm
   void setSoftThreshold(int thres);
   //! update progress bar
-  void updateProgress(const std::string& description, int cur, int max);
+  void updateProgress(const QString& description, int cur, int max);
 
   //! open a new mussa window so one can compare analyses
   void newMussaWindow();
@@ -159,4 +163,5 @@ protected slots:
   //! update annotations?
   void updateAnnotations();
 };
+
 #endif
index bef824470e1574da7372e83841c570d70f3c1523..097e247bb98e2b796fe82ed42b079b119ad1eaa2 100644 (file)
@@ -115,7 +115,7 @@ void MussaSetupWidget::updateThreshold(int new_threshold)
 
 MussaRef MussaSetupWidget::getMussaObject()
 {
-  MussaRef mussa(new Mussa);
+  MussaRef mussa = Mussa::init();
 
   int fastaIndex;
   int start;
index 04418d2a655a5f3e0d41cfcbae394825b5ad9e0b..55afefd02bf37ca5a4fc74470def09ca02306027 100644 (file)
@@ -6,6 +6,8 @@ using namespace boost::filesystem;
 #endif
 
 #include "qui/MussaWindow.hpp"
+#include "qui/threading/ThreadManager.hpp"
+#include "qui/threading/InterpreterThread.hpp"
 #include "alg/parse_options.hpp"
 #include "mussa_exceptions.hpp"
 
@@ -56,10 +58,18 @@ int main(int argc, char **argv)
     return 1;
   }
 
+  ThreadManager &thread = ThreadManagerFactory();
   try {
 #ifdef USE_PYTHON
     if (opts.runAsPythonInterpeter) {
-      get_py().interpreter();
+      // allow the user to keep the interpreter open even after
+      // closing all the windows
+      app.setQuitOnLastWindowClosed(false);
+      const InterpreterThread *interp = thread.create_interpreter();
+      // quit when the interpreter exits
+      QObject::connect(interp, SIGNAL(finished()),
+                       &app, SLOT(quit()));
+      app.exec();
     } else 
 #endif /* USE_PYTHON */
     if (opts.useGUI) { 
index fec991c75e4bf614b605ae6824743add819a3726..4f3b399e178bef47da84e9416fb721f7e9f5bd75 100644 (file)
 SubanalysisWindow::SubanalysisWindow(MussaRef m, QWidget *parent)
   : QWidget(parent),
     analysis(m),
+    parameterLayout(0),
+    thresholdLabel(0),
+    windowLabel(0),
     window(0),
     threshold(0),
     table(0),
     ok(0),
     cancel(0)
 {
-  QGridLayout *parameterLayout = new QGridLayout;
+  parameterLayout = new QGridLayout;
 
-  QLabel *thresholdLabel = new QLabel(tr("threshold (bp)"));
+  thresholdLabel = new QLabel(tr("threshold (bp)"));
   parameterLayout->addWidget(thresholdLabel, 0, 0);
   threshold = new QSpinBox(this);
   threshold->setValue(8);
   parameterLayout->addWidget(threshold, 1, 0);
-  QLabel *windowLabel = new QLabel(tr("window (bp)"));
+  windowLabel = new QLabel(tr("window (bp)"));
   parameterLayout->addWidget(windowLabel, 0, 1);
   window = new QSpinBox(this);
   window->setValue(10);
index 4ba873e0fabe6f0061d06d9bb67c45dc846e3fda..4057004cb9440fe374a89a315b2facb268be1514 100644 (file)
@@ -3,10 +3,12 @@
 
 #include <boost/shared_ptr.hpp>
 
-#include <QTableView>
+#include <QGridLayout>
+#include <QLabel>
 #include <QPushButton>
 #include <QSpinBox>
 #include <QStringList>
+#include <QTableView>
 #include <QWidget>
 
 #include "qui/subanalysis/SequenceLocationModel.hpp"
@@ -35,8 +37,11 @@ public slots:
 private:
   //! keep track of what analysis we're attached to
   MussaRef analysis;
+  QGridLayout *parameterLayout;
+  QLabel *thresholdLabel;
   QSpinBox *window;
   QSpinBox *threshold;
+  QLabel *windowLabel;
   QTableView *table;
   QPushButton *ok;
   QPushButton *cancel;
diff --git a/qui/threading/GuiProxy.cpp b/qui/threading/GuiProxy.cpp
new file mode 100644 (file)
index 0000000..446de63
--- /dev/null
@@ -0,0 +1,46 @@
+#include <QThreadStorage>
+
+#include "qui/threading/GuiProxy.hpp"
+#include "qui/threading/ThreadManager.hpp"
+#include <iostream>
+GuiProxy::GuiProxy() :
+  master(this)
+{
+}
+
+GuiProxy::GuiProxy(GuiProxy *master_) :
+  master(master_)
+{
+  connect(this, SIGNAL(create_mussa_window_signal(GuiProxy *)),
+          master, SLOT(create_mussa_window(GuiProxy *)),
+          Qt::QueuedConnection);
+}
+
+void GuiProxy::create_mussa_window(MussaRef m)
+{
+  if (this == master) {
+    MussaWindow *mw(new MussaWindow(m));
+    mw->show();
+    windows.push_back(mw);
+  } else {
+    if (!analysis) {
+      analysis = m;
+      emit create_mussa_window_signal(this);
+    }
+  }
+} 
+
+void GuiProxy::create_mussa_window(GuiProxy *proxy) {
+  create_mussa_window(proxy->analysis);
+  proxy->analysis.reset();
+}
+
+void MussaWindowProxy(MussaRef m) {
+  GuiProxy *proxy = ThreadManager::get_gui_proxy();  
+  if (proxy) {
+    proxy->create_mussa_window(m);
+  } else {
+    std::cout << "no local proxy" << std::endl;
+  }
+}
+    
\ No newline at end of file
diff --git a/qui/threading/GuiProxy.hpp b/qui/threading/GuiProxy.hpp
new file mode 100644 (file)
index 0000000..d35ce08
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef GUIPROXY_HPP_
+#define GUIPROXY_HPP_
+
+#include "alg/mussa.hpp"
+#include "qui/MussaWindow.hpp"
+
+#include <list>
+
+class GuiProxy;
+typedef boost::shared_ptr<GuiProxy> GuiProxyRef;
+class GuiProxy : public QObject {
+  Q_OBJECT
+  
+public:
+  //! default
+  GuiProxy();
+  //! initialize from master
+  GuiProxy(GuiProxy *);
+  GuiProxy(const GuiProxy &o);
+  
+signals:
+  void create_mussa_window_signal(GuiProxy *);
+
+public slots:
+  void create_mussa_window(MussaRef m);
+  void create_mussa_window(GuiProxy *);
+private: 
+  GuiProxy *master;
+  MussaRef analysis;
+  std::list<MussaWindow *> windows;
+};
+
+void MussaWindowProxy(MussaRef);
+#endif /*GUIPROXY_HPP_*/
diff --git a/qui/threading/InterpreterThread.cpp b/qui/threading/InterpreterThread.cpp
new file mode 100644 (file)
index 0000000..45171e7
--- /dev/null
@@ -0,0 +1,12 @@
+#include "py/python.hpp"
+#include "qui/threading/InterpreterThread.hpp"
+#include "qui/threading/ThreadManager.hpp"
+
+void InterpreterThread::run()
+{
+  get_py().interpreter();
+}
+
+InterpreterThread::InterpreterThread()
+{
+}
diff --git a/qui/threading/InterpreterThread.hpp b/qui/threading/InterpreterThread.hpp
new file mode 100644 (file)
index 0000000..dd8aea0
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef INTERPRETERTHREAD_HPP_
+#define INTERPRETERTHREAD_HPP_
+
+#include <QThread>
+
+class InterpreterThread : public QThread {
+  Q_OBJECT
+public:
+  void run();
+private:
+  //! only let ThreadManager create this object.
+  InterpreterThread();
+  friend class ThreadManager;
+}; 
+#endif /*INTERPRETERTHREAD_HPP_*/
diff --git a/qui/threading/ThreadManager.cpp b/qui/threading/ThreadManager.cpp
new file mode 100644 (file)
index 0000000..8abf214
--- /dev/null
@@ -0,0 +1,59 @@
+#include "qui/threading/ThreadManager.hpp"
+#include "qui/threading/InterpreterThread.hpp"
+#include <QMessageBox>
+#include <QMutex>
+#include <QThreadStorage>
+
+ThreadManager& ThreadManagerFactory() {
+  static ThreadManager *mgr;
+  if (!mgr) {
+    mgr = new ThreadManager;
+  }
+  return *mgr;
+}
+
+ThreadManager::ThreadManager()
+{
+  // initialize thread variable
+  get_gui_proxy();
+}
+
+const InterpreterThread *ThreadManager::create_interpreter() {
+  
+  static QMutex interpreter_lock;
+  static InterpreterThread *interpreter_thread;
+  if (interpreter_lock.tryLock()) {
+    // we're the first thread
+    interpreter_thread = new InterpreterThread();
+    interpreter_thread->start();
+  }
+  return interpreter_thread;
+  // someone already started a copy of the interpreter
+}
+
+GuiProxy *ThreadManager::get_gui_proxy()
+{
+  static GuiProxy *master;
+  static QThreadStorage <GuiProxy *> storage;
+  if (!master) {
+    // we don't have a master object so we probably should make one
+    assert (storage.hasLocalData() == false);
+    master = new GuiProxy;
+    if (!master) {
+      QMessageBox::critical(0, "Memory error", "Out of memory");
+    }
+    storage.setLocalData(master);
+    return master;
+  } else if (storage.hasLocalData()) {
+    // we've been initialized properly
+    return storage.localData();
+  } else {
+    // we have a master, but not a local proxy,
+    GuiProxy *client = new GuiProxy(master);
+    if (!client) {
+      QMessageBox::critical(0, "Memory error", "Out of memory");
+    }
+    storage.setLocalData(client);
+    return client;
+  }
+}
\ No newline at end of file
diff --git a/qui/threading/ThreadManager.hpp b/qui/threading/ThreadManager.hpp
new file mode 100644 (file)
index 0000000..3e878a0
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef THREADMANAGER_HPP_
+#define THREADMANAGER_HPP_
+
+#include "qui/threading/GuiProxy.hpp"
+
+#include <QThread>
+
+class InterpreterThread;
+class ThreadManager
+{
+public:
+  //! make a python interpreter
+  const InterpreterThread *create_interpreter();
+  static GuiProxy *get_gui_proxy();
+private:
+  ThreadManager();
+  friend ThreadManager& ThreadManagerFactory();
+};
+
+ThreadManager& ThreadManagerFactory();
+#endif /*THREADMANAGER_HPP_*/