# HG changeset patch # User Daniel J Sebald # Date 1366270568 18000 # Node ID 524c50060fb8df42c0d771eabecf05ba334b9268 # Parent e192525236ad2d32978fe1ed9cf5b44a712ee682 New background command queue for GUI to send commands and receive data. * file-editor-tab.cc (file_editor_tab::file_editor_tab): Added file_editor pointer to constructor. Connect background command signal to file_editor equivalent signal. (file_editor_tab::run_file_callback, file_editor_tab::add_breakpoint_callback, file_editor_tab::remove_breakpoint_callback, file_editor_tab::remove_all_breakpoints_callback): Remove. (file_editor_tab::request_add_breakpoint): Remove post_event, queue 'dbstop ()' command. (file_editor_tab::request_remove_breakpoint): Remove post_event, queue 'dbclear ()' command. (file_editor_tab::remove_all_breakpoints): Remove post_event, queue command 'dbclear ()' command. (file_editor_tab::add_all_breakpoints): New. Gets octave_value for 'file' and 'line', converts 'line' to array for 'file' name match, and calls do_breakpoint_marker for each entry in line number array. (file_editor_tab::load_file): Issue 'dbstatus()' command to background queue. * file-editor-tab.h: Add and "oct-obj.h" headers. (struct bp_info): Remove. (file_editor_tab::run_file_callback, file_editor_tab::add_breakpoint_callback, file_editor_tab::remove_breakpoint_callback, file_editor_tab::remove_all_breakpoints_callback): Remove. (file_editor_tab::add_all_breakpoints): New. * file-editor.cc (file_editor::request_new_file, file_editor::request_open_file): Add 'this' to list of file_editor_tab arguments. (file_editor::add_file_editor_tab): Connect have_octave_command_result file_editor signal to add_all_breakpoints file_editor_tab slot. * file-editor.h (file_editor::queue_background_octave_command, file_editor::have_octave_command_result): New signals. * main-window.cc (main_window::handle_save_workspace_request): Remove post_event, queue 'save ()' command. (main_window::handle_load_workspace_request): Remove post_event, queue 'load ()' command. (main_window::handle_clear_workspace_request): Remove post_event, queue 'clear ()' command. (main_window::handle_rename_variable_request): Remove post_event, queue assignment (=) command followed by 'clear()' command. (main_window::handle_clear_command_window_request): Remove post_event, queue 'clc ()' command. (main_window::handle_clear_history_request): Remove post_event, queue 'history ('-c')' command. (main_window::browse_for_directory): Remove post_event, queue 'cd ()' command. (main_window::set_current_working_directory): Remove post_event, queue 'cd ()' command. (main_window::debug_continue, main_window::debug_step_into, main_window::debug_step_out): Remove post_event, queue 'dbcont ()' command. (main_window::debug_quit): Remove post_event, queue 'dbquit ()' command. (main_window::closeEvent): Remove post_event, queue 'exit ()' command. (main_window::save_workspace_callback, main_window::load_workspace_callback, main_window::clear_workspace_callback, main_window::rename_variable_callback, main_window::clear_command_window_callback, main_window::clear_history_callback, main_window::change_directory_callback, main_window::debug_continue_callback, main_window::debug_continue_callback, main_window::debug_step_into_callback, main_window::debug_step_over_callback, main_window::debug_step_out_callback, main_window::debug_quit_callback, main_window::exit_callback): Remove. (main_window::construct_octave_qt_link): Connect signal queue_background_octave_command to slot handle_queue_octave_command for main window and _octave_qt_link and file editor window and _octave_qt_link. Connect _octave_qt_link have_octave_command_result signal to file editor equivalent. * main_window.h (main_window : public QMainWindow): Remove all member functions listed above for main_window.cc. Add queue_background_octave_command member. * octave-qt-link.cc (octave_qt_link::octave_qt_link): Register meta type octave_value_list, connect transmit_octave_command_result to receive_octave_command_result, connect finished_with_command_result to handle_finished_with_command_result. (octave_qt_link::do_process_events): New. Virtual override. Lock mutex, get command, unlock mutex, evaluate command with eval_string (), lock mutex, signal across thread, wait, unlock mutex. (octave_qt_link::handle_queue_octave_command): New. Lock mutex, place command info in queue, unlock mutex. (octave_qt_link::receive_octave_command_result): New. Emit signal to GUI object connections, signal finished with data. (octave_qt_link::handle_finished_with_command_result): New. Try locking mutex. If fail after 5 seconds, indicate connection lost. If success, unlock mutex and wake Octave worker thread. * octave-qt-link.h: Add and "oct-obj.h" headers. (octave_qt_link : public QObject, public octave_link): Add command status enumeration. Add command_info structure. Add QList to hold command_info. New do_process_events () member. Add background_queue_mutex, events_process_mutex, and events_process_waitcondition Qt members. New signals and slots as described for octave-qt-link.cc above. * octave-link.h (octave_link): Change do_process_events to virtual. diff -r e192525236ad -r 524c50060fb8 libgui/src/m-editor/file-editor-tab.cc --- a/libgui/src/m-editor/file-editor-tab.cc Tue Apr 16 21:34:39 2013 +0200 +++ b/libgui/src/m-editor/file-editor-tab.cc Thu Apr 18 02:36:08 2013 -0500 @@ -47,14 +47,16 @@ #include "file-editor-tab.h" #include "file-editor.h" -#include "octave-link.h" +#include "octave-qt-link.h" +#include "ov-struct.h" #include "debug.h" #include "oct-env.h" // Make parent null for the file editor tab so that warning // WindowModal messages don't affect grandparents. -file_editor_tab::file_editor_tab (QString directory) +file_editor_tab::file_editor_tab (file_editor *p, QString directory) + : QWidget (p) { // Make sure there is a slash at the end of the directory name // for identification when saved later. @@ -121,6 +123,14 @@ QSettings *settings = resource_manager::get_settings (); if (settings) notice_settings (settings); + + // For issuing debugger-related commands to the Octave core. + connect (this, + SIGNAL (queue_background_octave_command (const QString&, + const int)), + p, + SIGNAL (queue_background_octave_command (const QString&, + const int))); } file_editor_tab::~file_editor_tab () @@ -350,13 +360,6 @@ delete printer; } - -void -file_editor_tab::run_file_callback (void) -{ - // Maybe someday we will do something here? -} - void file_editor_tab::run_file (const QWidget* ID) { @@ -374,10 +377,6 @@ function_name.chop (file_info.suffix ().length () + 1); emit process_octave_code (QString ("cd \'%1\'\n%2\n") .arg(path).arg (function_name)); - - // TODO: Sending a run event crashes for long scripts. Find out why. - // octave_link::post_event - // (this, &file_editor_tab::run_file_callback, _file_name.toStdString ())); } void @@ -432,44 +431,6 @@ } void -file_editor_tab::add_breakpoint_callback (const bp_info& info) -{ - bp_table::intmap intmap; - intmap[0] = info.line + 1; - - std::string previous_directory = octave_env::get_current_directory (); - octave_env::chdir (info.path); - intmap = bp_table::add_breakpoint (info.function_name, intmap); - octave_env::chdir (previous_directory); - // bp_table::add_breakpoint also sets the marker in the editor -} - -void -file_editor_tab::remove_breakpoint_callback (const bp_info& info) -{ - bp_table::intmap intmap; - intmap[0] = info.line + 1; - - std::string previous_directory = octave_env::get_current_directory (); - octave_env::chdir (info.path); - bp_table::remove_breakpoint (info.function_name, intmap); - octave_env::chdir (previous_directory); -} - -void -file_editor_tab::remove_all_breakpoints_callback (const bp_info& info) -{ - bp_table::intmap intmap; - std::string previous_directory = octave_env::get_current_directory (); - octave_env::chdir (info.path); - intmap = bp_table::remove_all_breakpoints_in_file (info.function_name, true); - octave_env::chdir (previous_directory); - - if (intmap.size() > 0) - _edit_area->markerDeleteAll (breakpoint); -} - -void file_editor_tab::request_add_breakpoint (int line) { QFileInfo file_info (_file_name); @@ -479,10 +440,12 @@ // We have to cut off the suffix, because octave appends it. function_name.chop (file_info.suffix ().length () + 1); - bp_info info (path, function_name, line); + QString command; + command = "dbstop (\"" + function_name + "\", "; + command += QString ().setNum (line) + ");"; - octave_link::post_event - (this, &file_editor_tab::add_breakpoint_callback, info); + // There is only one output argument for dbstop. + emit queue_background_octave_command (command, 1); } void @@ -495,10 +458,12 @@ // We have to cut off the suffix, because octave appends it. function_name.chop (file_info.suffix ().length () + 1); - bp_info info (path, function_name, line); + QString command; + command = "dbclear (\"" + function_name + "\", "; + command += QString ().setNum (line + 1) + ");"; - octave_link::post_event - (this, &file_editor_tab::remove_breakpoint_callback, info); + // There are no output arguments for dbclear. + emit queue_background_octave_command (command, 0); } void @@ -556,10 +521,48 @@ // We have to cut off the suffix, because octave appends it. function_name.chop (file_info.suffix ().length () + 1); - bp_info info (path, function_name, 0); + QString command; + command = "dbclear (\"" + function_name + "\");"; - octave_link::post_event - (this, &file_editor_tab::remove_all_breakpoints_callback, info); + // There are no output arguments for dbclear. + emit queue_background_octave_command (command, 0); +} + +void +file_editor_tab::add_all_breakpoints (const QObject *ID, const QString& command, + const int status, + const octave_value_list& output) +{ + + // FIXME: Would like to use ID to avoid running code if possible. + // However, because of the SIGNAL/SIGNAL connection the sender that + // is stored in the queue is not the sender of the original signal. + // Either signal directly to the octave_qt_link, or place the original + // sender as an argument variable to queue_background_octave_command. + // + // Really don't need the ID in this case because dbstatus contains + // file information. +/* + if (ID != this) + return; +*/ + + if (command.contains ("dbstatus") && status == octave_qt_link::CommandOK) + { + octave_value file = output (0).map_value ().contents ("file"); + octave_value line = output (0).map_value ().contents ("line"); + string_vector svfile = file.all_strings (); + for (octave_idx_type i = 0; i < svfile.length (); i++) + { + if (svfile[i] == _file_name.toStdString ()) + { + octave_value_list linelst = line.list_value (); + NDArray linenr = linelst (i).array_value (); + for (octave_idx_type j = 0; j < linenr.length (); j++) + do_breakpoint_marker (true, this, linenr (j) - 1); + } + } + } } void @@ -774,7 +777,7 @@ } QString -file_editor_tab::load_file(const QString& fileName) +file_editor_tab::load_file (const QString& fileName) { QFile file (fileName); if (!file.open (QFile::ReadOnly)) @@ -791,6 +794,9 @@ update_window_title (false); // window title (no modification) _edit_area->setModified (false); // loaded file is not modified yet + // Inquire from Octave core any breakpoints that are set. + emit queue_background_octave_command ("dbstatus ();", 1); + return QString (); } diff -r e192525236ad -r 524c50060fb8 libgui/src/m-editor/file-editor-tab.h --- a/libgui/src/m-editor/file-editor-tab.h Tue Apr 16 21:34:39 2013 +0200 +++ b/libgui/src/m-editor/file-editor-tab.h Thu Apr 18 02:36:08 2013 -0500 @@ -30,13 +30,18 @@ #include #include "find-dialog.h" +// FIXME -- we should not be including config.h in header files. +#ifdef HAVE_CONFIG_H +#include +#endif +#include "oct-obj.h" class file_editor; class file_editor_tab : public QWidget { Q_OBJECT public: - file_editor_tab (QString directory = ""); + file_editor_tab (file_editor *p, QString directory = ""); ~file_editor_tab (); public slots: @@ -73,6 +78,8 @@ void next_breakpoint (const QWidget* ID); void previous_breakpoint (const QWidget* ID); void remove_all_breakpoints (const QWidget* ID); + void add_all_breakpoints (const QObject *ID, const QString& command, + const int status, const octave_value_list& output); void comment_selected_text (const QWidget* ID); void uncomment_selected_text (const QWidget* ID); @@ -99,6 +106,7 @@ void mru_add_file (const QString& file_name); void editor_check_conflict_save (const QString& saveFileName, bool remove_on_success); void process_octave_code (const QString& command); + void queue_background_octave_command (const QString& command, const int nargout); protected: void closeEvent (QCloseEvent *event); @@ -120,17 +128,6 @@ private: - struct bp_info - { - bp_info (const QString& p, const QString& fn, int l) - : path (p.toStdString ()), function_name (fn.toStdString ()), line (l) - { } - - std::string path; - std::string function_name; - int line; - }; - void save_file (const QString& saveFileName, bool remove_on_success = false); void save_file_as (bool remove_on_success = false); void message_duplicate_file_name (const QString& fileName); @@ -142,10 +139,6 @@ int check_file_modified (); void do_comment_selected_text (bool comment); - void run_file_callback (void); - void add_breakpoint_callback (const bp_info& info); - void remove_breakpoint_callback (const bp_info& info); - void remove_all_breakpoints_callback (const bp_info& info); void center_current_line (); QsciScintilla * _edit_area; diff -r e192525236ad -r 524c50060fb8 libgui/src/m-editor/file-editor.cc --- a/libgui/src/m-editor/file-editor.cc Tue Apr 16 21:34:39 2013 +0200 +++ b/libgui/src/m-editor/file-editor.cc Thu Apr 18 02:36:08 2013 -0500 @@ -138,7 +138,7 @@ // editor tab has yet to be created and there is no object to // pass a signal to. Hence, functionality is here. - file_editor_tab *fileEditorTab = new file_editor_tab (ced); + file_editor_tab *fileEditorTab = new file_editor_tab (this, ced); if (fileEditorTab) { add_file_editor_tab (fileEditorTab, ""); // new tab with empty title @@ -217,7 +217,7 @@ } else { - file_editor_tab *fileEditorTab = new file_editor_tab (); + file_editor_tab *fileEditorTab = new file_editor_tab (this); if (fileEditorTab) { QString result = fileEditorTab->load_file(openFileName); @@ -989,6 +989,19 @@ connect (this, SIGNAL (fetab_do_breakpoint_marker (bool, const QWidget *, int)), f, SLOT (do_breakpoint_marker (bool, const QWidget *, int))); + // This can be done after constructing the file_editor_tab, which does a + // load file and queues a background dbstatus command. The following signal + // will not be emitted until the GUI thread gets around to handling the + // recieved command result, which happens after this function returns. The + // file management construction steps could be cleaned up a little. + connect (this, + SIGNAL (have_octave_command_result (const QObject *, const QString&, + const int, + const octave_value_list&)), + f, + SLOT (add_all_breakpoints (const QObject *, const QString&, + const int, const octave_value_list&))); + _tab_widget->setCurrentWidget (f); } diff -r e192525236ad -r 524c50060fb8 libgui/src/m-editor/file-editor.h --- a/libgui/src/m-editor/file-editor.h Tue Apr 16 21:34:39 2013 +0200 +++ b/libgui/src/m-editor/file-editor.h Thu Apr 18 02:36:08 2013 -0500 @@ -98,6 +98,9 @@ void fetab_delete_debugger_pointer (const QWidget* ID, int line = -1); void fetab_do_breakpoint_marker (bool insert, const QWidget* ID, int line = -1); void fetab_set_focus (const QWidget* ID); + void queue_background_octave_command (const QString& command, const int nargout); + void have_octave_command_result (const QObject *, const QString&, + const int, const octave_value_list&); public slots: void focus (void); diff -r e192525236ad -r 524c50060fb8 libgui/src/main-window.cc --- a/libgui/src/main-window.cc Tue Apr 16 21:34:39 2013 +0200 +++ b/libgui/src/main-window.cc Thu Apr 18 02:36:08 2013 -0500 @@ -131,12 +131,18 @@ void main_window::handle_save_workspace_request (void) { + // FIXME: Freezing the GUI waiting for user input should be avoided. QString selectedFile = QFileDialog::getSaveFileName (this, tr ("Save Workspace As"), resource_manager::get_home_path ()); + if (!selectedFile.isEmpty ()) - octave_link::post_event (this, &main_window::save_workspace_callback, - selectedFile.toStdString ()); + { + QString command = "save ('" + selectedFile + "');"; + + // There are no output arguments for save. + emit queue_background_octave_command (command, 0); + } } void @@ -144,18 +150,24 @@ { QString file = file_arg; + // FIXME: Freezing the GUI waiting for user input should be avoided. if (file.isEmpty ()) file = QFileDialog::getOpenFileName (this, tr ("Load Workspace"), resource_manager::get_home_path ()); if (! file.isEmpty ()) - octave_link::post_event (this, &main_window::load_workspace_callback, - file.toStdString ()); + { + QString command = "load ('" + file + "');"; + + // Loading whole workspace, no output arguments are needed. + emit queue_background_octave_command (command, 0); + } } void main_window::handle_clear_workspace_request (void) { - octave_link::post_event (this, &main_window::clear_workspace_callback); + // There are no output arguments for clear. + emit queue_background_octave_command ("clear ();", 0); } void @@ -165,20 +177,34 @@ { name_pair names (old_name.toStdString (), new_name.toStdString ()); - octave_link::post_event (this, &main_window::rename_variable_callback, - names); + // If it is desired to check that the first command passed before + // issuing the second command, a slot will be needed that implements + // a simple state machine. + + QString command = new_name + " = " + old_name + ";"; + + // No output arguments are needed. The way this could fail is the + // old name not existing in the workspace, in which case clearing + // that old name effectively does nothing. + emit queue_background_octave_command (command, 0); + + // There are no output arguments for clear. + emit queue_background_octave_command ("clear ('" + old_name + "');", 0); } void main_window::handle_clear_command_window_request (void) { - octave_link::post_event (this, &main_window::clear_command_window_callback); + // There are no output arguments for clc. + emit queue_background_octave_command ("clc ();", 0); + command_editor::interrupt (true); } void main_window::handle_clear_history_request (void) { - octave_link::post_event (this, &main_window::clear_history_callback); + // Just clearing all history, so no output arguments are needed. + emit queue_background_octave_command ("history ('-c');", 0); } void @@ -326,13 +352,18 @@ void main_window::browse_for_directory (void) { + // FIXME: Freezing the GUI waiting for user input should be avoided. QString dir = QFileDialog::getExistingDirectory (this, tr ("Set working directory")); if (! dir.isEmpty ()) - octave_link::post_event (this, - &main_window::change_directory_callback, - dir.toStdString ()); + { + QString command = "cd ('" + dir + "');"; + + // For now, no output arguments are needed and there is no need to + // check for success because the file search dialog is fairly robust. + emit queue_background_octave_command (command, 0); + } } void @@ -345,8 +376,13 @@ QFileInfo fileInfo (xdir); if (fileInfo.exists () && fileInfo.isDir ()) - octave_link::post_event (this, &main_window::change_directory_callback, - xdir.toStdString ()); + { + QString command = "cd ('" + xdir + "');"; + + // For now, no output arguments are needed and there is no need to + // check for success because the file search dialog is fairly robust. + emit queue_background_octave_command (command, 0); + } } void @@ -411,31 +447,41 @@ void main_window::debug_continue (void) { - octave_link::post_event (this, &main_window::debug_continue_callback); + // There are no output arguments for dbcont. + emit queue_background_octave_command ("dbcont ();", 0); + command_editor::interrupt (true); } void main_window::debug_step_into (void) { - octave_link::post_event (this, &main_window::debug_step_into_callback); + // There are no output arguments for dbstep. + emit queue_background_octave_command ("dbstep ('in');", 0); + command_editor::interrupt (true); } void main_window::debug_step_over (void) { - octave_link::post_event (this, &main_window::debug_step_over_callback); + // There are no output arguments for dbstep. + emit queue_background_octave_command ("dbstep ();", 0); + command_editor::interrupt (true); } void main_window::debug_step_out (void) { - octave_link::post_event (this, &main_window::debug_step_out_callback); + // There are no output arguments for dbstep. + emit queue_background_octave_command ("dbstep ('out');", 0); + command_editor::interrupt (true); } void main_window::debug_quit (void) { - octave_link::post_event (this, &main_window::debug_quit_callback); + // There are no output arguments for dbquit. + emit queue_background_octave_command ("dbquit ();", 0); + command_editor::interrupt (true); } void @@ -450,7 +496,8 @@ main_window::closeEvent (QCloseEvent *e) { e->ignore (); - octave_link::post_event (this, &main_window::exit_callback); + // There are no output arguments for exit. + emit queue_background_octave_command ("exit ();", 0); } void @@ -782,6 +829,25 @@ editor_window, SLOT (handle_delete_debugger_pointer_request (const QString&, int))); + connect (this, + SIGNAL (queue_background_octave_command (const QString&, const int)), + _octave_qt_link, + SLOT (handle_queue_octave_command (const QString&, const int))); + + connect (editor_window, + SIGNAL (queue_background_octave_command (const QString&, const int)), + _octave_qt_link, + SLOT (handle_queue_octave_command (const QString&, const int))); + + connect (_octave_qt_link, + SIGNAL (have_octave_command_result (const QObject *, const QString&, + const int, + const octave_value_list&)), + editor_window, + SIGNAL (have_octave_command_result (const QObject *, const QString&, + const int, + const octave_value_list&))); + connect (_workspace_model, SIGNAL (rename_variable (const QString&, const QString&)), this, @@ -1307,111 +1373,6 @@ } void -main_window::save_workspace_callback (const std::string& file) -{ - Fsave (ovl (file)); -} - -void -main_window::load_workspace_callback (const std::string& file) -{ - Fload (ovl (file)); - - octave_link::set_workspace (true, symbol_table::workspace_info ()); -} - -void -main_window::clear_workspace_callback (void) -{ - Fclear (); -} - -void -main_window::rename_variable_callback (const main_window::name_pair& names) -{ - /* bool status = */ symbol_table::rename (names.first, names.second); - - // if (status) - octave_link::set_workspace (true, symbol_table::workspace_info ()); - - // else - // ; // we need an octave_link action that runs a GUI error option. -} - -void -main_window::clear_command_window_callback (void) -{ - Fclc (); - command_editor::interrupt (true); -} - - -void -main_window::clear_history_callback (void) -{ - Fhistory (ovl ("-c")); -} - -void -main_window::change_directory_callback (const std::string& directory) -{ - Fcd (ovl (directory)); -} - -void -main_window::debug_continue_callback (void) -{ - Fdbcont (); - - command_editor::interrupt (true); -} - -// The next three callbacks are invoked by GUI buttons. Those buttons -// should only be active when we are doing debugging, which means that -// Octave is waiting for input in get_debug_input. Calling -// command_editor::interrupt will force readline to return even if it -// has not read any input, and then get_debug_input will return, -// allowing the evaluator to continue and execute the next statement. - -void -main_window::debug_step_into_callback (void) -{ - Fdbstep (ovl ("in")); - - command_editor::interrupt (true); -} - -void -main_window::debug_step_over_callback (void) -{ - Fdbstep (); - - command_editor::interrupt (true); -} - -void -main_window::debug_step_out_callback (void) -{ - Fdbstep (ovl ("out")); - - command_editor::interrupt (true); -} - -void -main_window::debug_quit_callback (void) -{ - Fdbquit (); - - command_editor::interrupt (true); -} - -void -main_window::exit_callback (void) -{ - Fquit (); -} - -void main_window::find_files(const QString &start_dir) { diff -r e192525236ad -r 524c50060fb8 libgui/src/main-window.h --- a/libgui/src/main-window.h Tue Apr 16 21:34:39 2013 +0200 +++ b/libgui/src/main-window.h Thu Apr 18 02:36:08 2013 -0500 @@ -79,6 +79,7 @@ void relay_command_signal (const QString&); void new_file_signal (const QString&); void open_file_signal (const QString&); + void queue_background_octave_command (const QString&, const int); public slots: void report_status_message (const QString& statusMessage); @@ -175,32 +176,6 @@ void establish_octave_link (void); - void save_workspace_callback (const std::string& file); - - void load_workspace_callback (const std::string& file); - - void rename_variable_callback (const name_pair& names); - - void clear_command_window_callback (void); - - void clear_workspace_callback (void); - - void clear_history_callback (void); - - void change_directory_callback (const std::string& directory); - - void debug_continue_callback (void); - - void debug_step_into_callback (void); - - void debug_step_over_callback (void); - - void debug_step_out_callback (void); - - void debug_quit_callback (void); - - void exit_callback (void); - // Data models. workspace_model *_workspace_model; diff -r e192525236ad -r 524c50060fb8 libgui/src/octave-qt-link.cc --- a/libgui/src/octave-qt-link.cc Tue Apr 16 21:34:39 2013 +0200 +++ b/libgui/src/octave-qt-link.cc Thu Apr 18 02:36:08 2013 -0500 @@ -29,6 +29,7 @@ #include #include "str-vec.h" +#include "parse.h" #include "dialog.h" #include "workspace-element.h" @@ -37,7 +38,32 @@ octave_qt_link::octave_qt_link (octave_main_thread *mt) : octave_link (), main_thread (mt) -{ } +{ + // Register octave_value_list so that command results may be signaled + // to objects in the GUI thread. + qRegisterMetaType ("octave_value_list"); + + // This is a cross-thread connection when this object exists in the + // GUI thread and transmit_octave_command_result is emitted from the + // worker (Octave core) thread. + connect (this, + SIGNAL (transmit_octave_command_result (const QObject *, + const QString&, + const int, + const octave_value_list&)), + this, + SLOT (receive_octave_command_result (const QObject *, + const QString&, + const int, + const octave_value_list&))); + + // This is a cross-thread confirmation that the GUI thread is finished + // with the data resulting from a background Octave command. + connect (this, + SIGNAL (finished_with_command_result (void)), + this, + SLOT (handle_finished_with_command_result (void))); +} octave_qt_link::~octave_qt_link (void) { } @@ -261,3 +287,101 @@ { emit delete_debugger_pointer_signal (QString::fromStdString (file), line); } + +void +octave_qt_link::do_process_events (void) +{ + while (! background_octave_command_queue.isEmpty ()) + { + background_queue_mutex.lock (); + command_info ci = background_octave_command_queue.takeFirst (); + background_queue_mutex.unlock (); + + // Evaluate this string without producing any output or advancing the + // line number shown at the terminal command line prompt. + octave_value_list retval; + int parse_status; + retval = eval_string (ci.command.toStdString (), false, + parse_status, ci.nargout); + + int command_status; + if (parse_status) + command_status = ParseFail; + else if (error_state) + command_status = ProcessFail; + else + command_status = CommandOK; + + // This lock is to ensure that this Octave worker thread becomes + // unlocked by the QWaitCondition a few lines further down before + // the GUI thread attempts to wake the process. Otherwise, there + // is the risk of this thread being awaken before going to sleep. + events_process_mutex.lock (); + + emit transmit_octave_command_result (ci.senderid, ci.command, + command_status, retval); + + events_process_waitcondition.wait (&events_process_mutex); + events_process_mutex.unlock (); + } +} + +void +octave_qt_link::handle_queue_octave_command (const QString& command, + const int nargout) +{ + background_queue_mutex.lock (); + command_info cominf = {sender (), command, nargout}; + background_octave_command_queue.append (cominf); + background_queue_mutex.unlock (); +} + +void +octave_qt_link::receive_octave_command_result (const QObject *ID, + const QString& command, + const int status, + const octave_value_list& output) +{ + // Passing octave_value_list through a thread is thread safe. Passing an + // octave_value through a thread is not because octave_value uses a + // reference count which could be mangled in the queued signal/slot copy and + // delete mechanism. (When a signal is emitted from one thread to an object + // in another thread, queued signals result, not direct signals. That means + // a copy-construct is done in one thread, while the destruct is done in + // another thread.) No type of operation on the octave_value of the output + // list should be done in this routine. However, because the worker thread + // is asleep, operations on octave_value types and its member derivatives + // is fine in DIRECT connections to the following signal because then + // the reference count increment and decrement are in the same thread and + // happen before the worker thread is awaken. (Direct connections are those + // for which the signal and slot object are in the same thread.) + + emit have_octave_command_result (ID, command, status, output); + + emit finished_with_command_result (); +} + +void +octave_qt_link::handle_finished_with_command_result (void) +{ + // Wake up Octave worker process so that it continues but make sure the + // process is asleep first just in case there is some rare condition in + // which the GUI thread reaches this point before the Octave worker thread + // goes into a sleep state. + if (! events_process_mutex.tryLock (5000)) + { + // Create a NonModal message about error. + QMessageBox* msgBox = new QMessageBox ( + QMessageBox::Critical, tr ("Octave Qt Link"), + "Connection Timeout: Lost link to worker process.", + QMessageBox::Ok, 0); + msgBox->setWindowModality (Qt::NonModal); + msgBox->setAttribute (Qt::WA_DeleteOnClose); + msgBox->show (); + } + else + { + events_process_mutex.unlock (); + events_process_waitcondition.wakeAll (); + } +} diff -r e192525236ad -r 524c50060fb8 libgui/src/octave-qt-link.h --- a/libgui/src/octave-qt-link.h Tue Apr 16 21:34:39 2013 +0200 +++ b/libgui/src/octave-qt-link.h Thu Apr 18 02:36:08 2013 -0500 @@ -31,9 +31,16 @@ #include #include #include +#include +#include #include "octave-link.h" #include "octave-main-thread.h" +// FIXME -- we should not be including config.h in header files. +#ifdef HAVE_CONFIG_H +#include +#endif +#include "oct-obj.h" // \class OctaveLink // \brief Provides threadsafe access to octave. @@ -49,6 +56,13 @@ public: + enum + { + CommandOK = 0, + ParseFail, + ProcessFail + }; + octave_qt_link (octave_main_thread *mt); ~octave_qt_link (void); @@ -113,6 +127,21 @@ // Thread running octave_main. octave_main_thread *main_thread; + // GUI events issue background Octave commands which are stored in a queue. + struct command_info + { + const QObject *senderid; + const QString command; + const int nargout; + }; + QList background_octave_command_queue; + + // Process GUI events in the background Octave command queue. + void do_process_events (void); + QMutex background_queue_mutex; + QMutex events_process_mutex; + QWaitCondition events_process_waitcondition; + signals: void exit_signal (int status); @@ -142,6 +171,28 @@ void insert_debugger_pointer_signal (const QString&, int); void delete_debugger_pointer_signal (const QString&, int); + + // Emit this signal from within the Octave worker thread. There should + // be only one slot connected to this signal, receive_octave_command_result. + void transmit_octave_command_result (const QObject *, const QString&, + const int, const octave_value_list&); + + // Emit this signal from within the GUI thread. Many QObject slots may + // connect to this signal, but the programming rule is that none of them + // should modify any contents of the output octave_value. ONLY retrieve + // results. + void have_octave_command_result (const QObject *, const QString&, + const int, const octave_value_list&); + + void finished_with_command_result (void); + +public slots: + void handle_queue_octave_command (const QString& command, const int nargout); + void receive_octave_command_result (const QObject *ID, + const QString& command, + const int status, + const octave_value_list& output); + void handle_finished_with_command_result (void); }; #endif diff -r e192525236ad -r 524c50060fb8 libinterp/interpfcn/octave-link.h --- a/libinterp/interpfcn/octave-link.h Tue Apr 16 21:34:39 2013 +0200 +++ b/libinterp/interpfcn/octave-link.h Thu Apr 18 02:36:08 2013 -0500 @@ -285,7 +285,7 @@ bool link_enabled; void do_generate_events (void); - void do_process_events (void); + virtual void do_process_events (void); void do_discard_events (void); template