From 1c18256b28680c2b23e8c1a90fd451b7b7413a08 Mon Sep 17 00:00:00 2001 From: andreas <andreas@5a81b35b-ba03-0410-adc8-b2c5c5119f08> Date: Mon, 5 Apr 2010 04:49:19 +0000 Subject: [PATCH] new state machine based shell (new_shell branch) git-svn-id: https://dng.biozentrum.unibas.ch/svn/openstructure/branches/new_shell@1919 5a81b35b-ba03-0410-adc8-b2c5c5119f08 --- modules/gui/pymod/__init__.py | 17 +- modules/gui/src/CMakeLists.txt | 18 +- modules/gui/src/gosty.cc | 13 +- modules/gui/src/gosty_app.cc | 3 +- .../plot_viewer/plot_data_graphics_item.cc | 8 +- .../gui/src/python_shell/output_redirector.cc | 15 +- .../gui/src/python_shell/output_redirector.hh | 9 +- .../src/python_shell/python_interpreter.cc | 325 ++-------- .../src/python_shell/python_interpreter.hh | 80 +-- .../python_shell/python_interpreter_proxy.hh | 1 + .../python_shell/python_interpreter_worker.cc | 132 ++++ .../python_shell/python_interpreter_worker.hh | 53 ++ .../python_namespace_tree_item.cc | 2 + .../python_namespace_tree_model.cc | 3 +- modules/gui/src/python_shell/python_shell.cc | 4 - modules/gui/src/python_shell/python_shell.hh | 1 - .../src/python_shell/python_shell_widget.cc | 607 ++++++++++-------- .../src/python_shell/python_shell_widget.hh | 76 ++- modules/gui/src/python_shell/state.cc | 84 +++ modules/gui/src/python_shell/state.hh | 45 ++ modules/gui/src/python_shell/state_machine.cc | 55 ++ modules/gui/src/python_shell/state_machine.hh | 30 + modules/gui/src/python_shell/transition.cc | 91 +++ modules/gui/src/python_shell/transition.hh | 77 +++ .../gui/src/python_shell/transition_guard.cc | 64 ++ .../gui/src/python_shell/transition_guard.hh | 65 ++ 26 files changed, 1191 insertions(+), 687 deletions(-) create mode 100644 modules/gui/src/python_shell/python_interpreter_worker.cc create mode 100644 modules/gui/src/python_shell/python_interpreter_worker.hh create mode 100644 modules/gui/src/python_shell/state.cc create mode 100644 modules/gui/src/python_shell/state.hh create mode 100644 modules/gui/src/python_shell/state_machine.cc create mode 100644 modules/gui/src/python_shell/state_machine.hh create mode 100644 modules/gui/src/python_shell/transition.cc create mode 100644 modules/gui/src/python_shell/transition.hh create mode 100644 modules/gui/src/python_shell/transition_guard.cc create mode 100644 modules/gui/src/python_shell/transition_guard.hh diff --git a/modules/gui/pymod/__init__.py b/modules/gui/pymod/__init__.py index 517c21dae..3fb43183b 100644 --- a/modules/gui/pymod/__init__.py +++ b/modules/gui/pymod/__init__.py @@ -17,8 +17,21 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #------------------------------------------------------------------------------ from _gui import * +import sip + ## \brief Opens a DataViewer # \sa \ref fft_li.py "View Fourier Transform Example" \sa \ref modulate_image.py "Modulate Image Example" -def CreateDataViewer(ih): - return GostyApp.Instance().CreateDataViewer(ih) \ No newline at end of file +def _close_event_override_(event): + print "close event" +def _set_data_override_(data): + print "set data" + +def CreateDataViewer(ih,flag=True): + viewer=GostyApp.Instance().CreateDataViewer(ih) + if flag: + viewer.image_=ih + sip_viewer=sip.warpinstance(viewer.GetSipHandle()) + sip_viewer.closeEvent=_close_event_override_ + sip_viewer.setData=_set_data_override_ + return viewer diff --git a/modules/gui/src/CMakeLists.txt b/modules/gui/src/CMakeLists.txt index e9e413416..fe8963e68 100644 --- a/modules/gui/src/CMakeLists.txt +++ b/modules/gui/src/CMakeLists.txt @@ -49,9 +49,13 @@ python_tokenizer.hh python_syntax_highlighter.hh text_logger.hh python_interpreter.hh -python_interpreter_proxy.hh +python_interpreter_worker.hh python_shell_text_document_layout.hh shell_history.hh +state_machine.hh +state.hh +transition.hh +transition_guard.hh ) set(OST_GUI_SCENE_WIN_HEADERS @@ -258,7 +262,7 @@ python_shell/path_completer.cc python_shell/python_context_parser.cc python_shell/python_completer.cc python_shell/python_interpreter.cc -python_shell/python_interpreter_proxy.cc +python_shell/python_interpreter_worker.cc python_shell/python_namespace_tree_model.cc python_shell/python_namespace_tree_item.cc python_shell/python_shell.cc @@ -269,6 +273,10 @@ python_shell/python_syntax_highlighter.cc python_shell/shell_history.cc python_shell/string_literal_positions.cc python_shell/text_logger.cc +python_shell/state_machine.cc +python_shell/state.cc +python_shell/transition.cc +python_shell/transition_guard.cc scene_win/context_menu.cc scene_win/current_selection_node.cc scene_win/custom_part_node.cc @@ -320,10 +328,16 @@ python_shell/gutter.hh python_shell/path_completer.hh python_shell/python_completer.hh python_shell/python_interpreter.hh +python_shell/python_interpreter_worker.hh python_shell/python_namespace_tree_model.hh python_shell/python_shell.hh python_shell/python_shell_widget.hh python_shell/text_logger.hh +python_shell/state_machine.hh +python_shell/state.hh +python_shell/transition.hh +python_shell/transition_guard.hh +python_shell/output_redirector.hh panel_bar/button_bar.hh panel_bar/drop_box.hh panel_bar/panel_bar.hh diff --git a/modules/gui/src/gosty.cc b/modules/gui/src/gosty.cc index f7d452b4d..08968ebb0 100644 --- a/modules/gui/src/gosty.cc +++ b/modules/gui/src/gosty.cc @@ -52,7 +52,7 @@ DelayedScriptExecutor::DelayedScriptExecutor() void DelayedScriptExecutor::Exec() { PythonInterpreter& interp=PythonInterpreter::Instance(); - interp.ExecRelease(); + interp.Start(); } }}} @@ -127,9 +127,7 @@ int setup_resources(QApplication& app) int init_python_interpreter() { // the order of these two calls is important! - PythonInterpreter::Instance(true); - // following command would instanciate a multithreaded python interpreter. - // Untested, use at your own risk!! PythonInterpreter::Instance(true,true); + PythonInterpreter::Instance(); reclaim_signals(); // PythonInterpreter& py=PythonInterpreter::Instance(); @@ -179,11 +177,10 @@ int main(int argc, char** argv) return r; } PythonInterpreter& py_int=PythonInterpreter::Instance(); - py_int.ExecWait(); - py_int.RunInitRC(); + // todo remove RunInitRC and replace with general call to run script (with dngrc as argument) + //py_int.RunInitRC(); + prepare_scripts(argc,argv,py_int); // delay all execution of python scripts after app.exec() has been called. ost::gui::detail::DelayedScriptExecutor delayed_executor; - - prepare_scripts(argc,argv,py_int); return app.exec(); } diff --git a/modules/gui/src/gosty_app.cc b/modules/gui/src/gosty_app.cc index 8eed58bea..0a1769bb8 100644 --- a/modules/gui/src/gosty_app.cc +++ b/modules/gui/src/gosty_app.cc @@ -127,7 +127,8 @@ void GostyApp::SetupPyShellLogging() { TextLogger* console_logger=new TextLogger(stdout); this->ReadLoggerSettings("console", console_logger); - py_shell_->AddLogger(console_logger); + // get log from Interpreter instead of shell + //py_shell_->AddLogger(console_logger); // TODO: Setup file logging } diff --git a/modules/gui/src/plot_viewer/plot_data_graphics_item.cc b/modules/gui/src/plot_viewer/plot_data_graphics_item.cc index 68f287076..81a0b78c7 100644 --- a/modules/gui/src/plot_viewer/plot_data_graphics_item.cc +++ b/modules/gui/src/plot_viewer/plot_data_graphics_item.cc @@ -132,10 +132,10 @@ void PlotDataGraphicsItem::Redraw() void PlotDataGraphicsItem::Callback(const PlotDataEntry& pde) { - PlotDataInfoPtr infoptr=dyn_cast<PlotDataInfo>(infoptr_); - if(infoptr->callback!=boost::python::object()){ - PythonInterpreter::Instance().CallFunction(infoptr->callback,boost::python::make_tuple(pde.x,pde.y,pde.ex,pde.ey,pde.q,boost::python::str(pde.info.toStdString().c_str()))); - } + // PlotDataInfoPtr infoptr=dyn_cast<PlotDataInfo>(infoptr_); + // if(infoptr->callback!=boost::python::object()){ + // PythonInterpreter::Instance().CallFunction(infoptr->callback,boost::python::make_tuple(pde.x,pde.y,pde.ex,pde.ey,pde.q,boost::python::str(pde.info.toStdString().c_str()))); + // } } }}//ns diff --git a/modules/gui/src/python_shell/output_redirector.cc b/modules/gui/src/python_shell/output_redirector.cc index 2702b057a..ec052e0d3 100644 --- a/modules/gui/src/python_shell/output_redirector.cc +++ b/modules/gui/src/python_shell/output_redirector.cc @@ -27,16 +27,17 @@ namespace ost { namespace gui { -void OutputRedirector::Write( String const& str ) +OutputRedirector::OutputRedirector(): + QObject() { - buffer_.append(str); } -QString OutputRedirector::GetOutput() + + +void OutputRedirector::Write( String const& str ) { - QString ret=QString::fromStdString(buffer_); - ret.chop(1); // chop off additional newline - buffer_.clear(); - return ret; + //buffer_.append(str); + emit OnOutput(QString::fromStdString(str)); + } String OutputRedirector::buffer_; diff --git a/modules/gui/src/python_shell/output_redirector.hh b/modules/gui/src/python_shell/output_redirector.hh index 664c696cb..c19426505 100644 --- a/modules/gui/src/python_shell/output_redirector.hh +++ b/modules/gui/src/python_shell/output_redirector.hh @@ -27,14 +27,19 @@ */ #include <QString> +#include <QObject> #include <ost/gui/module_config.hh> namespace ost { namespace gui { -class DLLEXPORT_OST_GUI OutputRedirector { +class DLLEXPORT_OST_GUI OutputRedirector: public QObject { +Q_OBJECT + public: + OutputRedirector(); void Write(const String& str); - static QString GetOutput(); +signals: + void OnOutput(const QString& output); private: static String buffer_; }; diff --git a/modules/gui/src/python_shell/python_interpreter.cc b/modules/gui/src/python_shell/python_interpreter.cc index 4af471f17..4b11055c4 100644 --- a/modules/gui/src/python_shell/python_interpreter.cc +++ b/modules/gui/src/python_shell/python_interpreter.cc @@ -32,8 +32,8 @@ #include <boost/filesystem/convenience.hpp> #include <QDebug> +#include <QFile> #include <QApplication> -#include <QThread> #include <QStringList> #include <ost/log.hh> @@ -43,258 +43,78 @@ PythonInterpreter::PythonInterpreter(): QObject(), main_module_(), main_namespace_(), -#ifndef _MSC_VER - sig_act_(), -#endif - output_redirector_(), - main_thread_runner_(), running_(false), - mutex_(), - gstate_(), - main_thread_state_(NULL), - proxy_(this), compile_command_(), - exec_wait_(false), - exec_queue_(), - parse_expr_cmd_() + worker_() { + connect(this,SIGNAL(WakeWorker()),&worker_,SLOT(Wake()),Qt::QueuedConnection); + connect(&worker_,SIGNAL(Output(unsigned int,const QString&)),this,SIGNAL(Output(unsigned int,const QString&))); + connect(&worker_,SIGNAL(ErrorOutput(unsigned int,const QString&)),this,SIGNAL(ErrorOutput(unsigned int,const QString&))); + connect(&worker_,SIGNAL(Finished(unsigned int, bool)),this,SIGNAL(Finished(unsigned int, bool))); + main_module_ = bp::import("__main__"); + main_namespace_ = bp::extract<bp::dict>(main_module_.attr("__dict__")); + main_namespace_["os"]=bp::import("os"); + main_namespace_["__builtin__"]=bp::import("__builtin__"); + main_namespace_["keyword"]=bp::import("keyword"); + bp::object code=bp::import("code"); + compile_command_=code.attr("compile_command"); } PythonInterpreter::~PythonInterpreter() { - PyGILState_Release(gstate_); - PyEval_RestoreThread(main_thread_state_); } -PythonInterpreter& PythonInterpreter::Instance(bool register_sighandler, - bool multithreaded) +PythonInterpreter& PythonInterpreter::Instance() { - //todo handle register_sighandler; - static bool initialized=false; static PythonInterpreter instance; - static QThread thread; - if(!initialized){ - initialized=true; - if(multithreaded){ - instance.moveToThread(&thread); - thread.start(); - instance.Init(true); - }else{ - instance.Init(false); - } - } return instance; } -void PythonInterpreter::Init(bool multithreaded) -{ - if(multithreaded){ - //initialize over a blocking queued connection to get the correct thread context - /* connect(this, SIGNAL(InitSignal(bool)),this, SLOT(InitSlot(bool)),Qt::BlockingQueuedConnection); - emit InitSignal(true);*/ - }else{ - // direct initalization - InitSlot(false); - } -} -void PythonInterpreter::RedirectOutput() -{ - main_namespace_["sys"].attr("stderr") = output_redirector_; - main_namespace_["sys"].attr("stdout") = output_redirector_; -} -void PythonInterpreter::InitSlot(bool multithreaded) +void PythonInterpreter::Stop() { - if(multithreaded){ - //main_thread_runner_.moveToThread(QApplication::instance()->thread()); - //connect(this,SIGNAL(RunInMainThreadSignal(const QString&)),&main_thread_runner_,SLOT(Run(const QString&)),Qt::BlockingQueuedConnection); - }else{ - connect(this,SIGNAL(RunInMainThreadSignal(const QString&)),&main_thread_runner_,SLOT(Run(const QString&)),Qt::DirectConnection); - } - Py_InitializeEx(1); - PyEval_InitThreads(); - main_thread_state_ = PyEval_SaveThread(); - gstate_ = PyGILState_Ensure(); - //store signal handler - #ifndef _MSC_VER - sigaction(SIGINT,0,&sig_act_); - #endif - main_module_ = bp::import("__main__"); - main_module_.attr("in_gui_mode")=true; - main_namespace_ = bp::extract<bp::dict>(main_module_.attr("__dict__")); - parse_expr_cmd_=bp::import("parser").attr("expr"); - main_namespace_["OutputRedirector"] = bp::class_<OutputRedirector>("OutputRedirector", bp::init<>()) - .def("write", &OutputRedirector::Write); - main_namespace_["PythonInterpreterProxy"] = bp::class_<PythonInterpreterProxy>("PythonInterpreterProxy", bp::init<PythonInterpreter*>()) - .def("Run", &PythonInterpreterProxy::Run); - - main_namespace_["os"]=bp::import("os"); - main_namespace_["__builtin__"]=bp::import("__builtin__"); - main_namespace_["keyword"]=bp::import("keyword"); - - parse_expr_cmd_=bp::import("parser").attr("expr"); - - main_namespace_["sys"]=bp::import("sys"); - - main_module_.attr("Proxy")=proxy_; - bp::object code=bp::import("code"); - compile_command_=code.attr("compile_command"); - + running_=false; } -namespace { -#ifndef _MSC_VER - class SigIntHandler { - public: - SigIntHandler(const struct sigaction& act) { - sigaction(SIGINT,&act,&old_); - } - ~SigIntHandler() { - sigaction(SIGINT,&old_,0); - } - private: - struct sigaction old_; - }; -#endif -} -void PythonInterpreter::RunInitRC() +void PythonInterpreter::Start() { - if(!getenv("HOME")) return; - String fname=String(getenv("HOME"))+"/.dngrc"; - try { - if (boost::filesystem::exists(fname)) { - try { - bp::exec_file(bp::str(fname), main_module_.attr("__dict__"), - main_module_.attr("__dict__")); - } catch(bp::error_already_set&) { - PyErr_Print(); - } - } - } catch(std::exception&) { - // silently ignore. needed for boost 1.33.1 - } + running_=true; + emit WakeWorker(); } -void PythonInterpreter::ExecWait() -{ - ++exec_wait_; -} -void PythonInterpreter::ExecRelease() -{ - --exec_wait_; - assert(exec_wait_>=0); - if(exec_wait_==0) { - for(std::vector<QString>::const_iterator it=exec_queue_.begin(); - it!=exec_queue_.end();++it) { - if(!RunCommand(*it)) break; - } - exec_queue_.clear(); - } -} -void PythonInterpreter::RunScript(const String& fname) +unsigned int PythonInterpreter::RunScript(const QString& fname) { - std::ifstream script(fname.c_str()); - if(!script) { - LOGN_ERROR("could not open " << fname); - return; - } - - String line; - QString command(""); - while(std::getline(script,line)) { - command.append(QString::fromStdString(line)); - command.append('\n'); - if(this->GetCodeBlockStatus(command)!=CODE_BLOCK_INCOMPLETE) { - if(!RunCommand(command)) break; - command=QString(""); - } + QFile script(fname); + if (!script.open(QIODevice::ReadOnly | QIODevice::Text)){ + LOGN_ERROR("could not open " << fname.toStdString()); + return RunCommand(""); } + QString command=script.readAll(); command.append('\n'); command.append('\n'); - RunCommand(command); + return RunCommand(command); } -bool PythonInterpreter::IsSimpleExpression(const QString& expr) +unsigned int PythonInterpreter::RunCommand(const QString& command) { - try { - parse_expr_cmd_(bp::str(expr.toStdString())); - return true; - } catch(bp::error_already_set&) { - PyErr_Clear(); - return false; + unsigned int id=worker_.AddCommand(command); + if(running_){ + emit WakeWorker(); } -} - -bool PythonInterpreter::RunCommand(const QString& command) -{ - bool flag=true; - if(exec_wait_!=0) { - exec_queue_.push_back(command); - return flag; - } - - // becore doing anything give Qt the chance to handle events for 100ms - QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents,100); - mutex_.lock(); - running_=true; - mutex_.unlock(); -#ifndef _MSC_VER - SigIntHandler sh(sig_act_); -#endif - try{ - if(command.indexOf(QString("\n"))==-1){ - if(command=="exit" || command=="quit"){ - emit Exit(); - } - if (this->IsSimpleExpression(command)) { - bp::object repr=main_module_.attr("__builtins__").attr("repr"); - bp::object result=bp::eval(bp::str(command.toStdString()), - main_namespace_, main_namespace_); - String rstring=bp::extract<String>(repr(result)); - if(rstring!="None"){ - output_redirector_.Write(rstring); - output_redirector_.Write("\n"); - } - emit Done(STATUS_OK,output_redirector_.GetOutput()); - flag=true; - } else { - bp::exec(bp::str(command.toStdString()),main_namespace_,main_namespace_); - flag=true; - } - }else{ - bp::exec(bp::str(command.toStdString()),main_namespace_,main_namespace_); - } - emit Done(STATUS_OK,output_redirector_.GetOutput()); - }catch(bp::error_already_set){ - if(PyErr_ExceptionMatches(PyExc_SystemExit)){ - PyErr_Clear(); - emit Exit(); - } else{ - PyErr_Print(); - QString output=output_redirector_.GetOutput(); - std::cout << output.toStdString() << std::endl; - emit Done(STATUS_ERROR,output); - flag=false; - } - } - mutex_.lock(); - running_=false; - mutex_.unlock(); - return flag; + return id; } CodeBlockStatus PythonInterpreter::GetCodeBlockStatus(const QString& command) { + // move to pure c++ to avoid clash with running worker + // move to worker class CodeBlockStatus status=CODE_BLOCK_COMPLETE; - mutex_.lock(); - running_=true; - mutex_.unlock(); -#ifndef _MSC_VER +/*#ifndef _MSC_VER SigIntHandler sh(sig_act_); -#endif +#endif*/ QStringList lines=command.split("\n"); String cmd; for (int i=0; i<lines.size(); ++i) { @@ -328,70 +148,9 @@ CodeBlockStatus PythonInterpreter::GetCodeBlockStatus(const QString& command) } } } - mutex_.lock(); - running_=false; - mutex_.unlock(); - return status; -} - -enum returncodes{ - RC_OK=0, - RC_ERROR=2>>1, - RC_LOOP=2, - RC_EXIT=2<<1, -}; - -void PythonInterpreter::CallFunction(const bp::object& func, - const bp::object& args) -{ - unsigned int retcode=RC_OK; - bool result; - try{ -#ifndef _MSC_VER - SigIntHandler sh(sig_act_); -#endif - if (len(args)>0) { - result=bp::extract<bool>(func(args)); - } else { - result=bp::extract<bool>(func()); - } - if (!result) { - retcode|=RC_ERROR; - PyErr_Print(); - } - } catch(bp::error_already_set) { - if (PyErr_ExceptionMatches(PyExc_KeyboardInterrupt)){ - retcode|=RC_EXIT; - } else { - PyErr_Print(); - retcode|=RC_ERROR; - } - } + return status; } -bool PythonInterpreter::IsRunning() -{ - mutex_.lock(); - bool state=running_; - mutex_.unlock(); - return state; -} - -void PythonInterpreter::Abort() -{ - if(IsRunning()){ - raise(SIGINT); - } -} - -bp::object PythonInterpreter::RunInMainThread(const QString& widget) -{ - PyGILState_Release(gstate_); - emit RunInMainThreadSignal(widget); - gstate_ = PyGILState_Ensure(); - return main_module_.attr("Proxy").attr("_widget_"); - -} void PythonInterpreter::AppendModulePath(const QString& entry) { @@ -412,18 +171,6 @@ void PythonInterpreter::AppendCommandlineArgument(const QString& arg) RunCommand(init_code); } -void PythonInterpreter::SetCommandLineArguments(const QList<QString>& args) -{ - QString init_code(""); - init_code += "\nsys.argv=[]"; - for (QList<QString>::const_iterator i=args.begin(), e=args.end(); i!=e; ++i) { - init_code += "\nsys.argv.append('" ; - init_code += *i; - init_code += "')\n"; - } - - RunCommand(init_code); -} bp::object PythonInterpreter::GetMainModule() const { return main_module_; diff --git a/modules/gui/src/python_shell/python_interpreter.hh b/modules/gui/src/python_shell/python_interpreter.hh index bb69f5fe9..01b3e3273 100644 --- a/modules/gui/src/python_shell/python_interpreter.hh +++ b/modules/gui/src/python_shell/python_interpreter.hh @@ -27,18 +27,16 @@ #ifndef PYTHON_INTERPRETER_HH #define PYTHON_INTERPRETER_HH -#include <csignal> #include <vector> -#include <QMutex> +#include <QQueue> #include <QMetaType> -#include <boost/python.hpp> #include <ost/gui/module_config.hh> #include <ost/gui/module_config.hh> -#include "python_interpreter_proxy.hh" +#include "python_interpreter_worker.hh" #include "output_redirector.hh" #include "main_thread_runner.hh" @@ -52,34 +50,22 @@ enum InterpreterStatus{ typedef enum { - CODE_BLOCK_COMPLETE, - CODE_BLOCK_ERROR, - CODE_BLOCK_INCOMPLETE + CODE_BLOCK_COMPLETE=1, + CODE_BLOCK_ERROR=2, + CODE_BLOCK_INCOMPLETE=4 } CodeBlockStatus; class DLLEXPORT_OST_GUI PythonInterpreter: public QObject { Q_OBJECT public: - static PythonInterpreter& Instance(bool set_sigint_handler=true, - bool multitreaded=false); + static PythonInterpreter& Instance(); ~PythonInterpreter(); bool IsRunning(); - bp::object RunInMainThread(const QString& widget); void AppendModulePath(const QString& entry); void AppendCommandlineArgument(const QString& arg); - /// \brief set command line arguments - /// - /// Sets sys.argv to the given list of arguments - /// - /// The method internally uses RunCommand and is thus appended to the command - /// list when execution is delayed due to an open ExecWait() ExecRelease() - /// block - /// - /// \todo escape arguments - void SetCommandLineArguments(const QList<QString>& args); bp::dict GetMainNamespace() const; bp::object GetMainModule() const; @@ -88,62 +74,34 @@ public: /// Determines whether the command contains errors, is incomplete or ready /// to be run. CodeBlockStatus GetCodeBlockStatus(const QString& command); - void RunInitRC(); - void RunScript(const String& script); - /// \brief delay execution of Python commands - /// - /// The execution of Python commands with RunCommand and methods that - /// make use of RunCommand is delayed until ExecRelease() is called. - /// ExecWait(), ExecRelease() calls can be nested. + /// \brief stop execution of Python commands + /// \sa Start + void Stop(); + /// \brief start/continue execution of Python commands /// - /// \sa ExecRelease - void ExecWait(); - /// \brief execute pending Python commands - /// - /// \sa ExecWait - void ExecRelease(); + /// \sa Stop + void Start(); - /// \brief turn on output redirection - void RedirectOutput(); - /// \brief whether the string can be interpreted as a simple expression - /// - /// import and any kind of control structures don't count as simple - /// expressions and will return false. - bool IsSimpleExpression(const QString& expr); public slots: /// \brief execute python command - bool RunCommand(const QString& command); - void CallFunction(const bp::object& func,const bp::object& args); - void Abort(); -protected slots: - void InitSlot(bool multitreaded); + unsigned int RunScript(const QString& script); + unsigned int RunCommand(const QString& command); signals: - void Done(int status,const QString& output); + void Output(unsigned int id,const QString& output); + void ErrorOutput(unsigned int id,const QString& output); + void Finished(unsigned int id, bool error_state); void Exit(); - void InitSignal(bool multitreaded); - void RunInMainThreadSignal(const QString& widget); + void WakeWorker(); protected: - void Init(bool multitreaded); PythonInterpreter(); bp::object main_module_; bp::dict main_namespace_; -#ifndef _MSC_VER - struct sigaction sig_act_; -#endif - OutputRedirector output_redirector_; - MainThreadRunner main_thread_runner_; bool running_; - QMutex mutex_; - PyGILState_STATE gstate_; - PyThreadState* main_thread_state_; - PythonInterpreterProxy proxy_; bp::object compile_command_; - int exec_wait_; - std::vector<QString> exec_queue_; - bp::object parse_expr_cmd_; + PythonInterpreterWorker worker_; }; }} // ns diff --git a/modules/gui/src/python_shell/python_interpreter_proxy.hh b/modules/gui/src/python_shell/python_interpreter_proxy.hh index 1cb54fa0b..30ba00be1 100644 --- a/modules/gui/src/python_shell/python_interpreter_proxy.hh +++ b/modules/gui/src/python_shell/python_interpreter_proxy.hh @@ -25,6 +25,7 @@ Author: Andreas Schenk */ +#include <boost/python.hpp> #include <ost/gui/module_config.hh> namespace ost { namespace gui { diff --git a/modules/gui/src/python_shell/python_interpreter_worker.cc b/modules/gui/src/python_shell/python_interpreter_worker.cc new file mode 100644 index 000000000..8f1688804 --- /dev/null +++ b/modules/gui/src/python_shell/python_interpreter_worker.cc @@ -0,0 +1,132 @@ +#include "python_interpreter_worker.hh" +#include "python_interpreter.hh" + +namespace ost { namespace gui { + + namespace { +#ifndef _MSC_VER + class SigIntHandler { + public: + SigIntHandler(const struct sigaction& act) { + sigaction(SIGINT,&act,&old_); + } + ~SigIntHandler() { + sigaction(SIGINT,&old_,0); + } + private: + struct sigaction old_; + }; +#endif +} + + + +PythonInterpreterWorker::PythonInterpreterWorker(): + exec_queue_(), + command_id_(0), + output_redirector_(new OutputRedirector), + error_redirector_(new OutputRedirector), + #ifndef _MSC_VER + sig_act_(), + #endif + parse_expr_cmd_(), + repr_(), + main_namespace_(), + current_id_() +{ + Py_InitializeEx(1); + parse_expr_cmd_=bp::import("parser").attr("expr"); + main_namespace_ = bp::extract<bp::dict>(bp::import("__main__").attr("__dict__")); + repr_=bp::import("__main__").attr("__builtins__").attr("repr"); + #ifndef _MSC_VER + sigaction(SIGINT,0,&sig_act_); + #endif + main_namespace_["OutputRedirector"] = bp::class_<OutputRedirector,boost::shared_ptr<OutputRedirector>, boost::noncopyable >("OutputRedirector", bp::init<>()) + .def("write", &OutputRedirector::Write); + connect(output_redirector_.get(),SIGNAL(OnOutput(const QString&)),this,SLOT(handle_redirector_output(const QString&))); + connect(error_redirector_.get(),SIGNAL(OnOutput(const QString&)),this,SLOT(handle_redirector_error(const QString&))); + main_namespace_["sys"]=bp::import("sys"); + main_namespace_["sys"].attr("stderr") = error_redirector_; + main_namespace_["sys"].attr("stdout") = output_redirector_; +} + +void PythonInterpreterWorker::Wake() +{ + while (!exec_queue_.isEmpty()){ + std::pair<unsigned int, QString> pair=exec_queue_.dequeue(); + run_command_(pair); + } +} + +unsigned int PythonInterpreterWorker::AddCommand(const QString& command) +{ + exec_queue_.enqueue(std::pair<unsigned int, QString>(++command_id_,command)); //wrap around in command_id_ intended + return command_id_; +} + +bool PythonInterpreterWorker::is_simple_expression_(const QString& expr) +{ + try { + parse_expr_cmd_(bp::str(expr.toStdString())); + return true; + } catch(bp::error_already_set&) { + PyErr_Clear(); + return false; + } +} +void PythonInterpreterWorker::run_command_(std::pair<unsigned int,QString> pair) +{ + #ifndef _MSC_VER + SigIntHandler sh(sig_act_); + #endif + try{ + // todo handle quit and exit without parantheses + current_id_=pair.first; + QString command=pair.second; + if(is_simple_expression(command)){ + bp::object result=bp::eval(bp::str(command.toStdString()), + main_namespace_, main_namespace_); + String rstring=bp::extract<String>(repr_(result)); + if(rstring!="None"){ + handle_redirector_output(QString::fromStdString(rstring)+"\n"); + } + } else { + bp::exec(bp::str(command.toStdString()),main_namespace_,main_namespace_); + } + emit Finished(pair.first,true); + return; + }catch(bp::error_already_set){ + if(PyErr_ExceptionMatches(PyExc_SystemExit)){ + PyErr_Clear(); + //emit Exit(); + } else{ + PyErr_Print(); + } + } + emit Finished(pair.first,false); + return; +} + +bool PythonInterpreterWorker::is_simple_expression(const QString& expr) +{ + try { + parse_expr_cmd_(bp::str(expr.toStdString())); + return true; + } catch(bp::error_already_set&) { + PyErr_Clear(); + return false; + } +} + +void PythonInterpreterWorker::handle_redirector_output(const QString& output) +{ + emit Output(current_id_,output); +} + +void PythonInterpreterWorker::handle_redirector_error(const QString& output) +{ + emit ErrorOutput(current_id_,output); +} + +}} //ns + diff --git a/modules/gui/src/python_shell/python_interpreter_worker.hh b/modules/gui/src/python_shell/python_interpreter_worker.hh new file mode 100644 index 000000000..5093c61b9 --- /dev/null +++ b/modules/gui/src/python_shell/python_interpreter_worker.hh @@ -0,0 +1,53 @@ +#ifndef PYTHON_INTERPRETER_WORKER_HH +#define PYTHON_INTERPRETER_WORKER_HH + +#include <csignal> +#include <utility> +#include <QObject> +#include <QQueue> +#include <boost/python.hpp> +#include <boost/shared_ptr.hpp> +#include "output_redirector.hh" + +namespace ost { namespace gui { +namespace bp = boost::python; + +class PythonInterpreterWorker: public QObject +{ +Q_OBJECT +public: + PythonInterpreterWorker(); + unsigned int AddCommand(const QString& command); + +signals: + void Finished(unsigned int id, bool error_state); + void Output(unsigned int id,const QString& output); + void ErrorOutput(unsigned int id,const QString& output); + +public slots: + void Wake(); + +protected slots: + void handle_redirector_output(const QString& output); + void handle_redirector_error(const QString& output); + +protected: + bool is_simple_expression(const QString& expr); + void run_command_(std::pair<unsigned int,QString> pair); + bool is_simple_expression_(const QString& expr); + QQueue<std::pair<unsigned int,QString> > exec_queue_; + unsigned int command_id_; + boost::shared_ptr<OutputRedirector> output_redirector_; + boost::shared_ptr<OutputRedirector> error_redirector_; +#ifndef _MSC_VER + struct sigaction sig_act_; +#endif + bp::object parse_expr_cmd_; + bp::object repr_; + bp::dict main_namespace_; + unsigned int current_id_; +}; + +}} //ns + +#endif // PYTHON_INTERPRETER_WORKER_HH diff --git a/modules/gui/src/python_shell/python_namespace_tree_item.cc b/modules/gui/src/python_shell/python_namespace_tree_item.cc index c4af0f207..065eeab41 100644 --- a/modules/gui/src/python_shell/python_namespace_tree_item.cc +++ b/modules/gui/src/python_shell/python_namespace_tree_item.cc @@ -89,6 +89,8 @@ bool PythonNamespaceTreeItem::CanFetchMore() const void PythonNamespaceTreeItem::FetchMore() { + // todo should imediately return if worker thread is working + // todo fix completion for builtins initialized_=true; bp::object dir=bp::import("__main__").attr("__builtins__").attr("dir"); bp::list keys=bp::extract<bp::list>(dir(namespace_)); diff --git a/modules/gui/src/python_shell/python_namespace_tree_model.cc b/modules/gui/src/python_shell/python_namespace_tree_model.cc index c5b66e03d..1427a425d 100644 --- a/modules/gui/src/python_shell/python_namespace_tree_model.cc +++ b/modules/gui/src/python_shell/python_namespace_tree_model.cc @@ -26,7 +26,7 @@ PythonNamespaceTreeModel::PythonNamespaceTreeModel(): QAbstractItemModel(), root_item_(new PythonNamespaceTreeItem(PythonInterpreter::Instance().GetMainModule(),"main")) { - connect(&PythonInterpreter::Instance(), SIGNAL(Done(int,const QString&)), + connect(&PythonInterpreter::Instance(), SIGNAL(Finished(unsigned int,bool)), this, SLOT(NamespaceChanged(void))); } @@ -37,6 +37,7 @@ PythonNamespaceTreeModel::~PythonNamespaceTreeModel() void PythonNamespaceTreeModel::NamespaceChanged() { + // todo should only be called after all commands are executed delete root_item_; root_item_=new PythonNamespaceTreeItem(PythonInterpreter::Instance().GetMainModule(),"main"); dataChanged(QModelIndex(),QModelIndex()); diff --git a/modules/gui/src/python_shell/python_shell.cc b/modules/gui/src/python_shell/python_shell.cc index ae3030b8a..6999bb35c 100644 --- a/modules/gui/src/python_shell/python_shell.cc +++ b/modules/gui/src/python_shell/python_shell.cc @@ -63,10 +63,6 @@ bool PythonShell::Save(const QString& prefix) return true; } -void PythonShell::AddLogger(TextLogger* logger) -{ - this->PyShell()->AddLogger(logger); -} PythonShellWidget* PythonShell::PyShell() { diff --git a/modules/gui/src/python_shell/python_shell.hh b/modules/gui/src/python_shell/python_shell.hh index c9246f076..84f8929ce 100644 --- a/modules/gui/src/python_shell/python_shell.hh +++ b/modules/gui/src/python_shell/python_shell.hh @@ -40,7 +40,6 @@ public: virtual bool Save(const QString& prefix); public: - void AddLogger(TextLogger* logger); PythonShellWidget* PyShell(); }; diff --git a/modules/gui/src/python_shell/python_shell_widget.cc b/modules/gui/src/python_shell/python_shell_widget.cc index 6d8183bf9..2446daa93 100644 --- a/modules/gui/src/python_shell/python_shell_widget.cc +++ b/modules/gui/src/python_shell/python_shell_widget.cc @@ -16,8 +16,13 @@ // along with this library; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA //------------------------------------------------------------------------------ +/* + Authors: Marco Biasini, Andreas Schenk + */ + #include <iostream> +#include <QApplication> #include <QDebug> #include <QFontMetrics> #include <QClipboard> @@ -26,7 +31,6 @@ #include <QDirModel> #include <QStringList> -#include <ost/gui/python_shell/text_logger.hh> #include "python_shell_widget.hh" #include "gutter.hh" @@ -37,6 +41,7 @@ #include "python_completer.hh" #include "path_completer.hh" +#include "transition.hh" @@ -58,9 +63,15 @@ PythonShellWidget::PythonShellWidget(QWidget* parent): output_visible_(true), completion_start_(0), completion_end_(0), - mode_(SHELL_INTERACTION_BASH) + mode_(SHELL_INTERACTION_BASH), + block_edit_start_(document()->begin()), + output_blocks_(), + machine_(new StateMachine(this)), + readonly_machine_(new StateMachine(this)), + readwrite_state_(new State), + multiline_active_state_(new State) { - this->setLineWrapMode(QPlainTextEdit::NoWrap); + setLineWrapMode(QPlainTextEdit::NoWrap); document()->setDocumentLayout(new PythonShellTextDocumentLayout(document())); setViewportMargins(Gutter::GUTTER_WIDTH, 0, 0, 0); setUndoRedoEnabled(false); @@ -68,14 +79,17 @@ PythonShellWidget::PythonShellWidget(QWidget* parent): QFontMetrics metrics(font()); setTabStopWidth(2*metrics.width(" ")); setMaximumBlockCount(1000000); - connect(this, SIGNAL(updateRequest(QRect, int)) ,gutter_, - SLOT(Update(QRect, int))); - connect(this, SIGNAL(Execute(QString)),&PythonInterpreter::Instance(), - SLOT(RunCommand(QString)),Qt::QueuedConnection); - connect(&PythonInterpreter::Instance(), SIGNAL(Done(int,const QString&)),this, - SLOT(AppendOutput(int,const QString&)),Qt::QueuedConnection); + textCursor().block().setUserState(BLOCKTYPE_ACTIVE); completer_->setWidget(viewport()); + connect(&PythonInterpreter::Instance(), SIGNAL(Output(unsigned int, const QString &)), + this,SLOT(AppendOutput(unsigned int, const QString &))); + connect(&PythonInterpreter::Instance(), SIGNAL(Finished(unsigned int, bool)), + this,SLOT(OutputFinished(unsigned int,bool))); + connect(&PythonInterpreter::Instance(), SIGNAL(ErrorOutput(unsigned int, const QString &)), + this,SLOT(AppendError(unsigned int, const QString &))); + connect(this, SIGNAL(updateRequest(QRect, int)) , + gutter_,SLOT(Update(QRect, int))); connect(completer_,SIGNAL(activated(const QString&)),this, SLOT(InsertCompletion(const QString&))); connect(completer_,SIGNAL(recomplete(const QString&)),this, @@ -94,13 +108,216 @@ PythonShellWidget::PythonShellWidget(QWidget* parent): connect(this,SIGNAL(SetPathCompletionPrefix(const QString&)),path_completer_, SLOT(setCompletionPrefix(const QString&))); if (mode_==SHELL_INTERACTION_BASH) { - SetBlockEditMode(EDITMODE_SINGLELINE); + set_block_edit_mode_(EDITMODE_SINGLELINE); } else { - SetBlockEditMode(EDITMODE_MULTILINE_INACTIVE); + set_block_edit_mode_(EDITMODE_MULTILINE_INACTIVE); } - PythonInterpreter::Instance().RedirectOutput(); + setup_readonly_state_machine_(); + setup_state_machine_(); } +void PythonShellWidget::setup_readonly_state_machine_() +{ + State* readonly=new State; + readonly_machine_->addState(readonly); + readonly_machine_->addState(readwrite_state_); + readonly->addTransition((new SignalTransition(this, + SIGNAL(cursorPositionChanged()), + readwrite_state_, + new EditPositionGuard(this,EditPositionGuard::EQUAL |EditPositionGuard::BIGGER)))); + readonly->addTransition((new KeyEventTransition(QEvent::KeyPress, + Qt::Key_Any, + Qt::NoModifier, + readwrite_state_, + false))); + readwrite_state_->addTransition((new SignalTransition(this, + SIGNAL(cursorPositionChanged()), + readonly, + new EditPositionGuard(this,EditPositionGuard::SMALLER)))); + readwrite_state_->addTransition((new KeyEventTransition(QEvent::KeyPress, + Qt::Key_Backspace, + Qt::NoModifier, + readwrite_state_, + true, + new EditPositionGuard(this,EditPositionGuard::EQUAL)))); + connect(readonly,SIGNAL(entered()),this,SLOT(OnReadonlyEntered())); + connect(readwrite_state_,SIGNAL(entered()),this,SLOT(OnReadwriteEntered())); + readonly_machine_->setInitialState(readwrite_state_); + readonly_machine_->start(); +} + +void PythonShellWidget::setup_state_machine_() +{ + State* single_line=new State; + State* multi_line_inactive=new State; + State* completing=new State; + State* executing=new State; + State* history_up=new State; + State* history_down=new State; + machine_->addState(single_line); + machine_->addState(multiline_active_state_); + machine_->addState(multi_line_inactive); + machine_->addState(completing); + machine_->addState(executing); + machine_->addState(history_up); + machine_->addState(history_down); + + connect(single_line,SIGNAL(entered()),this,SLOT(OnSingleLineStateEntered())); + connect(multiline_active_state_,SIGNAL(entered()),this,SLOT(OnMultiLineActiveStateEntered())); + connect(multi_line_inactive,SIGNAL(entered()),this,SLOT(OnMultiLineInactiveStateEntered())); + connect(history_up,SIGNAL(entered()),this,SLOT(OnHistoryUpStateEntered())); + connect(history_down,SIGNAL(entered()),this,SLOT(OnHistoryDownStateEntered())); + connect(executing,SIGNAL(entered()),this,SLOT(OnExecuteStateEntered())); + + if (mode_==SHELL_INTERACTION_BASH) { + KeyEventTransition* tr1=new KeyEventTransition(QEvent::KeyPress, + Qt::Key_Return, + Qt::NoModifier, + executing, + true, + new BlockStatusGuard(this,CODE_BLOCK_COMPLETE | CODE_BLOCK_ERROR)); + single_line->addTransition(tr1); + connect(tr1,SIGNAL(triggered()),this,SLOT(OnEnterTransition())); + KeyEventTransition* tr3=new KeyEventTransition(QEvent::KeyPress, + Qt::Key_Return, + Qt::NoModifier, + multiline_active_state_, + true, + new BlockStatusGuard(this,CODE_BLOCK_INCOMPLETE)); + single_line->addTransition(tr3); + connect(tr3,SIGNAL(triggered()),this,SLOT(OnEnterTransition())); + single_line->addTransition(new KeyEventTransition(QEvent::KeyPress,Qt::Key_Up,Qt::NoModifier,history_up)); + single_line->addTransition(new KeyEventTransition(QEvent::KeyPress,Qt::Key_Down,Qt::NoModifier,history_down)); + + KeyEventTransition* tr4=new KeyEventTransition(QEvent::KeyPress, + Qt::Key_Return, + Qt::NoModifier, + executing, + true, + new BlockStatusGuard(this,CODE_BLOCK_COMPLETE | CODE_BLOCK_ERROR)); + multi_line_inactive->addTransition(tr4); + connect(tr4,SIGNAL(triggered()),this,SLOT(OnEnterTransition())); + KeyEventTransition* tr6=new KeyEventTransition(QEvent::KeyPress, + Qt::Key_Return, + Qt::NoModifier, + multiline_active_state_, + true, + new BlockStatusGuard(this,CODE_BLOCK_INCOMPLETE)); + multi_line_inactive->addTransition(tr6); + connect(tr6,SIGNAL(triggered()),this,SLOT(OnEnterTransition())); + multi_line_inactive->addTransition(new KeyEventTransition(QEvent::KeyPress,Qt::Key_Left,Qt::NoModifier,multiline_active_state_)); + multi_line_inactive->addTransition(new KeyEventTransition(QEvent::KeyPress,Qt::Key_Right,Qt::NoModifier,multiline_active_state_)); + multi_line_inactive->addTransition(new KeyEventTransition(QEvent::KeyPress,Qt::Key_Return,Qt::ControlModifier,multiline_active_state_)); + multi_line_inactive->addTransition(new KeyEventTransition(QEvent::KeyPress,Qt::Key_Up,Qt::NoModifier,history_up)); + multi_line_inactive->addTransition(new KeyEventTransition(QEvent::KeyPress,Qt::Key_Down,Qt::NoModifier,history_down)); + + KeyEventTransition* tr7=new KeyEventTransition(QEvent::KeyPress, + Qt::Key_Return, + Qt::NoModifier, + executing, + true, + new BlockStatusGuard(this,CODE_BLOCK_COMPLETE | CODE_BLOCK_ERROR)); + multiline_active_state_->addTransition(tr7); + connect(tr7,SIGNAL(triggered()),this,SLOT(OnEnterTransition())); + KeyEventTransition* tr8=new KeyEventTransition(QEvent::KeyPress, + Qt::Key_Return, + Qt::NoModifier, + multiline_active_state_, + true, + new BlockStatusGuard(this,CODE_BLOCK_INCOMPLETE)); + multiline_active_state_->addTransition(tr8); + connect(tr8,SIGNAL(triggered()),this,SLOT(OnEnterTransition())); + + multiline_active_state_->addTransition(new KeyEventTransition(QEvent::KeyPress,Qt::Key_Escape,Qt::NoModifier,multi_line_inactive)); + multiline_active_state_->addTransition(new KeyEventTransition(QEvent::KeyPress,Qt::Key_Up,Qt::ControlModifier,history_up)); + multiline_active_state_->addTransition(new KeyEventTransition(QEvent::KeyPress,Qt::Key_Down,Qt::ControlModifier,history_down)); + + history_up->addTransition(new AutomaticTransition(multi_line_inactive,new HistoryGuard(&history_,EDITMODE_MULTILINE_INACTIVE))); + history_up->addTransition(new AutomaticTransition(single_line,new HistoryGuard(&history_,EDITMODE_SINGLELINE))); + history_down->addTransition(new AutomaticTransition(multi_line_inactive,new HistoryGuard(&history_,EDITMODE_MULTILINE_INACTIVE))); + history_down->addTransition(new AutomaticTransition(single_line,new HistoryGuard(&history_,EDITMODE_SINGLELINE))); + + executing->addTransition(new AutomaticTransition(single_line)); + + machine_->setInitialState(single_line); + } else { + multi_line_inactive->addTransition(new KeyEventTransition(QEvent::KeyPress,Qt::Key_Return,Qt::ControlModifier,executing)); + multi_line_inactive->addTransition(new KeyEventTransition(QEvent::KeyPress,Qt::Key_Return,Qt::NoModifier,multiline_active_state_)); + + multiline_active_state_->addTransition(new KeyEventTransition(QEvent::KeyPress,Qt::Key_Return,Qt::ControlModifier,executing)); + + executing->addTransition(new AutomaticTransition(multi_line_inactive)); + + history_up->addTransition(new AutomaticTransition(multi_line_inactive)); + history_down->addTransition(new AutomaticTransition(multi_line_inactive)); + + machine_->setInitialState(multi_line_inactive); + } + + machine_->start(); +} + +void PythonShellWidget::OnReadonlyEntered() +{ + setReadOnly(true); +} + +void PythonShellWidget::OnReadwriteEntered() +{ + if(textCursor().position()< GetEditStartBlock().position()){ + moveCursor(QTextCursor::End); + } + setReadOnly(false); +} + +void PythonShellWidget::OnEnterTransition() +{ + QTextCursor cursor=textCursor(); + cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor); + setTextCursor(cursor); +} +void PythonShellWidget::OnSingleLineStateEntered() +{ + set_block_edit_mode_(EDITMODE_SINGLELINE); +} +void PythonShellWidget::OnMultiLineActiveStateEntered() +{ + insertPlainText(QString(QChar::ParagraphSeparator)); + set_block_edit_mode_(EDITMODE_MULTILINE_ACTIVE); +} +void PythonShellWidget::OnMultiLineInactiveStateEntered() +{ + set_block_edit_mode_(EDITMODE_MULTILINE_INACTIVE); +} +void PythonShellWidget::OnHistoryUpStateEntered() +{ + --history_; + set_command_(history_.GetCommand()); + set_block_edit_mode_(history_.GetCommandMode()); +} +void PythonShellWidget::OnHistoryDownStateEntered() +{ + ++history_; + set_command_(history_.GetCommand()); + set_block_edit_mode_(history_.GetCommandMode()); +} +void PythonShellWidget::OnExecuteStateEntered() +{ + set_block_type_(block_edit_start_,textCursor().block(),BLOCKTYPE_CODE); + insertPlainText(QString(QChar::ParagraphSeparator)); + QString command=GetCommand(); + unsigned int id=PythonInterpreter::Instance().RunCommand(command); + output_blocks_.insert(id,textCursor().block()); + command=command.trimmed(); + if (command.size()>0) { + history_.AppendCommand(command,get_block_edit_mode_()); + } + insertPlainText(QString(QChar::ParagraphSeparator)); + block_edit_start_=textCursor().block(); + +} + + void PythonShellWidget::SetTabWidth(int width) { tab_width_=width; QFontMetrics metrics(font()); @@ -115,14 +332,14 @@ int PythonShellWidget::GetTabWidth() const { -void PythonShellWidget::WrapIntoFunction(const QString& command) +void PythonShellWidget::wrap_into_function_(const QString& command) { QString tmp_command=command; tmp_command.replace(QString(QChar::LineSeparator), QString(QChar::LineSeparator)+QString("\t")); QString tmp="def func():"+QString(QChar::LineSeparator)+"\t"; tmp+=tmp_command; - SetCommand(tmp); + set_command_(tmp); QTextCursor tc=textCursor(); tc.setPosition(document()->lastBlock().position()+QString("def ").length()); tc.setPosition(document()->lastBlock().position()+ @@ -146,51 +363,79 @@ void PythonShellWidget::InsertCompletion(const QString& completion) } -void PythonShellWidget::AppendOutput(int status,const QString& output) +void PythonShellWidget::AppendOutput(unsigned int id,const QString& output) { - QStringList output_list=output.split("\n"); - if (output_list.size()>=maximumBlockCount ()){ - QTextCursor cursor=textCursor(); - cursor.movePosition(QTextCursor::Start); - cursor.movePosition(QTextCursor::End,QTextCursor::KeepAnchor); - cursor.removeSelectedText(); - output_list.erase(output_list.begin(),output_list.begin()+ - output_list.size()+1-maximumBlockCount()); - } else if (output_list.size()+blockCount()>maximumBlockCount()){ - QTextCursor cursor=textCursor(); - cursor.movePosition(QTextCursor::Start); - cursor.movePosition(QTextCursor::NextBlock,QTextCursor::KeepAnchor, - output_list.size()+1+blockCount()-maximumBlockCount()); - cursor.removeSelectedText(); + OutputBlockList::iterator it=output_blocks_.find(id); + if(it==output_blocks_.end()){ + return; } - moveCursor(QTextCursor::End); - QTextCursor cursor=textCursor(); - cursor.beginEditBlock(); - if (output!="" && output_list.size()>0) { - for(int i=0;i<output_list.size();++i){ - document()->lastBlock().setUserState(status); - insertPlainText(output_list[i]); - insertPlainText(QString(QChar::ParagraphSeparator)); - } + if(! it->isValid()){ + return; + } + QTextCursor cursor(*it); + cursor.movePosition(QTextCursor::EndOfBlock); + cursor.insertText(output); + set_block_type_(output_blocks_[id],cursor.block(),BLOCKTYPE_OUTPUT); + output_blocks_[id]=cursor.block(); + verticalScrollBar()->triggerAction(QAbstractSlider::SliderToMaximum); + ensureCursorVisible(); + repaint(); +} + +void PythonShellWidget::AppendError(unsigned int id,const QString& output) +{ + OutputBlockList::iterator it=output_blocks_.find(id); + if(it==output_blocks_.end()){ + return; + } + if(! it->isValid()){ + return; } - document()->lastBlock().setUserState(BLOCKTYPE_ACTIVE); - cursor.endEditBlock(); + QTextCursor cursor(*it); + cursor.movePosition(QTextCursor::EndOfBlock); + cursor.insertText(output); + set_block_type_(output_blocks_[id],cursor.block(),BLOCKTYPE_ERROR); + output_blocks_[id]=cursor.block(); verticalScrollBar()->triggerAction(QAbstractSlider::SliderToMaximum); - this->ensureCursorVisible(); + ensureCursorVisible(); + repaint(); } +void PythonShellWidget::set_block_type_(const QTextBlock& start, const QTextBlock& end, BlockType type) +{ + QTextBlock block=start; + while(block!=end){ + block.setUserState(type); + block=block.next(); + } + block.setUserState(type); + document()->markContentsDirty(start.position(),end.position()+end.length()-start.position()); +} +void PythonShellWidget::OutputFinished(unsigned int id, bool error) +{ + OutputBlockList::iterator it=output_blocks_.find(id); + if(it==output_blocks_.end()){ + return; + } + if(it->isValid()){ + QTextCursor cursor(*it); + cursor.movePosition(QTextCursor::Left,QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + } + output_blocks_.erase(it); +} -void PythonShellWidget::SetCommand(const QString& command) +void PythonShellWidget::set_command_(const QString& command) { - QTextCursor cursor=textCursor(); - cursor.setPosition(document()->lastBlock().position()); + QTextCursor cursor(block_edit_start_); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); - cursor.beginEditBlock(); cursor.removeSelectedText(); + if(block_edit_start_.isValid()){ + block_edit_start_=document()->lastBlock(); + } cursor.insertText(command); - cursor.endEditBlock(); - QAbstractTextDocumentLayout* layout=document()->documentLayout(); + QAbstractTextDocumentLayout* layout=document()->documentLayout(); dynamic_cast<PythonShellTextDocumentLayout*>(layout)->EmitSizeChange(); verticalScrollBar()->triggerAction(QAbstractSlider::SliderToMaximum); ensureCursorVisible(); @@ -253,69 +498,35 @@ void PythonShellWidget::Complete(bool inline_completion) } } -bool PythonShellWidget::HandleNav(QKeyEvent* event) -{ - if(event->key() == Qt::Key_Up){ - if (GetBlockEditMode()!=EDITMODE_MULTILINE_ACTIVE) { - SanitizeCursorPosition(); - if(history_.AtEnd()){ - history_.SetCurrentCommand(textCursor().block().text(), - GetBlockEditMode()); - } - --history_; - SetCommand(history_.GetCommand()); - this->SetBlockEditMode(history_.GetCommandMode()); - return true; - }else{ - QTextCursor temporary_cursor=textCursor(); - int temp_position_before=temporary_cursor.position(); - temporary_cursor.movePosition(QTextCursor::Up); - if (temporary_cursor.position()<document()->lastBlock().position() && - temp_position_before>=document()->lastBlock().position() && - !(event->modifiers() & Qt::ShiftModifier)) { - return true; - } - } - } - if (event->key() == Qt::Key_Down && - GetBlockEditMode()!=EDITMODE_MULTILINE_ACTIVE) { - SanitizeCursorPosition(); - ++history_; - SetCommand(history_.GetCommand()); - this->SetBlockEditMode(history_.GetCommandMode()); - return true; - } - return false; -} -bool PythonShellWidget::HandleCustomCommands(QKeyEvent* event) + +bool PythonShellWidget::handle_custom_commands_(QKeyEvent* event) { - // see ticket #38 - #if 0 + /* deactivated until fix found if ((event->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) && event->key() == Qt::Key_H) { - SetOutputVisible(!output_visible_); + set_output_visible_(!output_visible_); return true; - } - #endif + }*/ + if ((event->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) && event->key() == Qt::Key_W) { - WrapIntoFunction(textCursor().selectedText()); + wrap_into_function_(textCursor().selectedText()); return true; } // custom handling of CTRL+A to select only text in edit or output section if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_A) { QTextCursor cursor=textCursor(); - if(cursor.position()<document()->lastBlock().position()){ + if(cursor.position()<GetEditStartBlock().position()){ // select all output cursor.setPosition(0); - cursor.setPosition(document()->lastBlock().position(),QTextCursor::KeepAnchor); + cursor.setPosition(GetEditStartBlock().position(),QTextCursor::KeepAnchor); setTextCursor(cursor); }else{ //select whole edit section - cursor.movePosition(QTextCursor::StartOfBlock); - cursor.movePosition(QTextCursor::EndOfBlock,QTextCursor::KeepAnchor); + cursor.setPosition(GetEditStartBlock().position()); + cursor.movePosition(QTextCursor::End,QTextCursor::KeepAnchor); setTextCursor(cursor); } return true; @@ -323,81 +534,8 @@ bool PythonShellWidget::HandleCustomCommands(QKeyEvent* event) return false; } -bool PythonShellWidget::HandleReturnKey(QKeyEvent* event) -{ - if(event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) { - QString command=textCursor().block().text(); - SanitizeCursorPosition(); - command.replace(QString(QChar::LineSeparator),QString("\n")); - bool execute_cmd=false; - bool insert_new_line=false; - bool move_to_end=false; - if (mode_==SHELL_INTERACTION_MATHEMATICA) { - if (event->modifiers() & Qt::ControlModifier || - (command.endsWith("\n") && textCursor().atEnd())) { - execute_cmd=true; - move_to_end=(event->modifiers() & Qt::ControlModifier)==false; - } else { - if (textCursor().block().text().trimmed().length()==0) { - return true; - } - insert_new_line=true; - move_to_end=true; - } - } else { - if (event->modifiers() & Qt::ControlModifier) { - insert_new_line=true; - move_to_end=false; - } else { - PythonInterpreter& pi=PythonInterpreter::Instance(); - CodeBlockStatus status=pi.GetCodeBlockStatus(command); - if (status!=CODE_BLOCK_INCOMPLETE) { - execute_cmd=true; - move_to_end=true; - } else { - insert_new_line=true; - move_to_end=true; - } - } - } - QTextCursor cursor=this->textCursor(); - if (move_to_end) { - cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor); - this->setTextCursor(cursor); - } - if (insert_new_line) { - cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor); - bool new_indent=cursor.selectedText()==":"; - cursor.movePosition(QTextCursor::StartOfLine); - cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor); - insertPlainText(QString(QChar::LineSeparator)); - if(cursor.selectedText()[0].isSpace()){ - insertPlainText(QString(cursor.selectedText())); - } - if (new_indent){ - insertPlainText(QString("\t")); - } - QAbstractTextDocumentLayout* layout=document()->documentLayout(); - dynamic_cast<PythonShellTextDocumentLayout*>(layout)->EmitSizeChange(); - verticalScrollBar()->triggerAction(QAbstractSlider::SliderToMaximum); - SetBlockEditMode(EDITMODE_MULTILINE_ACTIVE); - return true; - } - if (execute_cmd) { - QKeyEvent new_event(event->type(),event->key(), - Qt::NoModifier, - event->text(),event->isAutoRepeat(), - event->count()); - QPlainTextEdit::keyPressEvent(&new_event); - ExecuteCommand(); - return true; - } - } - return false; -} - -bool PythonShellWidget::HandleCompletion(QKeyEvent* event) +bool PythonShellWidget::handle_completion_(QKeyEvent* event) { if(event->key() == Qt::Key_Tab){ QRegExp rx("^\\s*$"); // only white spaces from beginning of line @@ -413,79 +551,32 @@ bool PythonShellWidget::HandleCompletion(QKeyEvent* event) return false; } +QTextBlock PythonShellWidget::GetEditStartBlock() +{ + return block_edit_start_; +} + void PythonShellWidget::keyPressEvent(QKeyEvent* event) { - if (this->HandleCustomCommands(event)) - return; - - if (this->HandleCompletion(event)) - return; - if (this->HandleNav(event)) - return; - - if (this->HandleReturnKey(event)) - return; - if ((event->text()!="" && !(event->modifiers() & Qt::ControlModifier)) || - event->matches(QKeySequence::Cut) || event->key()== Qt::Key_Delete){ - SanitizeCursorPosition(); - if(GetBlockEditMode()==EDITMODE_MULTILINE_INACTIVE){ - SetBlockEditMode(EDITMODE_MULTILINE_ACTIVE); - } - } - if (GetBlockEditMode()==EDITMODE_MULTILINE_ACTIVE && event->key()==Qt::Key_Escape){ - SetBlockEditMode(EDITMODE_MULTILINE_INACTIVE); - } - - if (GetBlockEditMode()==EDITMODE_MULTILINE_INACTIVE && (event->key()==Qt::Key_Right || - event->key() == Qt::Key_Left)) { - SetBlockEditMode(EDITMODE_MULTILINE_ACTIVE); - } - - if (event->key()== Qt::Key_Backspace && - textCursor().position()<=document()->lastBlock().position() - && !textCursor().hasSelection()){ + if (this->handle_custom_commands_(event)){ return; } - - - if (event->key()== Qt::Key_Left && - textCursor().position()==document()->lastBlock().position() && - !(event->modifiers() & Qt::ShiftModifier)) { + if (this->handle_completion_(event)){ return; } QPlainTextEdit::keyPressEvent(event); } -void PythonShellWidget::ExecuteCommand() -{ - QString command=textCursor().block().previous().text(); - textCursor().block().previous().setUserState(BLOCKTYPE_CODE); - document()->markContentsDirty(textCursor().block().previous().position(), - textCursor().block().previous().length()); - QString cmd=command; - cmd.replace(QString(QChar::LineSeparator),QString("\n")); - emit Execute(cmd); - - command=command.trimmed(); - if (command.size()>0) { - history_.AppendCommand(command,GetBlockEditMode()); - } - if (mode_==SHELL_INTERACTION_MATHEMATICA) { - SetBlockEditMode(EDITMODE_MULTILINE_INACTIVE); - } else { - SetBlockEditMode(EDITMODE_SINGLELINE); - } -} void PythonShellWidget::mouseReleaseEvent(QMouseEvent * event) { QTextCursor mouse_cursor=cursorForPosition(event->pos()); - if (GetBlockEditMode()==EDITMODE_MULTILINE_INACTIVE && + if (get_block_edit_mode_()==EDITMODE_MULTILINE_INACTIVE && event->button() == Qt::LeftButton && mouse_cursor.position()>=document()->lastBlock().position()) { // switch to active edit mode upon mouse click in edit section - SetBlockEditMode(EDITMODE_MULTILINE_ACTIVE); + set_block_edit_mode_(EDITMODE_MULTILINE_ACTIVE); } if (event->button() == Qt::MidButton && mouse_cursor.position()<document()->lastBlock().position()) { @@ -496,51 +587,34 @@ void PythonShellWidget::mouseReleaseEvent(QMouseEvent * event) QPlainTextEdit::mouseReleaseEvent(event); } -void PythonShellWidget::SanitizeCursorPosition() -{ - if(textCursor().position()<document()->lastBlock().position()){ - if(textCursor().anchor()<document()->lastBlock().position()){ - moveCursor(QTextCursor::End); - }else{ - QTextCursor cursor=textCursor(); - cursor.setPosition(document()->lastBlock().position(), - QTextCursor::KeepAnchor); - setTextCursor(cursor); - } - }else{ - if(textCursor().anchor()<document()->lastBlock().position()){ - int current_position=textCursor().position(); - QTextCursor cursor=textCursor(); - cursor.setPosition(document()->lastBlock().position()); - cursor.setPosition(current_position,QTextCursor::KeepAnchor); - setTextCursor(cursor); - } - } -} -void PythonShellWidget::SetBlockEditMode(BlockEditMode flag) + +void PythonShellWidget::set_block_edit_mode_(BlockEditMode flag) { block_edit_mode_=flag; if(flag==EDITMODE_MULTILINE_ACTIVE){ - document()->lastBlock().setUserState(BLOCKTYPE_BLOCKEDIT); + set_block_type_(block_edit_start_,document()->lastBlock(),BLOCKTYPE_BLOCKEDIT); }else if(flag==EDITMODE_MULTILINE_INACTIVE){ - document()->lastBlock().setUserState(BLOCKTYPE_ACTIVE); + set_block_type_(block_edit_start_,document()->lastBlock(),BLOCKTYPE_ACTIVE); }else { - document()->lastBlock().setUserState(BLOCKTYPE_ACTIVE); + set_block_type_(block_edit_start_,document()->lastBlock(),BLOCKTYPE_ACTIVE); } gutter_->update(); - document()->markContentsDirty(document()->lastBlock().position(), - document()->lastBlock().length()); } -BlockEditMode PythonShellWidget::GetBlockEditMode() +BlockEditMode PythonShellWidget::get_block_edit_mode_() { return block_edit_mode_; } QString PythonShellWidget::GetCommand() { - return document()->lastBlock().text(); + + QTextCursor cursor(block_edit_start_); + cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); + QString text= cursor.selectedText(); + text.replace(QChar::ParagraphSeparator,"\n"); + return text; } void PythonShellWidget::insertFromMimeData(const QMimeData * source) @@ -549,7 +623,7 @@ void PythonShellWidget::insertFromMimeData(const QMimeData * source) return; } QString text=source->text(); - SanitizeCursorPosition(); + readonly_machine_->setActive(readwrite_state_); text=text.replace("\r\n", QString(1, QChar::LineSeparator)) .replace('\n', QChar::LineSeparator) .replace('\r', QChar::LineSeparator); @@ -570,18 +644,9 @@ void PythonShellWidget::insertFromMimeData(const QMimeData * source) text=lines.join(QString(1, QChar::LineSeparator)); int line_sep=text.count(QChar::LineSeparator); if(line_sep>0){ - QString command=GetCommand(); - int last_block_start=document()->lastBlock().position(); - int last_block_end=last_block_start+document()->lastBlock().length(); - int shifted_start=textCursor().selectionStart()-last_block_start; - int shifted_end=last_block_end-textCursor().selectionEnd(); - QString rpl_text=command.left(shifted_start)+text+ - command.right(shifted_end-1); - this->SetCommand(rpl_text); - SetBlockEditMode(EDITMODE_MULTILINE_ACTIVE); - }else{ - QPlainTextEdit::insertFromMimeData(source); + machine_->setActive(multiline_active_state_); } + QPlainTextEdit::insertFromMimeData(source); } GutterBlockList PythonShellWidget::GetGutterBlocks(const QRect& rect) @@ -602,7 +667,7 @@ GutterBlockList PythonShellWidget::GetGutterBlocks(const QRect& rect) return result; } -void PythonShellWidget::SetOutputVisible(bool flag) +void PythonShellWidget::set_output_visible_(bool flag) { output_visible_=flag; for (QTextBlock i=document()->begin(); i!=document()->end(); i=i.next()) { @@ -610,9 +675,10 @@ void PythonShellWidget::SetOutputVisible(bool flag) i.setVisible(flag); } } + repaint(); gutter_->update(); - viewport()->update(); - this->update(); + viewport()->update(); + this->update(); } void PythonShellWidget::resizeEvent(QResizeEvent* event) @@ -636,14 +702,5 @@ void PythonShellWidget::AquireFocus() setFocus(Qt::OtherFocusReason); } -void PythonShellWidget::AddLogger(TextLogger* logger) -{ - loggers_.push_back(logger); - logger->setParent(this); - connect(this,SIGNAL(Execute(const QString&)), - logger,SLOT(AppendCode(const QString&))); - connect(&PythonInterpreter::Instance(),SIGNAL(Done(int,const QString&)), - logger,SLOT(AppendOutput(int,const QString&))); -} }} diff --git a/modules/gui/src/python_shell/python_shell_widget.hh b/modules/gui/src/python_shell/python_shell_widget.hh index b4c34ce45..d55f8c3c3 100644 --- a/modules/gui/src/python_shell/python_shell_widget.hh +++ b/modules/gui/src/python_shell/python_shell_widget.hh @@ -16,23 +16,24 @@ // along with this library; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA //------------------------------------------------------------------------------ -#ifndef OST_GUI_PYTHON_SHELL_WIDGET_HH -#define OST_GUI_PYTHON_SHELL_WIDGET_HH - /* Authors: Marco Biasini, Andreas Schenk */ -#include <ost/gui/module_config.hh> +#ifndef OST_GUI_PYTHON_SHELL_WIDGET_HH +#define OST_GUI_PYTHON_SHELL_WIDGET_HH + + +#include <ost/gui/module_config.hh> +#include <QPlainTextEdit> #include "python_interpreter.hh" #include "shell_history.hh" #include "python_shell_fw.hh" - #include "python_syntax_highlighter.hh" - -#include <QPlainTextEdit> +#include "state_machine.hh" +#include "state.hh" namespace ost { namespace gui { @@ -40,7 +41,6 @@ namespace ost { namespace gui { class Gutter; class PythonCompleter; class PathCompleter; -class TextLogger; typedef enum { // to execute a command, the command must end with an empty line, or @@ -52,19 +52,21 @@ typedef enum { } ShellInteractionMode; class DLLEXPORT_OST_GUI PythonShellWidget : public QPlainTextEdit { + + typedef QHash<unsigned int,QTextBlock> OutputBlockList; Q_OBJECT + public: PythonShellWidget(QWidget* parent=NULL); GutterBlockList GetGutterBlocks(const QRect& rect); - void SetOutputVisible(bool flag=true); - - void AddLogger(TextLogger* logger); - void SetInteractionMode(ShellInteractionMode mode); - void SetTabWidth(int width); int GetTabWidth() const; + QString GetCommand(); + QTextBlock GetEditStartBlock(); + int tab_width_; + signals: void Execute(const QString& command); void RequestCompletion(const QRect& rect,bool inline_completion); @@ -74,30 +76,39 @@ signals: public slots: void InsertCompletion(const QString& completion); - void AppendOutput(int status,const QString& output); + void AppendOutput(unsigned int id,const QString& output); + void AppendError(unsigned int id,const QString& output); + void OutputFinished(unsigned int id, bool error); void AquireFocus(); void Complete(bool inline_completion=true); void Recomplete(const QString& completion); + // slots for state machine + void OnSingleLineStateEntered(); + void OnMultiLineActiveStateEntered(); + void OnMultiLineInactiveStateEntered(); + void OnHistoryUpStateEntered(); + void OnHistoryDownStateEntered(); + void OnExecuteStateEntered(); + void OnEnterTransition(); + void OnReadonlyEntered(); + void OnReadwriteEntered(); protected: virtual void mouseReleaseEvent (QMouseEvent* event ); virtual void keyPressEvent (QKeyEvent* event ); virtual void resizeEvent(QResizeEvent* event); virtual void showEvent(QShowEvent* event); - - void WrapIntoFunction(const QString& command); - void SetCommand(const QString& command); - QString GetCommand(); - void insertFromMimeData( const QMimeData * source ); - void SanitizeCursorPosition(); - void SetBlockEditMode(BlockEditMode flag); - - bool HandleNav(QKeyEvent* event); - bool HandleCompletion(QKeyEvent* event); - bool HandleCustomCommands(QKeyEvent* event); - bool HandleReturnKey(QKeyEvent* event); - BlockEditMode GetBlockEditMode(); - void ExecuteCommand(); + virtual void insertFromMimeData( const QMimeData * source ); + void set_output_visible_(bool flag=true); + void setup_readonly_state_machine_(); + void setup_state_machine_(); + void wrap_into_function_(const QString& command); + void set_command_(const QString& command); + void set_block_edit_mode_(BlockEditMode flag); + bool handle_completion_(QKeyEvent* event); + bool handle_custom_commands_(QKeyEvent* event); + BlockEditMode get_block_edit_mode_(); + void set_block_type_(const QTextBlock& start,const QTextBlock& end, BlockType type); PythonSyntaxHighlighter highlighter_; PythonCompleter* completer_; @@ -108,8 +119,13 @@ protected: bool output_visible_; int completion_start_; int completion_end_; - std::vector<TextLogger*> loggers_; - ShellInteractionMode mode_; + ShellInteractionMode mode_; + QTextBlock block_edit_start_; + OutputBlockList output_blocks_; + StateMachine* machine_; + StateMachine* readonly_machine_; + State* readwrite_state_; + State* multiline_active_state_; }; }}//ns diff --git a/modules/gui/src/python_shell/state.cc b/modules/gui/src/python_shell/state.cc new file mode 100644 index 000000000..2d0c8e51c --- /dev/null +++ b/modules/gui/src/python_shell/state.cc @@ -0,0 +1,84 @@ +#include <cassert> +#include "state_machine.hh" +#include "transition.hh" +#include "state.hh" + +namespace ost { namespace gui { + +State::State(): + QObject(), + mouse_event_transitions_(), + key_event_transitions_(), + automatic_transitions_() +{ +} +void State::addTransition(SignalTransition* transition) +{ + transition->setParent(this); +} +void State::addTransition(MouseEventTransition* transition) +{ + transition->setParent(this); + mouse_event_transitions_.append(transition); +} +void State::addTransition(KeyEventTransition* transition) +{ + transition->setParent(this); + key_event_transitions_.append(transition); +} +void State::addTransition(AutomaticTransition* transition) +{ + transition->setParent(this); + automatic_transitions_.append(transition); +} +bool State::isActive() +{ + return parent() && dynamic_cast<StateMachine*>(parent())->isActive(this); +} +void State::setActive() +{ + assert(parent()); + dynamic_cast<StateMachine*>(parent())->setActive(this); +} + +bool State::checkEvent(QKeyEvent* event) +{ + for(QList<KeyEventTransition*>::iterator it=key_event_transitions_.begin();it!=key_event_transitions_.end();++it){ + std::pair<bool,bool> pair=(*it)->checkEvent(event); + if(pair.first){ + return pair.second; + } + } + return false; +} + +bool State::checkAutomaticTransitions() +{ + for(QList<AutomaticTransition*>::iterator it=automatic_transitions_.begin();it!=automatic_transitions_.end();++it){ + if((*it)->checkTransition()){ + return true; + } + } + return false; +} + +bool State::checkEvent(QMouseEvent* event) +{ + for(QList<MouseEventTransition*>::iterator it=mouse_event_transitions_.begin();it!=mouse_event_transitions_.end();++it){ + std::pair<bool,bool> pair=(*it)->checkEvent(event); + if(pair.first){ + return pair.second; + } + } + return false; +} +void State::onEntry() +{ + emit entered(); +} +void State::onExit() +{ + emit exited(); +} + +}}//ns diff --git a/modules/gui/src/python_shell/state.hh b/modules/gui/src/python_shell/state.hh new file mode 100644 index 000000000..a6c05c36b --- /dev/null +++ b/modules/gui/src/python_shell/state.hh @@ -0,0 +1,45 @@ +#ifndef PYTHON_SHELL_STATE_HH +#define PYTHON_SHELL_STATE_HH + +#include <QObject> +#include <QList> + +//fw decl +class QKeyEvent; +class QMouseEvent; + +namespace ost { namespace gui { + +//fw decl +class SignalTransition; +class MouseEventTransition; +class KeyEventTransition; +class AutomaticTransition; + +class State: public QObject{ +Q_OBJECT +public: + State(); + void addTransition(SignalTransition * transition); + void addTransition(MouseEventTransition * transition); + void addTransition(KeyEventTransition* transition); + void addTransition(AutomaticTransition* transition); + bool isActive(); + void setActive(); + bool checkEvent(QKeyEvent* event); + bool checkEvent(QMouseEvent* event); + bool checkAutomaticTransitions(); + virtual void onEntry(); + virtual void onExit(); +signals: + void entered(); + void exited(); +protected: + QList<MouseEventTransition*> mouse_event_transitions_; + QList<KeyEventTransition*> key_event_transitions_; + QList<AutomaticTransition*> automatic_transitions_; +}; + + +}}//ns +#endif // PYTHON_SHELL_STATE_HH diff --git a/modules/gui/src/python_shell/state_machine.cc b/modules/gui/src/python_shell/state_machine.cc new file mode 100644 index 000000000..9ce3f37bc --- /dev/null +++ b/modules/gui/src/python_shell/state_machine.cc @@ -0,0 +1,55 @@ +#include <QEvent> +#include <QKeyEvent> +#include <QMouseEvent> +#include "state.hh" +#include "state_machine.hh" + +namespace ost { namespace gui { + +StateMachine::StateMachine(QObject* parent): + QObject(parent) +{ + parent->installEventFilter(this); +} + +bool StateMachine::eventFilter (QObject * watched, QEvent * event) +{ + if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { + QKeyEvent* key_event = dynamic_cast<QKeyEvent*>(event); + return active_state_->checkEvent(key_event); + } else if(event->type() == QEvent::MouseButtonDblClick || + event->type() == QEvent::MouseButtonPress || + event->type() == QEvent::MouseButtonRelease || + event->type() == QEvent::MouseMove){ + QMouseEvent *mouse_event = dynamic_cast<QMouseEvent*>(event); + return active_state_->checkEvent(mouse_event); + } else { + return false; + } +} + +void StateMachine::addState(State* state) +{ + state->setParent(this); +} +void StateMachine::setInitialState(State* state) +{ + active_state_=state; +} +void StateMachine::start() +{ + active_state_->onEntry(); +} +bool StateMachine::isActive(State* state) +{ + return state==active_state_; +} +void StateMachine::setActive(State* state) +{ + active_state_->onExit(); + active_state_=state; + active_state_->onEntry(); + active_state_->checkAutomaticTransitions(); +} + +}}//ns diff --git a/modules/gui/src/python_shell/state_machine.hh b/modules/gui/src/python_shell/state_machine.hh new file mode 100644 index 000000000..ac265dfd9 --- /dev/null +++ b/modules/gui/src/python_shell/state_machine.hh @@ -0,0 +1,30 @@ +#ifndef PYTHON_SHELL_STATE_MACHINE_HH +#define PYTHON_SHELL_STATE_MACHINE_HH + +#include <QObject> + +//fw decl +class QEvent; + +namespace ost { namespace gui { + +//fw decl +class State; + +class StateMachine: public QObject{ +Q_OBJECT +public: + StateMachine(QObject* parent); + void addState(State* state); + void setInitialState(State* state); + void start(); + bool isActive(State* state); + void setActive(State* state); + bool eventFilter (QObject * watched, QEvent * event); +protected: + State* active_state_; +}; + +}}//ns + +#endif // PYTHON_SHELL_STATE_MACHINE_HH diff --git a/modules/gui/src/python_shell/transition.cc b/modules/gui/src/python_shell/transition.cc new file mode 100644 index 000000000..57559da98 --- /dev/null +++ b/modules/gui/src/python_shell/transition.cc @@ -0,0 +1,91 @@ +#include <cassert> +#include <QKeyEvent> +#include <QMouseEvent> +#include "python_shell_widget.hh" +#include "state.hh" +#include "transition.hh" + +namespace ost { namespace gui { + +TransitionBase::TransitionBase(State* target, TransitionGuard* guard): + QObject(), + target_(target), + guard_(guard) +{ + guard_->setParent(this); +} +void TransitionBase::trigger_() +{ + emit triggered(); + target_->setActive(); +} +bool TransitionBase::is_active_() +{ + return parent() && dynamic_cast<State*>(parent())->isActive(); +} + +AutomaticTransition::AutomaticTransition(State* target, TransitionGuard* guard): + TransitionBase(target,guard) +{ +} + +bool AutomaticTransition::checkTransition() +{ + if(guard_->check()){ + trigger_(); + return true; + } + return false; +} + +SignalTransition::SignalTransition(QObject * sender,const char * signal, State* target, TransitionGuard* guard): + TransitionBase(target,guard) +{ + connect(sender,signal,this,SLOT(onSignal())); +} +void SignalTransition::onSignal() +{ + if(is_active_() && guard_->check()){ + trigger_(); + } +} + +KeyEventTransition::KeyEventTransition(QEvent::Type type,int key,Qt::KeyboardModifiers modifiers, State* target, bool swallow_event, TransitionGuard* guard): + TransitionBase(target,guard), + type_(type), + key_(key), + modifiers_(modifiers), + swallow_(swallow_event) +{ +} +std::pair<bool,bool> KeyEventTransition::checkEvent(QKeyEvent* event) +{ + assert(is_active_()); + if(event->type()==type_ && (event->key()==key_ || key_==Qt::Key_Any) && event->modifiers() == modifiers_ && guard_->check()){ + trigger_(); + return std::pair<bool,bool>(true,swallow_); + } + return std::pair<bool,bool>(false,false); +} + +MouseEventTransition::MouseEventTransition(QEvent::Type type,Qt::MouseButton button,Qt::KeyboardModifiers modifiers, State* target, bool swallow_event, TransitionGuard* guard): + TransitionBase(target,guard), + type_(type), + button_(button_), + modifiers_(modifiers), + swallow_(swallow_event) +{ +} +std::pair<bool,bool> MouseEventTransition::checkEvent(QMouseEvent* event) +{ +// only gets called if active + assert(is_active_()); + if(event->type()==type_ && event->button()==button_ && event->modifiers() == modifiers_ && guard_->check()){ + trigger_(); + return std::pair<bool,bool>(true,swallow_); + } + return std::pair<bool,bool>(false,false); +} + + +}}//ns diff --git a/modules/gui/src/python_shell/transition.hh b/modules/gui/src/python_shell/transition.hh new file mode 100644 index 000000000..9b832e1af --- /dev/null +++ b/modules/gui/src/python_shell/transition.hh @@ -0,0 +1,77 @@ +#ifndef PYTHON_SHELL_TRANSITION_HH +#define PYTHON_SHELL_TRANSITION_HH + +#include <utility> +#include <QObject> +#include <QEvent> +#include <ost/gui/python_shell/python_interpreter.hh> +#include "transition_guard.hh" + +//fw decl +class QKeyEvent; +class QMouseEvent; + +namespace ost { namespace gui { + +//fw decl +class State; +class PythonShellWidget; +class ShellHistory; + +class TransitionBase: public QObject{ +Q_OBJECT +public: + TransitionBase(State* target, TransitionGuard* guard=new TransitionGuard()); +signals: + void triggered(); +protected: + void trigger_(); + bool is_active_(); + State* target_; + TransitionGuard* guard_; +}; + +class AutomaticTransition: public TransitionBase{ +Q_OBJECT +public: + AutomaticTransition(State* target, TransitionGuard* guard=new TransitionGuard()); + bool checkTransition(); +}; + +class SignalTransition: public TransitionBase{ +Q_OBJECT +public: + SignalTransition(QObject * sender,const char * signal, State* target, TransitionGuard* guard=new TransitionGuard()); +protected slots: + void onSignal(); +}; + +class KeyEventTransition: public TransitionBase{ +Q_OBJECT +public: + KeyEventTransition(QEvent::Type type,int key,Qt::KeyboardModifiers modifiers, State* target, bool swallow_event=true, TransitionGuard* guard=new TransitionGuard()); + virtual std::pair<bool,bool> checkEvent(QKeyEvent* event); +protected: + QEvent::Type type_; + int key_; + Qt::KeyboardModifiers modifiers_; + bool swallow_; +}; + +class MouseEventTransition: public TransitionBase{ +Q_OBJECT +public: + MouseEventTransition(QEvent::Type type,Qt::MouseButton button,Qt::KeyboardModifiers modifiers, State* target, bool swallow_event=true, TransitionGuard* guard=new TransitionGuard()); + virtual std::pair<bool,bool> checkEvent(QMouseEvent* event); +protected: + QEvent::Type type_; + Qt::MouseButton button_; + Qt::KeyboardModifiers modifiers_; + bool swallow_; +}; + + + + +}}//ns +#endif // PYTHON_SHELL_TRANSITION_HH diff --git a/modules/gui/src/python_shell/transition_guard.cc b/modules/gui/src/python_shell/transition_guard.cc new file mode 100644 index 000000000..4409dfe3b --- /dev/null +++ b/modules/gui/src/python_shell/transition_guard.cc @@ -0,0 +1,64 @@ +#include <iostream> +#include "transition_guard.hh" +#include "python_shell_widget.hh" +#include "shell_history.hh" + +namespace ost { namespace gui { + +TransitionGuard::TransitionGuard(): + QObject() +{ +} + +bool TransitionGuard::check() +{ + return true; +} + +HistoryGuard::HistoryGuard(ShellHistory* history, BlockEditMode mode): + TransitionGuard(), + history_(history), + mode_(mode) +{ +} + +bool HistoryGuard::check() +{ + return history_->GetCommandMode()==mode_; +} + +EditPositionGuard::EditPositionGuard(PythonShellWidget* shell, int flags): + TransitionGuard(), + shell_(shell), + flags_(flags) +{ +} + +bool EditPositionGuard::check() +{ + bool returnflag=false; + if(flags_ & SMALLER){ + returnflag |= shell_->textCursor().position()< shell_->GetEditStartBlock().position(); + } + if(flags_ & EQUAL){ + returnflag |= shell_->textCursor().position()== shell_->GetEditStartBlock().position(); + } + if(flags_ & BIGGER){ + returnflag |= shell_->textCursor().position()> shell_->GetEditStartBlock().position(); + } + return returnflag; +} + +BlockStatusGuard::BlockStatusGuard(PythonShellWidget* shell, int status): + TransitionGuard(), + shell_(shell), + status_(status) +{ +} + +bool BlockStatusGuard::check() +{ + QString command=shell_->GetCommand(); + return PythonInterpreter::Instance().GetCodeBlockStatus(command) & status_; +} +}} //ns diff --git a/modules/gui/src/python_shell/transition_guard.hh b/modules/gui/src/python_shell/transition_guard.hh new file mode 100644 index 000000000..d3e90b9a3 --- /dev/null +++ b/modules/gui/src/python_shell/transition_guard.hh @@ -0,0 +1,65 @@ +#ifndef TRANSITION_GUARD_HH +#define TRANSITION_GUARD_HH + +#include <QObject> +#include "python_shell_fw.hh" +#include "python_interpreter.hh" + +namespace ost { namespace gui { + + +//fw decl + class ShellHistory; + class PythonShellWidget; + + +class TransitionGuard: public QObject +{ +Q_OBJECT + +public: + TransitionGuard(); + virtual bool check(); +}; + + + +class HistoryGuard : public TransitionGuard +{ +public: + HistoryGuard(ShellHistory* history, BlockEditMode mode); + virtual bool check(); +protected: + ShellHistory* history_; + BlockEditMode mode_; +}; + +class EditPositionGuard : public TransitionGuard +{ +public: + enum FLAGS{ + SMALLER=1, + EQUAL=2, + BIGGER=4 + }; + EditPositionGuard(PythonShellWidget* shell,int flags); + virtual bool check(); +protected: + PythonShellWidget* shell_; + int flags_; +}; + +class BlockStatusGuard : public TransitionGuard +{ +public: + BlockStatusGuard(PythonShellWidget* shell, int status); + virtual bool check(); +protected: + PythonShellWidget* shell_; + int status_; +}; + +}} //ns + + +#endif // TRANSITION_GUARD_HH -- GitLab