GNU Octave 10.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
 
Loading...
Searching...
No Matches
gh-manager.cc
Go to the documentation of this file.
1////////////////////////////////////////////////////////////////////////
2//
3// Copyright (C) 2007-2025 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 (HAVE_CONFIG_H)
27# include "config.h"
28#endif
29
30#include "cmd-edit.h"
31
32#include "builtin-defun-decls.h"
33#include "gh-manager.h"
34#include "graphics-utils.h"
35#include "input.h"
36#include "interpreter-private.h"
37#include "interpreter.h"
38
40
41static double
42make_handle_fraction ()
43{
44 static double maxrand = RAND_MAX + 2.0;
45
46 return (rand () + 1.0) / maxrand;
47}
48
50gh_manager::get_handle (bool integer_figure_handle)
51{
52 graphics_handle retval;
53
54 if (integer_figure_handle)
55 {
56 // Figure handles are positive integers corresponding
57 // to the figure number.
58
59 // We always want the lowest unused figure number.
60
61 retval = 1;
62
63 while (m_handle_map.find (retval) != m_handle_map.end ())
64 retval++;
65 }
66 else
67 {
68 // Other graphics handles are negative integers plus some random
69 // fractional part. To avoid running out of integers, we recycle the
70 // integer part but tack on a new random part each time.
71
72 auto p = m_handle_free_list.begin ();
73
74 if (p != m_handle_free_list.end ())
75 {
76 retval = *p;
77 m_handle_free_list.erase (p);
78 }
79 else
80 {
81 retval = graphics_handle (m_next_handle);
82
83 m_next_handle = std::ceil (m_next_handle) - 1.0 - make_handle_fraction ();
84 }
85 }
86
87 return retval;
88}
89
90void
91gh_manager::free (const graphics_handle& h, bool from_root)
92{
93 if (h.ok ())
94 {
95 if (h.value () == 0)
96 error ("graphics_handle::free: can't delete root object");
97
98 auto p = m_handle_map.find (h);
99
100 if (p == m_handle_map.end ())
101 error ("graphics_handle::free: invalid object %g", h.value ());
102
103 base_properties& bp = p->second.get_properties ();
104
105 if (! p->second.valid_object () || bp.is_beingdeleted ())
106 return;
107
108 graphics_handle parent_h = p->second.get_parent ();
109 graphics_object parent_go = nullptr;
110 if (! from_root || isfigure (h.value ()))
111 parent_go = get_object (parent_h);
112
113 bp.set_beingdeleted (true);
114
115 // delete listeners before invalidating object
116 p->second.remove_all_listeners ();
117
118 bp.delete_children (true, from_root);
119
120 // NOTE: Call the delete function while the object's state is still valid.
121 octave_value val = bp.get_deletefcn ();
122
123 bp.execute_deletefcn ();
124
125 // Notify graphics toolkit.
126 p->second.finalize ();
127
128
129 // NOTE: Call remove_child before erasing the go from the map if not
130 // removing from groot.
131 // A callback function might have already deleted the parent
132 if ((! from_root || isfigure (h.value ())) && parent_go.valid_object ()
133 && h.ok ())
134 parent_go.remove_child (h);
135
136 // Note: this will be valid only for first explicitly deleted
137 // object. All its children will then have an
138 // unknown graphics toolkit.
139
140 // Graphics handles for non-figure objects are negative
141 // integers plus some random fractional part. To avoid
142 // running out of integers, we recycle the integer part
143 // but tack on a new random part each time.
144
145 m_handle_map.erase (p);
146
147 if (h.value () < 0)
148 m_handle_free_list.insert
149 (std::ceil (h.value ()) - make_handle_fraction ());
150 }
151}
152
153void
155 const graphics_handle& new_gh)
156{
157 auto p = m_handle_map.find (old_gh);
158
159 if (p == m_handle_map.end ())
160 error ("graphics_handle::free: invalid object %g", old_gh.value ());
161
162 graphics_object go = p->second;
163
164 m_handle_map.erase (p);
165
166 m_handle_map[new_gh] = go;
167
168 if (old_gh.value () < 0)
169 m_handle_free_list.insert (std::ceil (old_gh.value ())
170 - make_handle_fraction ());
171
172 for (auto& hfig : m_figure_list)
173 {
174 if (hfig == old_gh)
175 {
176 hfig = new_gh;
177 break;
178 }
179 }
180}
181
182void
184{
185 // FIXME: should we process or discard pending events?
186
187 m_event_queue.clear ();
188
189 // Don't use m_figure_list_iterator because we'll be removing elements
190 // from the list elsewhere.
191
192 Matrix hlist = figure_handle_list (true);
193
194 for (octave_idx_type i = 0; i < hlist.numel (); i++)
195 {
196 graphics_handle h = lookup (hlist(i));
197
198 if (h.ok ())
199 close_figure (h);
200 }
201
202 // They should all be closed now. If not, force them to close.
203
204 hlist = figure_handle_list (true);
205
206 for (octave_idx_type i = 0; i < hlist.numel (); i++)
207 {
208 graphics_handle h = lookup (hlist(i));
209
210 if (h.ok ())
212 }
213
214 // None left now, right?
215
216 hlist = figure_handle_list (true);
217
218 if (hlist.numel () != 0)
219 warning ("gh_manager::close_all_figures: some graphics elements failed to close");
220
221 // Clear all callback objects from our list.
222
223 m_callback_objects.clear ();
224}
225
226// We use a random value for the handle to avoid issues with plots and
227// scalar values for the first argument.
228gh_manager::gh_manager (octave::interpreter& interp)
229 : m_interpreter (interp), m_handle_map (), m_handle_free_list (),
230 m_next_handle (-1.0 - (rand () + 1.0) / (RAND_MAX + 2.0)),
231 m_figure_list (), m_graphics_lock (), m_event_queue (),
232 m_callback_objects (), m_event_processing (0)
233{
234 m_handle_map[0] = graphics_object (new root_figure ());
235
236 octave::gtk_manager& gtk_mgr = octave::__get_gtk_manager__ ();
237
238 // Make sure the default graphics toolkit is registered.
239 gtk_mgr.default_toolkit ();
240}
241
243gh_manager::make_graphics_handle (const std::string& go_name,
244 const graphics_handle& p,
245 bool integer_figure_handle,
246 bool call_createfcn, bool notify_toolkit)
247{
248 graphics_handle h = get_handle (integer_figure_handle);
249
250 base_graphics_object *bgo = make_graphics_object_from_type (go_name, h, p);
251
252 if (! bgo)
253 error ("gh_manager::make_graphics_handle: invalid object type '%s'",
254 go_name.c_str ());
255
256 graphics_object go (bgo);
257
258 m_handle_map[h] = go;
259
260 if (go_name == "axes")
261 {
262 // Handle defaults for labels since overriding defaults for
263 // them can't work before the axes object is fully
264 // constructed.
265
266 axes::properties& props
267 = dynamic_cast<axes::properties&> (go.get_properties ());
268
269 graphics_object tgo;
270
271 tgo = get_object (props.get_xlabel ());
272 tgo.override_defaults ();
273
274 tgo = get_object (props.get_ylabel ());
275 tgo.override_defaults ();
276
277 tgo = get_object (props.get_zlabel ());
278 tgo.override_defaults ();
279
280 tgo = get_object (props.get_title ());
281 tgo.override_defaults ();
282 }
283
284 // Overriding defaults will work now because the handle is valid
285 // and we can find parent objects (not just handles).
286 go.override_defaults ();
287
288 if (call_createfcn)
289 bgo->get_properties ().execute_createfcn ();
290
291 // Notify graphics toolkit.
292 if (notify_toolkit)
293 go.initialize ();
294
295 return h;
296}
297
299gh_manager::make_figure_handle (double val, bool notify_toolkit)
300{
301 graphics_handle h = val;
302
303 base_graphics_object *bgo = new figure (h, 0);
304 graphics_object go (bgo);
305
306 m_handle_map[h] = go;
307
308 // Notify graphics toolkit.
309 if (notify_toolkit)
310 go.initialize ();
311
312 go.override_defaults ();
313
314 return h;
315}
316
317void
319{
320 pop_figure (h);
321
322 m_figure_list.push_front (h);
323}
324
325void
327{
328 for (auto it = m_figure_list.begin (); it != m_figure_list.end (); it++)
329 {
330 if (*it == h)
331 {
332 m_figure_list.erase (it);
333 break;
334 }
335 }
336}
337
338static void
339xset_gcbo (const graphics_handle& h)
340{
341 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
342
343 graphics_object go = gh_mgr.get_object (0);
344
345 root_figure::properties& props
346 = dynamic_cast<root_figure::properties&> (go.get_properties ());
347
348 props.set_callbackobject (h.as_octave_value ());
349}
350
351void
353{
354 octave::autolock guard (m_graphics_lock);
355
356 m_callback_objects.pop_front ();
357
358 xset_gcbo (m_callback_objects.empty ()
359 ? graphics_handle () : m_callback_objects.front ().get_handle ());
360}
361
362void
364{
365 if (octave::thread::is_thread ())
367 else
368 {
369 octave::autolock guard (m_graphics_lock);
370
371 post_event (graphics_event::create_callback_event (h, l));
372 }
373}
374
375void
377 const octave_value& cb_arg,
378 const octave_value& data)
379{
380 if (cb_arg.is_defined () && ! cb_arg.isempty ())
381 {
383 octave_value ov_fcn;
384 octave_function *fcn = nullptr;
385
386 args(0) = h.as_octave_value ();
387 if (data.is_defined ())
388 args(1) = data;
389 else
390 args(1) = Matrix ();
391
392 octave::unwind_action_safe restore_gcbo_action
394
395 graphics_object go (get_object (h));
396 if (go)
397 {
398 // FIXME: Is the lock necessary when we're only calling a
399 // const "get" method?
400 octave::autolock guard (m_graphics_lock);
401 m_callback_objects.push_front (go);
402 xset_gcbo (h);
403 }
404
405 // Copy CB because "function_value" method is non-const.
406 octave_value cb = cb_arg;
407
408 if (cb.is_function ())
409 fcn = cb.function_value ();
410 else if (cb.is_function_handle ())
411 ov_fcn = cb;
412 else if (cb.is_string ())
413 {
414 int status;
415 std::string s = cb.string_value ();
416
417 try
418 {
419 m_interpreter.eval_string (s, false, status, 0);
420 }
421 catch (const octave::execution_exception& ee)
422 {
423 m_interpreter.handle_exception (ee);
424 }
425 }
426 else if (cb.iscell () && cb.length () > 0
427 && (cb.rows () == 1 || cb.columns () == 1)
428 && (cb.cell_value ()(0).is_function ()
429 || cb.cell_value ()(0).is_function_handle ()))
430 {
431 Cell c = cb.cell_value ();
432
433 ov_fcn = c(0);
434
435 for (int i = 1; i < c.numel () ; i++)
436 args(1+i) = c(i);
437 }
438 else
439 {
440 std::string nm = cb.class_name ();
441 error ("trying to execute non-executable object (class = %s)",
442 nm.c_str ());
443 }
444
445 if (fcn || ov_fcn.is_defined ())
446 try
447 {
448 if (ov_fcn.is_defined ())
449 m_interpreter.feval (ov_fcn, args);
450 else
451 m_interpreter.feval (fcn, args);
452 }
453 catch (const octave::execution_exception& ee)
454 {
455 m_interpreter.handle_exception (ee);
456 }
457
458 // Redraw after interacting with a user-interface (ui*) object.
460 {
461 if (go)
462 {
463 std::string go_name
464 = go.get_properties ().graphics_object_name ();
465
466 if (go_name.length () > 1
467 && go_name[0] == 'u' && go_name[1] == 'i')
468 {
469 Fdrawnow (m_interpreter);
470 Vdrawnow_requested = false;
471 }
472 }
473 }
474 }
475}
476
477static int
478process_graphics_events ()
479{
480 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
481
482 return gh_mgr.process_events ();
483}
484
485void
486gh_manager::post_event (const graphics_event& e)
487{
488 m_event_queue.push_back (e);
489
490 octave::command_editor::add_event_hook (process_graphics_events);
491}
492
493void
494gh_manager::post_callback (const graphics_handle& h, const std::string& name,
495 const octave_value& data)
496{
497 octave::autolock guard (m_graphics_lock);
498
499 graphics_object go = get_object (h);
500
501 if (go.valid_object ())
502 {
503 caseless_str cname (name);
504 int busyaction = base_graphics_event::QUEUE;
505
506 if (cname == "deletefcn" || cname == "createfcn"
507 || cname == "closerequestfcn"
508 || ((go.isa ("figure") || go.isa ("uipanel")
509 || go.isa ("uibuttongroup"))
510 && (cname == "resizefcn" || cname == "sizechangedfcn")))
511 busyaction = base_graphics_event::INTERRUPT;
512 else if (go.get_properties ().get_busyaction () == "cancel")
513 busyaction = base_graphics_event::CANCEL;
514
515 // The "closerequestfcn" callback must be executed once the figure has
516 // been made current. Let "close" do the job.
517 if (cname == "closerequestfcn")
518 {
519 std::string cmd ("close (gcbf ());");
520 post_event (graphics_event::create_mcode_event (h, cmd, busyaction));
521 }
522 else
523 post_event (graphics_event::create_callback_event (h, name, data,
524 busyaction));
525 }
526}
527
528void
529gh_manager::post_function (graphics_event::event_fcn fcn, void *fcn_data)
530{
531 octave::autolock guard (m_graphics_lock);
532
533 post_event (graphics_event::create_function_event (fcn, fcn_data));
534}
535
536void
537gh_manager::post_set (const graphics_handle& h, const std::string& name,
538 const octave_value& value, bool notify_toolkit,
539 bool redraw_figure)
540{
541 octave::autolock guard (m_graphics_lock);
542
543 post_event (graphics_event::create_set_event (h, name, value, notify_toolkit,
544 redraw_figure));
545}
546
547int
549{
550 graphics_event e;
551 bool old_Vdrawnow_requested = Vdrawnow_requested;
552 bool events_executed = false;
553
554 do
555 {
556 e = graphics_event ();
557
558 {
559 octave::autolock guard (m_graphics_lock);
560
561 if (! m_event_queue.empty ())
562 {
563 if (m_callback_objects.empty () || force)
564 {
565 e = m_event_queue.front ();
566
567 m_event_queue.pop_front ();
568 }
569 else
570 {
571 const graphics_object& go = m_callback_objects.front ();
572
573 if (go.get_properties ().is_interruptible ())
574 {
575 e = m_event_queue.front ();
576
577 m_event_queue.pop_front ();
578 }
579 else
580 {
581 std::list<graphics_event>::iterator p = m_event_queue.begin ();
582
583 while (p != m_event_queue.end ())
584 if (p->get_busyaction () == base_graphics_event::CANCEL)
585 {
586 p = m_event_queue.erase (p);
587 }
588 else if (p->get_busyaction ()
589 == base_graphics_event::INTERRUPT)
590 {
591 e = (*p);
592 m_event_queue.erase (p);
593 break;
594 }
595 else
596 p++;
597 }
598 }
599 }
600 }
601
602 if (e.ok ())
603 {
604 e.execute ();
605 events_executed = true;
606 }
607 }
608 while (e.ok ());
609
610 {
611 octave::autolock guard (m_graphics_lock);
612
613 if (m_event_queue.empty () && m_event_processing == 0)
614 octave::command_editor::remove_event_hook (process_graphics_events);
615 }
616
617 if (events_executed)
618 octave::flush_stdout ();
619
620 if (Vdrawnow_requested && ! old_Vdrawnow_requested)
621 {
622 Fdrawnow (m_interpreter);
623
624 Vdrawnow_requested = false;
625 }
626
627 return 0;
628}
629
630
631/*
632## Test interruptible/busyaction properties
633%!function cb (h, ~)
634%! setappdata (gcbf (), "cb_exec", [getappdata(gcbf (), "cb_exec") h]);
635%! drawnow ();
636%! setappdata (gcbf (), "cb_exec", [getappdata(gcbf (), "cb_exec") h]);
637%!endfunction
638%!
639%!testif HAVE_OPENGL, HAVE_QT; have_window_system () && any (strcmp ("qt", available_graphics_toolkits ()))
640%! hf = figure ("visible", "off", "resizefcn", @cb);
641%! graphics_toolkit (hf, "qt");
642%! unwind_protect
643%! ## Default
644%! hui1 = uicontrol ("parent", hf, "interruptible", "on", "callback", @cb);
645%! hui2 = uicontrol ("parent", hf, "busyaction", "queue", "callback", @cb);
646%! hui3 = uicontrol ("parent", hf, "busyaction", "queue", "callback", @cb);
647%! __go_post_callback__ (hui1, "callback");
648%! __go_post_callback__ (hui2, "callback");
649%! __go_post_callback__ (hui3, "callback");
650%!
651%! assert (getappdata (hf, "cb_exec"), []);
652%! drawnow ();
653%! assert (getappdata (hf, "cb_exec"), [hui1 hui2 hui3 hui3 hui2 hui1]);
654%!
655%! ## Interruptible off
656%! setappdata (hf, "cb_exec", []);
657%! set (hui1, "interruptible", "off");
658%! __go_post_callback__ (hui1, "callback");
659%! __go_post_callback__ (hui2, "callback");
660%! __go_post_callback__ (hui3, "callback");
661%! drawnow ();
662%! assert (getappdata (hf, "cb_exec"), [hui1 hui1 hui2 hui3 hui3 hui2]);
663%!
664%! ## "resizefcn" callback interrupts regardless of interruptible property
665%! setappdata (hf, "cb_exec", []);
666%! __go_post_callback__ (hui1, "callback");
667%! __go_post_callback__ (hf, "resizefcn");
668%! drawnow ();
669%! assert (getappdata (hf, "cb_exec"), [hui1 hf hf hui1]);
670%!
671%! ## test "busyaction" "cancel"
672%! setappdata (hf, "cb_exec", []);
673%! set (hui2, "busyaction", "cancel");
674%! __go_post_callback__ (hui1, "callback");
675%! __go_post_callback__ (hui2, "callback");
676%! __go_post_callback__ (hui3, "callback");
677%! __go_post_callback__ (hf, "resizefcn");
678%! drawnow ();
679%! assert (getappdata (hf, "cb_exec"), [hui1 hf hui3 hui3 hf hui1]);
680%! unwind_protect_cleanup
681%! close (hf)
682%! end_unwind_protect
683*/
684
685void
687{
688 octave::autolock guard (m_graphics_lock);
689
690 if (enable)
691 {
692 m_event_processing++;
693
694 octave::command_editor::add_event_hook (process_graphics_events);
695 }
696 else
697 {
698 m_event_processing--;
699
700 if (m_event_queue.empty () && m_event_processing == 0)
701 octave::command_editor::remove_event_hook (process_graphics_events);
702 }
703}
704
705OCTAVE_END_NAMESPACE(octave)
octave_idx_type numel() const
Number of elements in the array.
Definition Array.h:418
Definition Cell.h:41
void restore_gcbo()
void post_set(const graphics_handle &h, const std::string &name, const octave_value &value, bool notify_toolkit=true, bool redraw_figure=false)
graphics_object get_object(double val) const
Definition gh-manager.h:68
void renumber_figure(const graphics_handle &old_gh, const graphics_handle &new_gh)
void execute_listener(const graphics_handle &h, const octave_value &l)
gh_manager(octave::interpreter &interp)
void push_figure(const graphics_handle &h)
void post_event(const graphics_event &e)
void close_all_figures()
Matrix figure_handle_list(bool show_hidden=false)
Definition gh-manager.h:131
graphics_handle lookup(double val) const
Definition gh-manager.h:54
void enable_event_processing(bool enable=true)
void post_function(graphics_event::event_fcn fcn, void *fcn_data=nullptr)
void free(const graphics_handle &h, bool from_root=false)
Definition gh-manager.cc:91
void pop_figure(const graphics_handle &h)
void post_callback(const graphics_handle &h, const std::string &name, const octave_value &data=Matrix())
graphics_handle make_graphics_handle(const std::string &go_name, const graphics_handle &p, bool integer_figure_handle=false, bool call_createfcn=true, bool notify_toolkit=true)
void execute_callback(const graphics_handle &h, const std::string &name, const octave_value &data=Matrix())
Definition gh-manager.h:150
int process_events(bool force=false)
graphics_handle make_figure_handle(double val, bool notify_toolkit=true)
graphics_handle get_handle(bool integer_figure_handle)
Definition gh-manager.cc:50
double value() const
Definition oct-handle.h:78
bool ok() const
Definition oct-handle.h:113
octave_value as_octave_value() const
Definition oct-handle.h:80
bool is_function_handle() const
Definition ov.h:768
std::string class_name() const
Definition ov.h:1362
octave_function * function_value(bool silent=false) const
Cell cell_value() const
octave_idx_type rows() const
Definition ov.h:545
bool is_string() const
Definition ov.h:637
bool is_defined() const
Definition ov.h:592
bool isempty() const
Definition ov.h:601
bool is_function() const
Definition ov.h:777
bool iscell() const
Definition ov.h:604
std::string string_value(bool force=false) const
Definition ov.h:983
octave_idx_type length() const
octave_idx_type columns() const
Definition ov.h:547
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value daspk_fcn
void warning(const char *fmt,...)
Definition error.cc:1078
void error(const char *fmt,...)
Definition error.cc:1003
octave_handle graphics_handle
bool isfigure(double val)
void force_close_figure(const graphics_handle &h)
void close_figure(const graphics_handle &h)
octave_value_list Fdrawnow(octave::interpreter &interp, const octave_value_list &args, int)
base_graphics_object * make_graphics_object_from_type(const caseless_str &type, const graphics_handle &h, const graphics_handle &p)
Definition graphics.cc:1409
bool Vdrawnow_requested
Definition input.cc:89