From ef061e25ecdf36b23f1753e791f07853a98984b5 Mon Sep 17 00:00:00 2001 From: Diane Trout Date: Tue, 5 Dec 2006 00:17:22 +0000 Subject: [PATCH] provide python interpreter for mussa qui via a seperate thread 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). --- CMakeLists.txt | 4 ++ alg/mussa.cpp | 14 +++-- alg/mussa.hpp | 14 +++-- alg/nway_paths.cpp | 5 ++ alg/nway_paths.hpp | 19 ++++--- alg/parse_options.cpp | 3 +- py/CMakeLists.txt | 8 ++- py/MussaWindow.cpp | 49 +++-------------- py/module_qui.cpp | 9 ++++ py/mussa.cpp | 18 ++++--- py/nway_paths.cpp | 24 ++++++++- py/python.cpp | 31 +++++++++-- py/python.hpp | 7 ++- py/test/CMakeLists.txt | 1 + py/test/TestMussa.py | 3 +- qui/CMakeLists.txt | 5 ++ qui/MussaWindow.cpp | 24 ++++----- qui/MussaWindow.hpp | 7 ++- qui/mussa_setup_dialog/MussaSetupWidget.cpp | 2 +- qui/mussagl.cpp | 12 ++++- qui/subanalysis/SubanalysisWindow.cpp | 9 ++-- qui/subanalysis/SubanalysisWindow.hpp | 7 ++- qui/threading/GuiProxy.cpp | 46 ++++++++++++++++ qui/threading/GuiProxy.hpp | 34 ++++++++++++ qui/threading/InterpreterThread.cpp | 12 +++++ qui/threading/InterpreterThread.hpp | 15 ++++++ qui/threading/ThreadManager.cpp | 59 +++++++++++++++++++++ qui/threading/ThreadManager.hpp | 21 ++++++++ 28 files changed, 363 insertions(+), 99 deletions(-) create mode 100644 py/module_qui.cpp create mode 100644 qui/threading/GuiProxy.cpp create mode 100644 qui/threading/GuiProxy.hpp create mode 100644 qui/threading/InterpreterThread.cpp create mode 100644 qui/threading/InterpreterThread.hpp create mode 100644 qui/threading/ThreadManager.cpp create mode 100644 qui/threading/ThreadManager.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a31259..51249ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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}) diff --git a/alg/mussa.cpp b/alg/mussa.cpp index f205fe4..295fd49 100644 --- a/alg/mussa.cpp +++ b/alg/mussa.cpp @@ -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 m(new Mussa()); + return m; } boost::filesystem::path Mussa::get_analysis_path() const diff --git a/alg/mussa.hpp b/alg/mussa.hpp index e85055d..83e08cb 100644 --- a/alg/mussa.hpp +++ b/alg/mussa.hpp @@ -13,7 +13,8 @@ // ---------------------------------------- // ---------- mussa_class.hh ----------- // ---------------------------------------- -#include +#include +#include #include #include @@ -31,13 +32,17 @@ 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 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 MussaRef; #endif diff --git a/alg/nway_paths.cpp b/alg/nway_paths.cpp index 7b085a0..df2cf2e 100644 --- a/alg/nway_paths.cpp +++ b/alg/nway_paths.cpp @@ -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; diff --git a/alg/nway_paths.hpp b/alg/nway_paths.hpp index 3259116..8725432 100644 --- a/alg/nway_paths.hpp +++ b/alg/nway_paths.hpp @@ -14,6 +14,7 @@ // ---------- mussa_nway.hh ----------- // ---------------------------------------- #include +#include #include @@ -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 ConservedPaths; + NwayPaths(); NwayPaths(const NwayPaths&); @@ -43,6 +46,8 @@ public: void setup_ent(double new_entropy_thres, std::vector 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::iterator pbegin() { return pathz.begin() ; } - std::list::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::iterator rpbegin() { return refined_pathz.begin() ; } - std::list::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 pathz; - std::list refined_pathz; + ConservedPaths pathz; + ConservedPaths refined_pathz; protected: int threshold; diff --git a/alg/parse_options.cpp b/alg/parse_options.cpp index b911bbc..a6126c7 100644 --- a/alg/parse_options.cpp +++ b/alg/parse_options.cpp @@ -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. diff --git a/py/CMakeLists.txt b/py/CMakeLists.txt index 00a18b5..869faad 100644 --- a/py/CMakeLists.txt +++ b/py/CMakeLists.txt @@ -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}" ) diff --git a/py/MussaWindow.cpp b/py/MussaWindow.cpp index 9fe6b77..7d6fa81 100644 --- a/py/MussaWindow.cpp +++ b/py/MussaWindow.cpp @@ -1,49 +1,12 @@ - #include -#include -#include -#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", init) - .def("show", &MussaWindow::show) - ; - */ - boost::python::class_("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 index 0000000..2d039d3 --- /dev/null +++ b/py/module_qui.cpp @@ -0,0 +1,9 @@ +#include +using namespace boost::python; + +void export_gui_proxy(); + +BOOST_PYTHON_MODULE(mussaqui) +{ + export_gui_proxy(); +} diff --git a/py/mussa.cpp b/py/mussa.cpp index a203c7a..5f42599 100644 --- a/py/mussa.cpp +++ b/py/mussa.cpp @@ -25,14 +25,14 @@ void export_mussa() .def("__delitem__", &std_item::del) ; - py::class_("Mussa") + py::class_("_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 >(); + py::enum_("analysis_modes") .value("TransitiveNway", Mussa::TransitiveNway ) .value("RadialNway", Mussa::RadialNway ) diff --git a/py/nway_paths.cpp b/py/nway_paths.cpp index 1ba4450..6ca2f20 100644 --- a/py/nway_paths.cpp +++ b/py/nway_paths.cpp @@ -5,8 +5,28 @@ namespace py = boost::python; void export_nway_paths() { - py::class_("NwayPaths") + /*py::class_("ConservedPaths") + .def("__len__", &NwayPaths::ConservedPaths::size, "return length of paths") + .def("__contains__", &std_item::in) + .def("__iter__", py::iterator()) + .def("clear", &NwayPaths::ConservedPaths::clear, "remove all the paths") + .def("append", &std_item::add, + py::with_custodian_and_ward<1,2>()) // to let container keep value + .def("__getitem__", &std_item::get, + py::return_value_policy()) + .def("__setitem__", &std_item::set, + py::with_custodian_and_ward<1,2>()) // to let container keep value + .def("__delitem__", &std_item::del) + ;*/ + + py::class_("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)) + ; } diff --git a/py/python.cpp b/py/python.cpp index fe2364e..d43ee2f 100644 --- a/py/python.cpp +++ b/py/python.cpp @@ -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 diff --git a/py/python.hpp b/py/python.hpp index fc210c3..a9fc025 100644 --- a/py/python.hpp +++ b/py/python.hpp @@ -4,6 +4,7 @@ #include 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); diff --git a/py/test/CMakeLists.txt b/py/test/CMakeLists.txt index 15c6799..f797b0b 100644 --- a/py/test/CMakeLists.txt +++ b/py/test/CMakeLists.txt @@ -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} diff --git a/py/test/TestMussa.py b/py/test/TestMussa.py index d5d91cb..84062ae 100644 --- a/py/test/TestMussa.py +++ b/py/test/TestMussa.py @@ -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') diff --git a/qui/CMakeLists.txt b/qui/CMakeLists.txt index 5e89814..914678b 100644 --- a/qui/CMakeLists.txt +++ b/qui/CMakeLists.txt @@ -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) diff --git a/qui/MussaWindow.cpp b/qui/MussaWindow.cpp index 9325a82..11eb02f 100644 --- a/qui/MussaWindow.cpp +++ b/qui/MussaWindow.cpp @@ -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 diff --git a/qui/MussaWindow.hpp b/qui/MussaWindow.hpp index b538cc0..1d6e274 100644 --- a/qui/MussaWindow.hpp +++ b/qui/MussaWindow.hpp @@ -26,6 +26,9 @@ class QLabel; class QStringList; class Mussa; class QAssistantClient; +class MussaWindow; + +typedef boost::shared_ptr 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 diff --git a/qui/mussa_setup_dialog/MussaSetupWidget.cpp b/qui/mussa_setup_dialog/MussaSetupWidget.cpp index bef8244..097e247 100644 --- a/qui/mussa_setup_dialog/MussaSetupWidget.cpp +++ b/qui/mussa_setup_dialog/MussaSetupWidget.cpp @@ -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; diff --git a/qui/mussagl.cpp b/qui/mussagl.cpp index 04418d2..55afefd 100644 --- a/qui/mussagl.cpp +++ b/qui/mussagl.cpp @@ -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) { diff --git a/qui/subanalysis/SubanalysisWindow.cpp b/qui/subanalysis/SubanalysisWindow.cpp index fec991c..4f3b399 100644 --- a/qui/subanalysis/SubanalysisWindow.cpp +++ b/qui/subanalysis/SubanalysisWindow.cpp @@ -12,20 +12,23 @@ 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); diff --git a/qui/subanalysis/SubanalysisWindow.hpp b/qui/subanalysis/SubanalysisWindow.hpp index 4ba873e..4057004 100644 --- a/qui/subanalysis/SubanalysisWindow.hpp +++ b/qui/subanalysis/SubanalysisWindow.hpp @@ -3,10 +3,12 @@ #include -#include +#include +#include #include #include #include +#include #include #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 index 0000000..446de63 --- /dev/null +++ b/qui/threading/GuiProxy.cpp @@ -0,0 +1,46 @@ +#include + +#include "qui/threading/GuiProxy.hpp" +#include "qui/threading/ThreadManager.hpp" +#include +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 index 0000000..d35ce08 --- /dev/null +++ b/qui/threading/GuiProxy.hpp @@ -0,0 +1,34 @@ +#ifndef GUIPROXY_HPP_ +#define GUIPROXY_HPP_ + +#include "alg/mussa.hpp" +#include "qui/MussaWindow.hpp" + +#include + +class GuiProxy; +typedef boost::shared_ptr 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 windows; +}; + +void MussaWindowProxy(MussaRef); +#endif /*GUIPROXY_HPP_*/ diff --git a/qui/threading/InterpreterThread.cpp b/qui/threading/InterpreterThread.cpp new file mode 100644 index 0000000..45171e7 --- /dev/null +++ b/qui/threading/InterpreterThread.cpp @@ -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 index 0000000..dd8aea0 --- /dev/null +++ b/qui/threading/InterpreterThread.hpp @@ -0,0 +1,15 @@ +#ifndef INTERPRETERTHREAD_HPP_ +#define INTERPRETERTHREAD_HPP_ + +#include + +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 index 0000000..8abf214 --- /dev/null +++ b/qui/threading/ThreadManager.cpp @@ -0,0 +1,59 @@ +#include "qui/threading/ThreadManager.hpp" +#include "qui/threading/InterpreterThread.hpp" +#include +#include +#include + +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 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 index 0000000..3e878a0 --- /dev/null +++ b/qui/threading/ThreadManager.hpp @@ -0,0 +1,21 @@ +#ifndef THREADMANAGER_HPP_ +#define THREADMANAGER_HPP_ + +#include "qui/threading/GuiProxy.hpp" + +#include + +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_*/ -- 2.30.2