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