# HG changeset patch # User Daniel J Sebald # Date 1411164787 18000 # Node ID bfedf9d2c44ac8dff8d5cd03de577cdb12275e9a # Parent 90dd85369c2e8b2db2b040854ec979bd7367c578 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::add_breakpoint_callback): Removed. (file_editor_tab::remove_breakpoint_callback): Removed. (file_editor_tab::remove_all_breakpoints_callback): Removed. (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): Use bp_info second argument of 1. Remove post_event, queue 'dbclear ()' command. (file_editor_tab::add_all_breakpoints): Added. Get octave_value for 'file' and 'line', convert 'line' to array for 'file' name match, and call do_breakpoint_marker() for each entry in line number array. (file_editor_tab::load_file): Emit 'dbstatus()' command to background queue. * file-editor-tab.h: (file_editor_tab::file_editor_tab): Add parent input argument. (file_editor_tab::add_all_breakpoints): Added. Declaration. (file_editor_tab::queue_background_octave_command): Added. New signal. (file_editor_tab::add_breakpoint_callback): Removed. (file_editor_tab::remove_breakpoint_callback): Removed. (file_editor_tab::remove_all_breakpoints_callback): Removed. * file-editor.cc (file_editor::request_new_file, file_editor::request_open_file): Add 'this' to calls to new file_editor_tab(). (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): Added. New signals. * main-window.cc (main_window::main_window): Remove initialization of _cmd_queue, _cmd_processing, _cmd_queue_mutex, _dbg_queue, _dbg_processing, and _dbg_queue_mutex. (main_window::~main_window): Remove deleting _cmd_queue. (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::execute_command_in_terminal): Emit queue_octave_command() rather than call member function queue_command(). (main_window::run_file_in_terminal): Remove post_event, move code from run_file_callback() and queue_command() to here utilizing queue_octave_command() signal. (main_window::run_file_callback): Removed. (main_window::queue_command): Moved to octave-qt-link.cc. (main_window::handle_new_figure_request): Remove post_event, queue 'figure ()' command. (main_window::browse_for_directory): Add comment about modal dialogs. (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 queue_debug() call, queue 'dbcont ()' command. (main_window::debug_quit): Remove queue_debug() call, queue 'dbquit ()' command. (main_window::closeEvent): Remove post_event, queue 'exit ()' command. (main_window::construct_octave_qt_link): Connect queue_octave_command() and queue_background_octave_command() signals to corresponding octave_qt_link slots. Connect editor_window queue_background_octave_command signals to corresponding octave_qt_link slots. Connect _octave_qt_link have_octave_command_result() signal to editor_window have_octave_command_result() slot. (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::execute_command_callback, main_window::new_figure_callback, main_window::change_directory_callback, main_window::queue_debug, main_window::execute_debug_callback, main_window::exit_callback): Removed. * main-window.h (main_window : public QMainWindow): Remove QQueue.h and QSemaphore.h from list of includes. Remove all member functions listed above for main-window.cc. Declare queue_octave_command() and queue_octave_background_command() signals. Removed _cmd_queue, _cmd_processing, _cmd_queue_mutex, _dbg_queue, _dbg_processing, and _dbg_queue declarations. * octave-qt-link.cc: Add parse.h, cmd-edit.h and oct-mutex.h to list of included files. (octave_qt_link::octave_qt_link): Initialize _cmd_processing object. 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): Added. Virtual override. Lock (foreground) event queue, execute event, unlock event queue. Lock background queue mutex, get background command, unlock background mutex, evaluate command with eval_string (), redraw, lock mutex, signal across thread, wait, unlock mutex. (octave_qt_link::handle_queue_background_octave_command): Added. Lock background queue mutex, place command info in queue, unlock mutex. (octave_qt_link::receive_octave_command_result): Added. Emit signal to GUI object connections, signal finished with data. (octave_qt_link::handle_finished_with_command_result): Added. Try locking mutex. If fail after 5 seconds, indicate connection lost. If success, unlock mutex and wake Octave worker thread. (octave_qt_link::execute_command_callback): Moved here from main-window.cc. (octave_qt_link::handle_queue_octave_command: Moved here from main-window.cc where it was named queue_command(). * octave-qt-link.h: Add QStringList.h, QMutex.h, QWaitCondition.h, QSemaphore.h, and "oct-obj.h" header list. (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. Add _cmd_queue, _cmd_process, and _cmd_queue_mutex member objects. Declare execute_command_callback(). New signals and slots declarations as described for octave-qt-link.cc above. * octave-link.h (octave_link): Change do_process_events to virtual. diff --git a/libgui/src/m-editor/file-editor-tab.cc b/libgui/src/m-editor/file-editor-tab.cc --- a/libgui/src/m-editor/file-editor-tab.cc +++ b/libgui/src/m-editor/file-editor-tab.cc @@ -57,6 +57,7 @@ #include "debug.h" #include "octave-qt-link.h" +#include "ov-struct.h" #include "version.h" #include "utils.h" #include "defaults.h" @@ -64,7 +65,8 @@ // Make parent null for the file editor tab so that warning // WindowModal messages don't affect grandparents. -file_editor_tab::file_editor_tab (const QString& directory_arg) +file_editor_tab::file_editor_tab (file_editor *p, const QString directory_arg) + : QWidget (p) { QString directory = directory_arg; _lexer_apis = 0; @@ -169,6 +171,22 @@ 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))); + connect (this, + SIGNAL (queue_background_octave_command (const QString&, + const int, + const QObject*)), + p, + SIGNAL (queue_background_octave_command (const QString&, + const int, + const QObject*))); } file_editor_tab::~file_editor_tab (void) @@ -756,33 +774,6 @@ _edit_area->markerDeleteAll (bookmark); } -void -file_editor_tab::add_breakpoint_callback (const bp_info& info) -{ - bp_table::intmap line_info; - line_info[0] = info.line; - - if (octave_qt_link::file_in_path (info.file, info.dir)) - bp_table::add_breakpoint (info.function_name, line_info); -} - -void -file_editor_tab::remove_breakpoint_callback (const bp_info& info) -{ - bp_table::intmap line_info; - line_info[0] = info.line; - - if (octave_qt_link::file_in_path (info.file, info.dir)) - bp_table::remove_breakpoint (info.function_name, line_info); -} - -void -file_editor_tab::remove_all_breakpoints_callback (const bp_info& info) -{ - if (octave_qt_link::file_in_path (info.file, info.dir)) - bp_table::remove_all_breakpoints_in_file (info.function_name, true); -} - file_editor_tab::bp_info::bp_info (const QString& fname, int l) : line (l), file (fname.toStdString ()) { @@ -818,8 +809,12 @@ { bp_info info (_file_name, line+1); - octave_link::post_event - (this, &file_editor_tab::add_breakpoint_callback, info); + QString command; + command = "dbstop (\"" + QString(info.function_name.c_str()) + "\", "; + command += QString ().setNum (info.line) + ");"; + + // There is only one output argument for dbstop. + emit queue_background_octave_command (command, 1); } void @@ -827,8 +822,12 @@ { bp_info info (_file_name, line+1); - octave_link::post_event - (this, &file_editor_tab::remove_breakpoint_callback, info); + QString command; + command = "dbclear (\"" + QString(info.function_name.c_str()) + "\", "; + command += QString ().setNum (line + 1) + ");"; + + // There are no output arguments for dbclear. + emit queue_background_octave_command (command, 0); } void @@ -886,10 +885,40 @@ if (ID != this) return; - bp_info info (_file_name); + bp_info info (_file_name, 0+1); - octave_link::post_event - (this, &file_editor_tab::remove_all_breakpoints_callback, info); + QString command; + command = "dbclear (\"" + QString(info.function_name.c_str()) + "\");"; + + // 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) +{ + // Only act if the command originated from this tab. + 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 (same_file (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)); + } + } + } } void @@ -1309,6 +1338,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, this); + return QString (); } diff --git a/libgui/src/m-editor/file-editor-tab.h b/libgui/src/m-editor/file-editor-tab.h --- a/libgui/src/m-editor/file-editor-tab.h +++ b/libgui/src/m-editor/file-editor-tab.h @@ -44,7 +44,7 @@ public: - file_editor_tab (const QString& directory = ""); + file_editor_tab (file_editor *p, QString directory = ""); ~file_editor_tab (void); @@ -88,6 +88,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 scintilla_command (const QWidget *, unsigned int); @@ -136,6 +138,11 @@ void execute_command_in_terminal_signal (const QString&); void set_global_edit_shortcuts_signal (bool); void create_context_menu_tab_signal (QMenu *); + void queue_background_octave_command (const QString& command, + const int nargout, + const QObject* ID); + void queue_background_octave_command (const QString& command, + const int nargout); protected: @@ -203,9 +210,6 @@ QString comment_string (const QString&); void do_indent_selected_text (bool indent); - 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 (); void add_octave_apis (octave_value_list key_ovl); diff --git a/libgui/src/m-editor/file-editor.cc b/libgui/src/m-editor/file-editor.cc --- a/libgui/src/m-editor/file-editor.cc +++ b/libgui/src/m-editor/file-editor.cc @@ -148,7 +148,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 @@ -356,7 +356,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); @@ -1587,6 +1587,22 @@ int)), f, SLOT (do_breakpoint_marker (bool, const QWidget*, int))); + // The following connection can be done sometime after constructing the + // file_editor_tab, which loads a file and queues a background dbstatus + // command, so long as there is not a return to the GUI thread core in + // between. That being the case, even if that dbstatus command has + // completed by now, 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. Note that 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); check_actions (); diff --git a/libgui/src/m-editor/file-editor.h b/libgui/src/m-editor/file-editor.h --- a/libgui/src/m-editor/file-editor.h +++ b/libgui/src/m-editor/file-editor.h @@ -114,6 +114,11 @@ void execute_command_in_terminal_signal (const QString&); void file_loaded_signal (); + void queue_background_octave_command (const QString& command, const int nargout, const QObject* 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 --git a/libgui/src/main-window.cc b/libgui/src/main-window.cc --- a/libgui/src/main-window.cc +++ b/libgui/src/main-window.cc @@ -90,12 +90,6 @@ community_news_window (0), _octave_qt_link (0), _clipboard (QApplication::clipboard ()), - _cmd_queue (new QStringList ()), // no command pending - _cmd_processing (1), - _cmd_queue_mutex (), - _dbg_queue (new QStringList ()), // no debug pending - _dbg_processing (1), - _dbg_queue_mutex (), _prevent_readline_conflicts (true) { QSettings *settings = resource_manager::get_settings (); @@ -155,7 +149,6 @@ community_news_window = 0; } delete _octave_qt_link; - delete _cmd_queue; } bool @@ -191,13 +184,18 @@ void main_window::handle_save_workspace_request (void) { + // FIXME: Freezing the GUI waiting for user input should be avoided. QString file = QFileDialog::getSaveFileName (this, tr ("Save Workspace As"), ".", 0, 0, QFileDialog::DontUseNativeDialog); if (! file.isEmpty ()) - octave_link::post_event (this, &main_window::save_workspace_callback, - file.toStdString ()); + { + QString command = "save ('" + file + "');"; + + // There are no output arguments for save. + emit queue_background_octave_command (command, 0); + } } void @@ -205,19 +203,25 @@ { 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"), ".", 0, 0, QFileDialog::DontUseNativeDialog); 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 @@ -227,8 +231,18 @@ { 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 that + // the old name doesn't exist. + emit queue_background_octave_command (command, 0); + + // There are no output arguments for clear. + emit queue_background_octave_command ("clear ('" + old_name + "');", 0); } void @@ -240,13 +254,15 @@ 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); } 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); } bool @@ -259,7 +275,7 @@ void main_window::execute_command_in_terminal (const QString& command) { - queue_command (command); + emit queue_octave_command (command); if (focus_console_after_command ()) focus_command_window (); } @@ -290,37 +306,20 @@ return; } - octave_link::post_event (this, &main_window::run_file_callback, info); - if (focus_console_after_command ()) - focus_command_window (); -} - -void -main_window::run_file_callback (const QFileInfo& info) -{ QString dir = info.absolutePath (); - QString function_name = info.fileName (); - function_name.chop (info.suffix ().length () + 1); if (octave_qt_link::file_in_path (info.absoluteFilePath ().toStdString (), dir.toStdString ())) - queue_command (function_name); -} - -void -main_window::queue_command (QString command) -{ - _cmd_queue_mutex.lock (); - _cmd_queue->append (command); // queue command - _cmd_queue_mutex.unlock (); - - if (_cmd_processing.tryAcquire ()) // if callback not processing, post event - octave_link::post_event (this, &main_window::execute_command_callback); + { + emit queue_octave_command (function_name); + if (focus_console_after_command ()) + focus_command_window (); + } } void main_window::handle_new_figure_request (void) { - octave_link::post_event (this, &main_window::new_figure_callback); + emit queue_background_octave_command ("figure ();", 0); } void @@ -748,6 +747,7 @@ void main_window::browse_for_directory (void) { + // FIXME: Freezing the GUI waiting for user input should be avoided. QString dir = QFileDialog::getExistingDirectory (this, tr ("Browse directories"), 0, QFileDialog::ShowDirsOnly | @@ -771,8 +771,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 @@ -835,31 +840,46 @@ void main_window::debug_continue (void) { - queue_debug ("cont"); + // There are no output arguments for dbcont. + emit queue_background_octave_command ("dbcont ();", 0); + // Force debug result to appear. + emit queue_octave_command (""); } void main_window::debug_step_into (void) { - queue_debug ("in"); + // There are no output arguments for dbstep. + emit queue_background_octave_command ("dbstep ('in');", 0); + // Force debug result to appear. + emit queue_octave_command (""); } void main_window::debug_step_over (void) { - queue_debug ("step"); + // There are no output arguments for dbstep. + emit queue_background_octave_command ("dbstep ();\n", 0); + // Force debug result to appear. + emit queue_octave_command (""); } void main_window::debug_step_out (void) { - queue_debug ("out"); + // There are no output arguments for dbstep. + emit queue_background_octave_command ("dbstep ('out');", 0); + // Force debug result to appear. + emit queue_octave_command (""); } void main_window::debug_quit (void) { - queue_debug ("quit"); + // There are no output arguments for dbquit. + emit queue_background_octave_command ("dbquit ();", 0); + // Force debug result to appear. + emit queue_octave_command (""); } void @@ -914,7 +934,8 @@ { e->ignore (); if (confirm_exit_octave()) - octave_link::post_event (this, &main_window::exit_callback); + // There are no output arguments for exit. + emit queue_background_octave_command ("exit ();", 0); } void @@ -1339,7 +1360,6 @@ #endif focus_command_window (); // make sure that the command window has focus - } @@ -1421,6 +1441,41 @@ SIGNAL (show_doc_signal (const QString &)), this, SLOT (handle_show_doc (const QString &))); + connect (this, + SIGNAL (queue_octave_command (const QString&)), + _octave_qt_link, + SLOT (handle_queue_octave_command (const QString&))); + + connect (this, + SIGNAL (queue_background_octave_command (const QString&, const int)), + _octave_qt_link, + SLOT (handle_queue_background_octave_command (const QString&, + const int))); + + connect (editor_window, + SIGNAL (queue_background_octave_command (const QString&, const int)), + _octave_qt_link, + SLOT (handle_queue_background_octave_command (const QString&, + const int))); + + connect (editor_window, + SIGNAL (queue_background_octave_command (const QString&, + const int, + const QObject*)), + _octave_qt_link, + SLOT (handle_queue_background_octave_command (const QString&, + const int, + const QObject*))); + + 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, @@ -2050,38 +2105,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::command_window_undo_callback (void) { command_editor::undo (); @@ -2089,125 +2112,12 @@ } void -main_window::clear_command_window_callback (void) -{ - Fclc (); - command_editor::interrupt (true); -} - -void main_window::resize_command_window_callback (void) { command_editor::resize_terminal (); } void -main_window::clear_history_callback (void) -{ - Fhistory (ovl ("-c")); -} - -void -main_window::execute_command_callback () -{ - bool repost = false; // flag for reposting event for this callback - - if (!_cmd_queue->isEmpty ()) // list can not be empty here, just to make sure - { - std::string pending_input = command_editor::get_current_line (); - command_editor::set_initial_input (pending_input); - - _cmd_queue_mutex.lock (); // critical path - std::string command = _cmd_queue->takeFirst ().toStdString (); - if (_cmd_queue->isEmpty ()) - _cmd_processing.release (); // cmd queue empty, processing will stop - else - repost = true; // not empty, repost at end - _cmd_queue_mutex.unlock (); - - command_editor::replace_line (command); - - command_editor::redisplay (); - // We are executing inside the command editor event loop. Force - // the current line to be returned for processing. - command_editor::accept_line (); - } - - if (repost) // queue not empty, so repost event for further processing - octave_link::post_event (this, &main_window::execute_command_callback); - -} - -void -main_window::new_figure_callback (void) -{ - Fbuiltin (ovl ("figure")); - Fdrawnow (); -} - -void -main_window::change_directory_callback (const std::string& directory) -{ - Fcd (ovl (directory)); -} - -// The next 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::queue_debug (QString debug_cmd) -{ - _dbg_queue_mutex.lock (); - _dbg_queue->append (debug_cmd); // queue command - _dbg_queue_mutex.unlock (); - - if (_dbg_processing.tryAcquire ()) // if callback not processing, post event - octave_link::post_event (this, &main_window::execute_debug_callback); -} - -void -main_window::execute_debug_callback () -{ - bool repost = false; // flag for reposting event for this callback - - if (!_dbg_queue->isEmpty ()) // list can not be empty here, just to make sure - { - _dbg_queue_mutex.lock (); // critical path - QString debug = _dbg_queue->takeFirst (); - if (_dbg_queue->isEmpty ()) - _dbg_processing.release (); // cmd queue empty, processing will stop - else - repost = true; // not empty, repost at end - _dbg_queue_mutex.unlock (); - - if (debug == "step") - Fdbstep (); - else if (debug == "cont") - Fdbcont (); - else if (debug == "quit") - Fdbquit (); - else - Fdbstep (ovl (debug.toStdString ())); - - command_editor::interrupt (true); - } - - if (repost) // queue not empty, so repost event for further processing - octave_link::post_event (this, &main_window::execute_debug_callback); - -} - -void -main_window::exit_callback (void) -{ - Fquit (); -} - -void main_window::find_files (const QString &start_dir) { diff --git a/libgui/src/main-window.h b/libgui/src/main-window.h --- a/libgui/src/main-window.h +++ b/libgui/src/main-window.h @@ -31,12 +31,10 @@ #include #include #include -#include #include #include #include #include -#include // Editor includes #include "file-editor-interface.h" @@ -82,6 +80,8 @@ void settings_changed (const QSettings *); void new_file_signal (const QString&); void open_file_signal (const QString&); + void queue_octave_command (const QString&); + void queue_background_octave_command (const QString&, const int); void show_doc_signal (const QString&); @@ -240,38 +240,12 @@ 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 command_window_undo_callback (void); - void clear_command_window_callback (void); - void resize_command_window_callback (void); - void clear_workspace_callback (void); - - void clear_history_callback (void); - - void execute_command_callback (); - void run_file_callback (const QFileInfo& info); bool focus_console_after_command (); - void new_figure_callback (void); - - void change_directory_callback (const std::string& directory); - - void exit_callback (void); - - void queue_command (QString command); - - void queue_debug (QString command); - - void execute_debug_callback (); - void configure_shortcuts (); bool confirm_exit_octave (); @@ -387,16 +361,6 @@ // Flag for closing whole application. bool _closing; - // semaphore to synchronize execution signals and related callback - QStringList *_cmd_queue; - QSemaphore _cmd_processing; - QMutex _cmd_queue_mutex; - - // semaphore to synchronize debug signals and related callbacks - QStringList *_dbg_queue; - QSemaphore _dbg_processing; - QMutex _dbg_queue_mutex; - bool _prevent_readline_conflicts; }; diff --git a/libgui/src/octave-qt-link.cc b/libgui/src/octave-qt-link.cc --- a/libgui/src/octave-qt-link.cc +++ b/libgui/src/octave-qt-link.cc @@ -38,14 +38,17 @@ #include "load-path.h" #include "oct-env.h" #include "utils.h" +#include "parse.h" +#include "cmd-edit.h" +#include "oct-mutex.h" #include "octave-qt-link.h" #include "resource-manager.h" octave_qt_link::octave_qt_link (QWidget *p) : octave_link (), main_thread (new QThread ()), - command_interpreter (new octave_interpreter ()) + command_interpreter (new octave_interpreter ()), _cmd_processing (1) { connect (this, SIGNAL (execute_interpreter_signal (void)), command_interpreter, SLOT (execute (void))); @@ -56,6 +59,31 @@ command_interpreter->moveToThread (main_thread); main_thread->start (); + + // 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) { } @@ -474,6 +502,156 @@ emit delete_debugger_pointer_signal (QString::fromStdString (file), line); } +void +octave_qt_link::do_process_events (void) +{ + // This processes commands in the foreground similar to the overridden + // code in octave_link::do_process_events. + event_queue_mutex->lock (); + gui_event_queue.run (); + event_queue_mutex->unlock (); + + // This processes commands in the background and returns results. + 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); + Fdrawnow (); + + 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_background_octave_command (const QString& command, + const int nargout, + const QObject* ID) +{ + background_queue_mutex.lock (); + command_info cominf = {ID, 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 (); + } +} + +// FIXME: Callback can likely be avoided if handle_queue_octave_command is +// placed in the worker thread. See background queue setup. +void +octave_qt_link::execute_command_callback () +{ + bool repost = false; // flag for reposting event for this callback + + if (!_cmd_queue.isEmpty ()) // list can not be empty here, just to make sure + { + std::string pending_input = command_editor::get_current_line (); + command_editor::set_initial_input (pending_input); + + _cmd_queue_mutex.lock (); // critical path + std::string command = _cmd_queue.takeFirst ().toStdString (); + if (_cmd_queue.isEmpty ()) + _cmd_processing.release (); // cmd queue empty, processing will stop + else + repost = true; // not empty, repost at end + _cmd_queue_mutex.unlock (); + + command_editor::replace_line (command); + + command_editor::redisplay (); + // We are executing inside the command editor event loop. Force + // the current line to be returned for processing. + command_editor::accept_line (); + } + + if (repost) // queue not empty, so repost event for further processing + octave_link::post_event (this, &octave_qt_link::execute_command_callback); + +} + +void +octave_qt_link::handle_queue_octave_command (const QString& command) +{ + _cmd_queue_mutex.lock (); + _cmd_queue.append (command); // queue command + _cmd_queue_mutex.unlock (); + + if (_cmd_processing.tryAcquire ()) // if callback not processing, post event + octave_link::post_event (this, &octave_qt_link::execute_command_callback); +} bool octave_qt_link::file_in_path (const std::string& file, const std::string& dir) diff --git a/libgui/src/octave-qt-link.h b/libgui/src/octave-qt-link.h --- a/libgui/src/octave-qt-link.h +++ b/libgui/src/octave-qt-link.h @@ -29,13 +29,19 @@ #include #include +#include #include #include #include +#include +#include +#include #include "octave-link.h" #include "octave-interpreter.h" +#include "oct-obj.h" + // Defined for purposes of sending QList as part of signal. typedef QList QIntList; @@ -53,6 +59,13 @@ public: + enum + { + CommandOK = 0, + ParseFail, + ProcessFail + }; + octave_qt_link (QWidget *p); ~octave_qt_link (void); @@ -146,6 +159,30 @@ octave_interpreter *command_interpreter; + // 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; + + // Background Octave command queue mutex access control. + void do_process_events (void); + QMutex background_queue_mutex; + QMutex events_process_mutex; + QWaitCondition events_process_waitcondition; + + // GUE events issue foreground Octave commands which are stored in a queue. + QStringList _cmd_queue; + QSemaphore _cmd_processing; + + // Foreground Octave command queue mutex access control. + QMutex _cmd_queue_mutex; + + void execute_command_callback (void); + signals: void execute_interpreter_signal (void); @@ -185,9 +222,35 @@ void show_doc_signal (const QString &file); + // 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 terminal_interrupt (void); + + void handle_queue_octave_command (const QString& command); + void handle_queue_background_octave_command (const QString& command, const int nargout, const QObject* ID); + void handle_queue_background_octave_command (const QString& command, const int nargout) + { + handle_queue_background_octave_command (command, nargout, sender ()); + }; + 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 --git a/libinterp/corefcn/octave-link.h b/libinterp/corefcn/octave-link.h --- a/libinterp/corefcn/octave-link.h +++ b/libinterp/corefcn/octave-link.h @@ -356,7 +356,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 exporting patch: