1 //////////////////////////////////////////////////////////////////////// 2 // 3 // Copyright (C) 2011-2021 The Octave Project Developers 4 // 5 // See the file COPYRIGHT.md in the top-level directory of this 6 // distribution or <https://octave.org/copyright/>. 7 // 8 // This file is part of Octave. 9 // 10 // Octave is free software: you can redistribute it and/or modify it 11 // under the terms of the GNU General Public License as published by 12 // the Free Software Foundation, either version 3 of the License, or 13 // (at your option) any later version. 14 // 15 // Octave is distributed in the hope that it will be useful, but 16 // WITHOUT ANY WARRANTY; without even the implied warranty of 17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 // GNU General Public License for more details. 19 // 20 // You should have received a copy of the GNU General Public License 21 // along with Octave; see the file COPYING. If not, see 22 // <https://www.gnu.org/licenses/>. 23 // 24 //////////////////////////////////////////////////////////////////////// 25 26 #if ! defined (octave_event_manager_h) 27 #define octave_event_manager_h 1 28 29 #include "octave-config.h" 30 31 #include <functional> 32 #include <list> 33 #include <memory> 34 #include <string> 35 36 #include "oct-mutex.h" 37 #include "octave.h" 38 #include "event-queue.h" 39 #include "uint8NDArray.h" 40 41 class octave_value; 42 class string_vector; 43 44 namespace octave 45 { 46 typedef std::function<void (void)> fcn_callback; 47 typedef std::function<void (octave::interpreter&)> meth_callback; 48 49 class symbol_info_list; 50 51 // The methods in this class provide a way to pass signals to the GUI 52 // thread. A GUI that wishes to act on these events should derive 53 // from this class and perform actions in a thread-safe way. In 54 // Octave's Qt-based GUI, for example, these functions are all 55 // implemented as wrappers around Qt signals that trigger actions in 56 // the GUI. The Qt signal/slot mechanism ensures that the actions are 57 // properly queued for execution when the objects corresponding to the 58 // signal and slot belong to different threads. 59 // 60 // These functions should not be called directly. Instead all 61 // requests from the interpreter for GUI actions should be done 62 // through the event_manager class. That class checks to ensure that 63 // the GUI is connected and enabled before calling these virtual 64 // functions. 65 66 // FIXME: it would be nice if instead of requiring the GUI to derive 67 // from this class, it could subscribe to individual events, possibly 68 // multiple times. In that way, it would be more flexible and 69 // decentralized, similar to the Qt signal/slot connection mechanism 70 // and would allow the GUI to connect multiple signals to a single 71 // action or multiple actions to a single signal. 72 73 // FIXME: audit this list of functions and determine whether they are 74 // all necessary and whether there might be better names for them. 75 76 class interpreter_events 77 { 78 public: 79 80 interpreter_events (void) = default; 81 82 interpreter_events (const interpreter_events&) = default; 83 84 interpreter_events& operator = (const interpreter_events&) = default; 85 86 virtual ~interpreter_events (void) = default; 87 88 // Dialogs. 89 90 typedef std::list<std::pair<std::string, std::string>> filter_list; 91 92 virtual std::list<std::string> file_dialog(const filter_list &,const std::string &,const std::string &,const std::string &,const std::string &)93 file_dialog (const filter_list& /*filter*/, 94 const std::string& /*title*/, 95 const std::string& /*filename*/, 96 const std::string& /*dirname*/, 97 const std::string& /*multimode*/) 98 { 99 return std::list<std::string> (); 100 } 101 102 virtual std::list<std::string> input_dialog(const std::list<std::string> &,const std::string &,const std::list<float> &,const std::list<float> &,const std::list<std::string> &)103 input_dialog (const std::list<std::string>& /*prompt*/, 104 const std::string& /*title*/, 105 const std::list<float>& /*nr*/, 106 const std::list<float>& /*nc*/, 107 const std::list<std::string>& /*defaults*/) 108 { 109 return std::list<std::string> (); 110 } 111 112 virtual std::pair<std::list<int>, int> list_dialog(const std::list<std::string> &,const std::string &,int,int,const std::list<int> &,const std::string &,const std::list<std::string> &,const std::string &,const std::string &)113 list_dialog (const std::list<std::string>& /*list*/, 114 const std::string& /*mode*/, int /*width*/, int /*height*/, 115 const std::list<int>& /*initial_value*/, 116 const std::string& /*name*/, 117 const std::list<std::string>& /*prompt*/, 118 const std::string& /*ok_string*/, 119 const std::string& /*cancel_string*/) 120 { 121 return std::pair<std::list<int>, int> (); 122 } 123 124 virtual std::string question_dialog(const std::string &,const std::string &,const std::string &,const std::string &,const std::string &,const std::string &)125 question_dialog (const std::string& /*msg*/, const std::string& /*title*/, 126 const std::string& /*btn1*/, const std::string& /*btn2*/, 127 const std::string& /*btn3*/, const std::string& /*btndef*/) 128 { 129 return ""; 130 } 131 update_path_dialog(void)132 virtual void update_path_dialog (void) { } 133 show_preferences(void)134 virtual void show_preferences (void) { } 135 apply_preferences(void)136 virtual void apply_preferences (void) { } 137 show_doc(const std::string &)138 virtual void show_doc (const std::string& /*file*/) { } 139 edit_file(const std::string &)140 virtual bool edit_file (const std::string& /*file*/) { return false; } 141 142 virtual void edit_variable(const std::string &,const octave_value &)143 edit_variable (const std::string& /*name*/, const octave_value& /*val*/) 144 { } 145 146 // Other requests for user interaction, usually some kind of 147 // confirmation before another action. Could these be reformulated 148 // using the question_dialog action? 149 confirm_shutdown(void)150 virtual bool confirm_shutdown (void) { return false; } 151 prompt_new_edit_file(const std::string &)152 virtual bool prompt_new_edit_file (const std::string& /*file*/) 153 { 154 return false; 155 } 156 157 virtual int debug_cd_or_addpath_error(const std::string &,const std::string &,bool)158 debug_cd_or_addpath_error (const std::string& /*file*/, 159 const std::string& /*dir*/, 160 bool /*addpath_option*/) 161 { 162 return -1; 163 } 164 165 // Requests for information normally stored in the GUI. 166 get_named_icon(const std::string &)167 virtual uint8NDArray get_named_icon (const std::string& /*icon_name*/) 168 { 169 return uint8NDArray (); 170 } 171 gui_preference(const std::string &,const std::string &)172 virtual std::string gui_preference (const std::string& /*key*/, 173 const std::string& /*value*/) 174 { 175 return ""; 176 } 177 178 // Requests for GUI action that do not require user interaction. 179 // These are different from other notifications in that they are not 180 // associated with changes in the interpreter state (like a change 181 // in the current working directory or command history). 182 copy_image_to_clipboard(const std::string &)183 virtual bool copy_image_to_clipboard (const std::string& /*file*/) 184 { 185 return false; 186 } 187 focus_window(const std::string)188 virtual void focus_window (const std::string /*win_name*/) 189 { } 190 191 virtual void execute_command_in_terminal(const std::string &)192 execute_command_in_terminal (const std::string& /*command*/) { } 193 register_doc(const std::string &)194 virtual void register_doc (const std::string& /*file*/) { } 195 unregister_doc(const std::string &)196 virtual void unregister_doc (const std::string& /*file*/) { } 197 198 // Notifications of events in the interpreter that a GUI will 199 // normally wish to respond to. 200 directory_changed(const std::string &)201 virtual void directory_changed (const std::string& /*dir*/) { } 202 203 virtual void file_remove(const std::string &,const std::string &)204 file_remove (const std::string& /*old_nm*/, const std::string& /*new_nm*/) 205 { } 206 file_renamed(bool)207 virtual void file_renamed (bool) { } 208 209 virtual void set_workspace(bool,bool,const octave::symbol_info_list &,bool)210 set_workspace (bool /*top_level*/, bool /*debug*/, 211 const octave::symbol_info_list& /*syminfo*/, 212 bool /*update_variable_editor*/) 213 { } 214 clear_workspace(void)215 virtual void clear_workspace (void) { } 216 set_history(const string_vector &)217 virtual void set_history (const string_vector& /*hist*/) { } 218 append_history(const std::string &)219 virtual void append_history (const std::string& /*hist_entry*/) { } 220 clear_history(void)221 virtual void clear_history (void) { } 222 pre_input_event(void)223 virtual void pre_input_event (void) { } 224 post_input_event(void)225 virtual void post_input_event (void) { } 226 227 virtual void enter_debugger_event(const std::string &,const std::string &,int)228 enter_debugger_event (const std::string& /*fcn_name*/, 229 const std::string& /*fcn_file_name*/, 230 int /*line*/) 231 { } 232 233 virtual void execute_in_debugger_event(const std::string &,int)234 execute_in_debugger_event (const std::string& /*file*/, int /*line*/) { } 235 exit_debugger_event(void)236 virtual void exit_debugger_event (void) { } 237 238 virtual void update_breakpoint(bool,const std::string &,int,const std::string &)239 update_breakpoint (bool /*insert*/, const std::string& /*file*/, 240 int /*line*/, const std::string& /*cond*/) 241 { } 242 }; 243 244 //! Provides threadsafe access to octave. 245 //! 246 //! This class provides thread-safe communication between the 247 //! interpreter and a GUI. 248 249 class event_manager 250 { 251 public: 252 253 event_manager (interpreter& interp); 254 255 // No copying! 256 257 event_manager (const event_manager&) = delete; 258 259 event_manager& 260 operator = (const event_manager&) = delete; 261 262 virtual ~event_manager (void); 263 264 // OBJ should be an object of a class that is derived from the base 265 // class interpreter_events, or nullptr to disconnect and delete the 266 // previous link. 267 268 void connect_link (const std::shared_ptr<interpreter_events>& obj); 269 270 bool enable (void); 271 disable(void)272 bool disable (void) 273 { 274 bool retval = link_enabled; 275 link_enabled = false; 276 return retval; 277 } 278 enabled(void)279 bool enabled (void) const 280 { 281 return link_enabled; 282 } 283 284 // If disable is TRUE, then no additional events will be processed 285 // other than exit. 286 287 void process_events (bool disable = false); 288 289 void discard_events (void); 290 291 // The post_event and post_exception functions provide a thread-safe 292 // way for the GUI to queue interpreter functions for execution. 293 // The queued functions are executed when the interpreter is 294 // otherwise idle. 295 post_event(const fcn_callback & fcn)296 void post_event (const fcn_callback& fcn) 297 { 298 if (enabled ()) 299 gui_event_queue.add (fcn); 300 } 301 post_event(const meth_callback & meth)302 void post_event (const meth_callback& meth) 303 { 304 if (enabled ()) 305 gui_event_queue.add (std::bind (meth, std::ref (m_interpreter))); 306 } 307 308 // The following functions correspond to the virtual fuunctions in 309 // the interpreter_events class. They provide a way for the 310 // interpreter to notify the GUI that some event has occurred 311 // (directory or workspace changed, for example) or to request the 312 // GUI to perform some action (display a dialog, for example). 313 314 // Please keep this list of declarations in the same order as the 315 // ones above in the interpreter_events class. 316 317 typedef std::list<std::pair<std::string, std::string>> filter_list; 318 319 std::list<std::string> file_dialog(const filter_list & filter,const std::string & title,const std::string & filename,const std::string & dirname,const std::string & multimode)320 file_dialog (const filter_list& filter, const std::string& title, 321 const std::string& filename, const std::string& dirname, 322 const std::string& multimode) 323 { 324 return (enabled () 325 ? instance->file_dialog (filter, title, filename, dirname, 326 multimode) 327 : std::list<std::string> ()); 328 } 329 330 std::list<std::string> input_dialog(const std::list<std::string> & prompt,const std::string & title,const std::list<float> & nr,const std::list<float> & nc,const std::list<std::string> & defaults)331 input_dialog (const std::list<std::string>& prompt, 332 const std::string& title, 333 const std::list<float>& nr, 334 const std::list<float>& nc, 335 const std::list<std::string>& defaults) 336 { 337 return (enabled () 338 ? instance->input_dialog (prompt, title, nr, nc, defaults) 339 : std::list<std::string> ()); 340 } 341 342 std::pair<std::list<int>, int> list_dialog(const std::list<std::string> & list,const std::string & mode,int width,int height,const std::list<int> & initial_value,const std::string & name,const std::list<std::string> & prompt,const std::string & ok_string,const std::string & cancel_string)343 list_dialog (const std::list<std::string>& list, 344 const std::string& mode, 345 int width, int height, 346 const std::list<int>& initial_value, 347 const std::string& name, 348 const std::list<std::string>& prompt, 349 const std::string& ok_string, 350 const std::string& cancel_string) 351 { 352 return (enabled () 353 ? instance->list_dialog (list, mode, width, height, 354 initial_value, name, prompt, 355 ok_string, cancel_string) 356 : std::pair<std::list<int>, int> ()); 357 } 358 359 std::string question_dialog(const std::string & msg,const std::string & title,const std::string & btn1,const std::string & btn2,const std::string & btn3,const std::string & btndef)360 question_dialog (const std::string& msg, const std::string& title, 361 const std::string& btn1, const std::string& btn2, 362 const std::string& btn3, const std::string& btndef) 363 { 364 return (enabled () 365 ? instance->question_dialog (msg, title, btn1, 366 btn2, btn3, btndef) 367 : ""); 368 } 369 update_path_dialog(void)370 void update_path_dialog (void) 371 { 372 if (octave::application::is_gui_running () && enabled ()) 373 instance->update_path_dialog (); 374 } 375 show_preferences(void)376 bool show_preferences (void) 377 { 378 if (enabled ()) 379 { 380 instance->show_preferences (); 381 return true; 382 } 383 else 384 return false; 385 } 386 apply_preferences(void)387 bool apply_preferences (void) 388 { 389 if (enabled ()) 390 { 391 instance->apply_preferences (); 392 return true; 393 } 394 else 395 return false; 396 } 397 show_doc(const std::string & file)398 bool show_doc (const std::string& file) 399 { 400 if (enabled ()) 401 { 402 instance->show_doc (file); 403 return true; 404 } 405 else 406 return false; 407 } 408 edit_file(const std::string & file)409 bool edit_file (const std::string& file) 410 { 411 return enabled () ? instance->edit_file (file) : false; 412 } 413 edit_variable(const std::string & name,const octave_value & val)414 bool edit_variable (const std::string& name, const octave_value& val) 415 { 416 if (enabled ()) 417 { 418 instance->edit_variable (name, val); 419 return true; 420 } 421 else 422 return false; 423 } 424 confirm_shutdown(void)425 bool confirm_shutdown (void) 426 { 427 bool retval = true; 428 429 if (enabled ()) 430 retval = instance->confirm_shutdown (); 431 432 return retval; 433 } 434 prompt_new_edit_file(const std::string & file)435 bool prompt_new_edit_file (const std::string& file) 436 { 437 return enabled () ? instance->prompt_new_edit_file (file) : false; 438 } 439 debug_cd_or_addpath_error(const std::string & file,const std::string & dir,bool addpath_option)440 int debug_cd_or_addpath_error (const std::string& file, 441 const std::string& dir, bool addpath_option) 442 { 443 return (enabled () 444 ? instance->debug_cd_or_addpath_error (file, dir, addpath_option) 445 : 0); 446 } 447 get_named_icon(const std::string & icon_name)448 uint8NDArray get_named_icon (const std::string& icon_name) 449 { 450 return (enabled () 451 ? instance->get_named_icon (icon_name) : uint8NDArray ()); 452 } 453 gui_preference(const std::string & key,const std::string & value)454 std::string gui_preference (const std::string& key, 455 const std::string& value) 456 { 457 return enabled () ? instance->gui_preference (key, value) : ""; 458 } 459 copy_image_to_clipboard(const std::string & file)460 bool copy_image_to_clipboard (const std::string& file) 461 { 462 return enabled () ? instance->copy_image_to_clipboard (file) : false; 463 } 464 focus_window(const std::string win_name)465 virtual void focus_window (const std::string win_name) 466 { 467 if (enabled ()) 468 instance->focus_window (win_name); 469 } 470 471 // Preserves pending input. execute_command_in_terminal(const std::string & command)472 void execute_command_in_terminal (const std::string& command) 473 { 474 if (enabled ()) 475 instance->execute_command_in_terminal (command); 476 } 477 register_doc(const std::string & file)478 bool register_doc (const std::string& file) 479 { 480 if (enabled ()) 481 { 482 instance->register_doc (file); 483 return true; 484 } 485 else 486 return false; 487 } 488 unregister_doc(const std::string & file)489 bool unregister_doc (const std::string& file) 490 { 491 if (enabled ()) 492 { 493 instance->unregister_doc (file); 494 return true; 495 } 496 else 497 return false; 498 499 } 500 directory_changed(const std::string & dir)501 void directory_changed (const std::string& dir) 502 { 503 if (enabled ()) 504 instance->directory_changed (dir); 505 } 506 507 // Methods for removing/renaming files which might be open in editor file_remove(const std::string & old_name,const std::string & new_name)508 void file_remove (const std::string& old_name, const std::string& new_name) 509 { 510 if (octave::application::is_gui_running () && enabled ()) 511 instance->file_remove (old_name, new_name); 512 } 513 file_renamed(bool load_new)514 void file_renamed (bool load_new) 515 { 516 if (octave::application::is_gui_running () && enabled ()) 517 instance->file_renamed (load_new); 518 } 519 520 void set_workspace (void); 521 522 void set_workspace (bool top_level, const octave::symbol_info_list& syminfo, 523 bool update_variable_editor = true) 524 { 525 if (enabled ()) 526 instance->set_workspace (top_level, debugging, syminfo, 527 update_variable_editor); 528 } 529 clear_workspace(void)530 void clear_workspace (void) 531 { 532 if (enabled ()) 533 instance->clear_workspace (); 534 } 535 set_history(const string_vector & hist)536 void set_history (const string_vector& hist) 537 { 538 if (enabled ()) 539 instance->set_history (hist); 540 } 541 append_history(const std::string & hist_entry)542 void append_history (const std::string& hist_entry) 543 { 544 if (enabled ()) 545 instance->append_history (hist_entry); 546 } 547 clear_history(void)548 void clear_history (void) 549 { 550 if (enabled ()) 551 instance->clear_history (); 552 } 553 pre_input_event(void)554 void pre_input_event (void) 555 { 556 if (enabled ()) 557 instance->pre_input_event (); 558 } 559 post_input_event(void)560 void post_input_event (void) 561 { 562 if (enabled ()) 563 instance->post_input_event (); 564 } 565 enter_debugger_event(const std::string & fcn_name,const std::string & fcn_file_name,int line)566 void enter_debugger_event (const std::string& fcn_name, 567 const std::string& fcn_file_name, int line) 568 { 569 if (enabled ()) 570 { 571 debugging = true; 572 573 instance->enter_debugger_event (fcn_name, fcn_file_name, line); 574 } 575 } 576 execute_in_debugger_event(const std::string & file,int line)577 void execute_in_debugger_event (const std::string& file, int line) 578 { 579 if (enabled ()) 580 instance->execute_in_debugger_event (file, line); 581 } 582 exit_debugger_event(void)583 void exit_debugger_event (void) 584 { 585 if (enabled () && debugging) 586 { 587 debugging = false; 588 589 instance->exit_debugger_event (); 590 } 591 } 592 593 void update_breakpoint (bool insert, const std::string& file, 594 int line, const std::string& cond = "") 595 { 596 if (enabled ()) 597 instance->update_breakpoint (insert, file, line, cond); 598 } 599 600 private: 601 602 interpreter& m_interpreter; 603 604 // Using a shared_ptr to manage the link_events object ensures that it 605 // will be valid until it is no longer needed. 606 607 std::shared_ptr<interpreter_events> instance; 608 609 protected: 610 611 // Semaphore to lock access to the event queue. 612 octave::mutex *event_queue_mutex; 613 614 // Event Queue. 615 octave::event_queue gui_event_queue; 616 617 bool debugging; 618 bool link_enabled; 619 }; 620 } 621 622 #endif 623