changeset 13924:3b654a0753b1

Implement waitfor, uiwait and uiresume. * liboctave/cmd-edit.h (command_editor::run_event_hooks): New static method. * liboctave/cmd-edit.cc (command_editor::run_event_hooks): Implement it. * src/graphics.h.in (listener_mode::PREDELETE): New enum value. * src/graphics.cc (<ctime>, "cutils.h"): New included headers. (base_properties::has_dynamic_properties): Look also into all_props. (gh_manager::do_execute_callback): Allow any type of function to be specified, not only function handles. (waitfor_results): New utility static variable. (compare_property_values, cleanup_waitfor_id, do_cleanup_waitfor_listener, cleanup_waitfor_postset_listener, cleanup_waitfor_predelete_listener, waitfor_listener, waitfor_del_listener): New utility static functions. (Fwaitfor): New function. * plot/uiwait.m: New function. * plot/uiresume.m: Likewise. * plot/modules.mk (plot_FCN_FILES): Add them to the list.
author Michael Goffioul <michael.goffioul@gmail.com>
date Wed, 23 Nov 2011 08:59:25 +0000
parents 7b83576b3b48
children ec435c4d8198
files liboctave/cmd-edit.cc liboctave/cmd-edit.h scripts/plot/module.mk scripts/plot/uiresume.m scripts/plot/uiwait.m src/graphics.cc src/graphics.h.in
diffstat 7 files changed, 538 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/liboctave/cmd-edit.cc	Wed Nov 23 00:20:00 2011 -0500
+++ b/liboctave/cmd-edit.cc	Wed Nov 23 08:59:25 2011 +0000
@@ -1227,6 +1227,12 @@
 }
 
 void
+command_editor::run_event_hooks (void)
+{
+  event_handler ();
+}
+
+void
 command_editor::read_init_file (const std::string& file_arg)
 {
   if (instance_ok ())
--- a/liboctave/cmd-edit.h	Wed Nov 23 00:20:00 2011 -0500
+++ b/liboctave/cmd-edit.h	Wed Nov 23 08:59:25 2011 +0000
@@ -137,6 +137,8 @@
 
   static void remove_event_hook (event_hook_fcn f);
 
+  static void run_event_hooks (void);
+
   static void read_init_file (const std::string& file = std::string ());
 
   static void re_read_init_file (void);
--- a/scripts/plot/module.mk	Wed Nov 23 00:20:00 2011 -0500
+++ b/scripts/plot/module.mk	Wed Nov 23 08:59:25 2011 +0000
@@ -190,8 +190,10 @@
   plot/uipanel.m \
   plot/uipushtool.m \
   plot/uiputfile.m \
+  plot/uiresume.m \
   plot/uitoggletool.m \
   plot/uitoolbar.m \
+  plot/uiwait.m \
   plot/view.m \
   plot/waitforbuttonpress.m \
   plot/whitebg.m \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/plot/uiresume.m	Wed Nov 23 08:59:25 2011 +0000
@@ -0,0 +1,45 @@
+## Copyright (C) 2011 Michael Goffioul
+##
+## This file is part of Octave.
+##
+## Octave is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or (at
+## your option) any later version.
+##
+## Octave is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+## General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn  {Function File} uiresume (@var{h})
+## Resumes program execution suspended with @code{uiwait}. The handle @var{h}
+## must be the same as the on specified in @code{uiwait}. If the handle
+## is invalid or there is no @code{uiwait} call pending for the figure
+## with handle @var{h}, this function does nothing.
+## @seealso{uiwait}
+## @end deftypefn
+
+## Author: goffioul
+
+function uiresume (h)
+
+  if (! ishandle (h) || ! strcmp (get (h, "type"), "figure"))
+    error ("uiresume: invalid figure handle");
+  endif
+
+  try
+    uiwait_state = get (h, "__uiwait_state__");
+    if (strcmp (uiwait_state, "active"))
+      set (h, "__uiwait_state__", "triggered");
+    endif
+  catch
+    # Ignore exception
+  end_try_catch
+
+endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/plot/uiwait.m	Wed Nov 23 08:59:25 2011 +0000
@@ -0,0 +1,80 @@
+## Copyright (C) 2011 Michael Goffioul
+##
+## This file is part of Octave.
+##
+## Octave is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or (at
+## your option) any later version.
+##
+## Octave is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+## General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn  {Function File} uiwait
+## @deftypefnx {Function File} uiwait (@var{h})
+## @deftypefnx {Function File} uiwait (@var{h}, @var{timeout})
+## Suspends program execution until the figure with handle @var{h} is
+## deleted or @code{uiresume} is called. When no figure handle is specified,
+## this function uses the current figure.
+##
+## If the figure handle is invalid or there is no current figure, this
+## functions returns immediately.
+##
+## When specified, @var{timeout} defines the number of seconds to wait
+## for the figure deletion or the @code{uiresume} call. The timeout value
+## must be at least 1. If a smaller value is specified, a warning is issued
+## and a timeout value of 1 is used instead. If a non integer value is
+## specified, it is truncated towards 0. If @var{timeout} is not specified,
+## the program execution is suspended indefinitely.
+## @seealso{uiresume, waitfor}
+## @end deftypefn
+
+## Author: goffioul
+
+function uiwait (varargin)
+
+  h = [];
+  timeout = [];
+
+  if (nargin == 0)
+    h = get (0, "currentfigure");
+  else
+    h = varargin{1};
+    if (! ishandle (h) || ! strcmp (get (h, "type"), "figure"))
+      error ("uiwait: invalid figure handle");
+    endif
+    if (nargin > 1)
+      timeout = varargin{2};
+    endif
+  endif
+
+  if (! isempty (h))
+    unwind_protect
+      try
+        addproperty ("__uiwait_state__", h, "radio", "none|{active}|triggered");
+      catch
+        if (! strcmp (get (h, "__uiwait_state__"), "none"))
+          error ("uiwait: an active uiwait call for this figure already exists");
+        endif
+	set (h, "__uiwait_state__", "active");
+      end_try_catch
+      waitfor_args = {h, "__uiwait_state__", "triggered"};
+      if (! isempty (timeout))
+        waitfor_args(end+1:end+2) = {"timeout", timeout};
+      endif
+      waitfor (waitfor_args{:});
+    unwind_protect_cleanup
+      if (ishandle (h) && isprop (h, "__uiwait_state__"))
+        set (h, "__uiwait_state__", "none");
+      endif
+    end_unwind_protect
+  endif
+
+endfunction
--- a/src/graphics.cc	Wed Nov 23 00:20:00 2011 -0500
+++ b/src/graphics.cc	Wed Nov 23 08:59:25 2011 +0000
@@ -27,6 +27,7 @@
 #include <cctype>
 #include <cfloat>
 #include <cstdlib>
+#include <ctime>
 
 #include <algorithm>
 #include <list>
@@ -39,6 +40,7 @@
 #include "file-stat.h"
 
 #include "cmd-edit.h"
+#include "cutils.h"
 #include "defun.h"
 #include "display.h"
 #include "error.h"
@@ -2484,7 +2486,10 @@
 {
   const std::set<std::string>& dynprops = dynamic_property_names ();
 
-  return dynprops.find (pname) != dynprops.end ();
+  if (dynprops.find (pname) != dynprops.end ())
+    return true;
+  else
+    return all_props.find (pname) != all_props.end ();
 }
 
 void
@@ -7679,7 +7684,7 @@
 
       octave_value cb = cb_arg;
 
-      if (cb.is_function_handle ())
+      if (cb.is_function () || cb.is_function_handle ())
         fcn = cb.function_value ();
       else if (cb.is_string ())
         {
@@ -7690,7 +7695,8 @@
         }
       else if (cb.is_cell () && cb.length () > 0
                && (cb.rows () == 1 || cb.columns () == 1)
-               && cb.cell_value ()(0).is_function_handle ())
+               && (cb.cell_value ()(0).is_function ()
+                   || cb.cell_value ()(0).is_function_handle ()))
         {
           Cell c = cb.cell_value ();
 
@@ -9433,3 +9439,396 @@
 
   return ret;
 }
+
+static bool
+compare_property_values (const octave_value& o1, const octave_value& o2)
+{
+  octave_value_list args (2);
+
+  args(0) = o1;
+  args(1) = o2;
+
+  octave_value_list result = feval ("isequal", args, 1);
+
+  if (! error_state && result.length () > 0)
+    return result(0).bool_value ();
+
+  return false;
+}
+
+static std::map<uint32_t, bool> waitfor_results;
+
+static void
+cleanup_waitfor_id (uint32_t id)
+{
+  waitfor_results.erase (id);
+}
+
+static void
+do_cleanup_waitfor_listener (const octave_value& listener,
+                             listener_mode mode = POSTSET)
+{
+  Cell c = listener.cell_value ();
+
+  if (c.numel () >= 4)
+    {
+      double h = c(2).double_value ();
+
+      if (! error_state)
+        {
+          caseless_str pname = c(3).string_value ();
+
+          if (! error_state)
+            {
+              gh_manager::auto_lock guard;
+
+              graphics_handle handle = gh_manager::lookup (h);
+
+              if (handle.ok ())
+                {
+                  graphics_object go = gh_manager::get_object (handle);
+
+                  if (go.get_properties ().has_property (pname))
+                    {
+                      go.get_properties ()
+                        .delete_listener (pname, listener, mode);
+                      if (mode == POSTSET)
+                        go.get_properties ()
+                          .delete_listener (pname, listener, PERSISTENT);
+                    }
+                }
+            }
+        }
+    }
+}
+
+static void
+cleanup_waitfor_postset_listener(const octave_value& listener)
+{ do_cleanup_waitfor_listener (listener, POSTSET); }
+
+static void
+cleanup_waitfor_predelete_listener(const octave_value& listener)
+{ do_cleanup_waitfor_listener (listener, PREDELETE); }
+
+static octave_value_list
+waitfor_listener (const octave_value_list& args, int)
+{
+  if (args.length () > 3)
+    {
+      uint32_t id = args(2).uint32_scalar_value ().value ();
+
+      if (! error_state)
+        {
+          if (args.length () > 5)
+            {
+              double h = args(0).double_value ();
+
+              if (! error_state)
+                {
+                  caseless_str pname = args(4).string_value ();
+
+                  if (! error_state)
+                    {
+                      gh_manager::auto_lock guard;
+
+                      graphics_handle handle = gh_manager::lookup (h);
+
+                      if (handle.ok ())
+                        {
+                          graphics_object go = gh_manager::get_object (handle);
+                          octave_value pvalue = go.get (pname);
+
+                          if (compare_property_values (pvalue, args(5)))
+                            waitfor_results[id] = true;
+                        }
+                    }
+                }
+            }
+          else
+            waitfor_results[id] = true;
+        }
+    }
+
+  return octave_value_list ();
+}
+
+static octave_value_list
+waitfor_del_listener (const octave_value_list& args, int)
+{
+  if (args.length () > 2)
+    {
+      uint32_t id = args(2).uint32_scalar_value ().value ();
+
+      if (! error_state)
+        waitfor_results[id] = true;
+    }
+
+  return octave_value_list ();
+}
+
+DEFUN (waitfor, args, ,
+  "-*- texinfo -*-\n\
+@deftypefn  {Built-in Function} {} waitfor (@var{h})\n\
+@deftypefnx {Built-in Function} {} waitfor (@var{h}, @var{prop})\n\
+@deftypefnx {Built-in Function} {} waitfor (@var{h}, @var{prop}, @var{value})\n\
+@deftypefnx {Built-in Function} {} waitfor (@dots{}, \"timeout\", @var{timeout})\n\
+Suspends the execution of the current program until a condition is\n\
+satisfied on the graphics handle @var{h}. While the program is suspended\n\
+graphics events are still being processed normally, allowing callbacks to\n\
+modify the state of graphics objects. This function is reentrant and can be\n\
+called from a callback, while another @code{waitfor} call is pending at\n\
+top-level.\n\
+\n\
+In the first form, program execution is suspended until the graphics object\n\
+@var{h} is destroyed. If the graphics handle is invalid, the function\n\
+returns immediately.\n\
+\n\
+In the second form, execution is suspended until the graphics object is\n\
+destroyed or the property named @var{prop} is modified. If the graphics\n\
+handle is invalid or the property does not exist, the function returns\n\
+immediately.\n\
+\n\
+In the third form, execution is suspended until the graphics object is\n\
+destroyed or the property named @var{prop} is set to @var{value}. The\n\
+function @code{isequal} is used to compare property values. If the graphics\n\
+handle is invalid, the property does not exist or the property is already\n\
+set to @var{value}, the function returns immediately.\n\
+\n\
+An optional timeout can be specified using the property @code{timeout}.\n\
+This timeout value is the number of seconds to wait for the condition to be\n\
+true. @var{timeout} must be at least 1. If a smaller value is specified, a\n\
+warning is issued and a value of 1 is used instead. If the timeout value is\n\
+not an integer, it is truncated towards 0.\n\
+\n\
+To define a condition on a property named @code{timeout}, use the string\n\
+@code{\\timeout} instead.\n\
+\n\
+In all cases, typing CTRL-C stops program execution immediately.\n\
+@seealso{isequal}\n\
+@end deftypefn")
+{
+  if (args.length () > 0)
+    {
+      double h = args(0).double_value ();
+
+      if (! error_state)
+        {
+          caseless_str pname;
+
+          unwind_protect frame;
+
+          static uint32_t id_counter = 0;
+          uint32_t id = 0;
+
+          int max_arg_index = 0;
+          int timeout_index = -1;
+
+          int timeout = 0;
+
+          if (args.length () > 1)
+            {
+              pname = args(1).string_value ();
+              if (! error_state
+                  && ! pname.empty ()
+                  && ! pname.compare ("timeout"))
+                {
+                  if (pname.compare ("\\timeout"))
+                    pname = "timeout";
+
+                  static octave_value wf_listener;
+
+                  if (! wf_listener.is_defined ())
+                    wf_listener =
+                      octave_value (new octave_builtin (waitfor_listener,
+                                                        "waitfor_listener"));
+
+                  max_arg_index++;
+                  if (args.length () > 2)
+                    {
+                      if (args(2).is_string ())
+                        {
+                          caseless_str s = args(2).string_value ();
+
+                          if (! error_state)
+                            {
+                              if (s.compare ("timeout"))
+                                timeout_index = 2;
+                              else
+                                max_arg_index++;
+                            }
+                        }
+                      else
+                        max_arg_index++;
+                    }
+
+                  Cell listener (1, max_arg_index >= 2 ? 5 : 4);
+
+                  id = id_counter++;
+                  frame.add_fcn (cleanup_waitfor_id, id);
+                  waitfor_results[id] = false;
+
+                  listener(0) = wf_listener;
+                  listener(1) = octave_uint32 (id);
+                  listener(2) = h;
+                  listener(3) = pname;
+
+                  if (max_arg_index >= 2)
+                    listener(4) = args(2);
+
+                  octave_value ov_listener (listener);
+
+                  gh_manager::auto_lock guard;
+
+                  graphics_handle handle = gh_manager::lookup (h);
+
+                  if (handle.ok ())
+                    {
+                      graphics_object go = gh_manager::get_object (handle);
+
+                      if (max_arg_index >= 2
+                          && compare_property_values (go.get (pname),
+                                                      args(2)))
+                        waitfor_results[id] = true;
+                      else
+                        {
+
+                          frame.add_fcn (cleanup_waitfor_postset_listener,
+                                         ov_listener);
+                          go.add_property_listener (pname, ov_listener,
+                                                    POSTSET);
+                          go.add_property_listener (pname, ov_listener,
+                                                    PERSISTENT);
+
+                          if (go.get_properties ()
+                              .has_dynamic_property (pname))
+                            {
+                              static octave_value wf_del_listener;
+
+                              if (! wf_del_listener.is_defined ())
+                                wf_del_listener =
+                                  octave_value (new octave_builtin
+                                                (waitfor_del_listener,
+                                                 "waitfor_del_listener"));
+
+                              Cell del_listener (1, 4);
+
+                              del_listener(0) = wf_del_listener;
+                              del_listener(1) = octave_uint32 (id);
+                              del_listener(2) = h;
+                              del_listener(3) = pname;
+
+                              octave_value ov_del_listener (del_listener);
+
+                              frame.add_fcn (cleanup_waitfor_predelete_listener,
+                                             ov_del_listener);
+                              go.add_property_listener (pname, ov_del_listener,
+                                                        PREDELETE);
+                            }
+                        }
+                    }
+                }
+              else if (error_state || pname.empty ())
+                error ("waitfor: invalid property name, expected a non-empty string value");
+            }
+
+          if (! error_state
+              && timeout_index < 0
+              && args.length () > (max_arg_index + 1))
+            {
+              caseless_str s = args(max_arg_index + 1).string_value ();
+
+              if (! error_state)
+                {
+                  if (s.compare ("timeout"))
+                    timeout_index = max_arg_index + 1;
+                  else
+                    error ("waitfor: invalid parameter `%s'", s.c_str ());
+                }
+              else
+                error ("waitfor: invalid parameter, expected `timeout'");
+            }
+
+          if (! error_state && timeout_index >= 0)
+            {
+              if (args.length () > (timeout_index + 1))
+                {
+                  timeout = static_cast<int>
+                    (args(timeout_index + 1).scalar_value ());
+
+                  if (! error_state)
+                    {
+                      if (timeout < 1)
+                        {
+                          warning ("waitfor: the timeout value must be >= 1, using 1 instead");
+                          timeout = 1;
+                        }
+                    }
+                  else
+                    error ("waitfor: invalid timeout value, expected a value >= 1");
+                }
+              else
+                error ("waitfor: missing timeout value");
+            }
+
+          // FIXME: There is still a "hole" in the following loop. The code
+          //        assumes that an object handle is unique, which is a fair
+          //        assumptions, except for figures. If a figure is destroyed
+          //        then recreated with the same figure ID, within the same
+          //        run of event hooks, then the figure destruction won't be
+          //        caught and the loop will not stop. This is an unlikely
+          //        possibility in practice, though.
+          //
+          //        Using deletefcn callback is also unreliable as it could be
+          //        modified during a callback execution and the waitfor loop
+          //        would not stop.
+          //
+          //        The only "good" implementation would require object
+          //        listeners, similar to property listeners.
+
+          time_t start = 0;
+
+          if (timeout > 0)
+            start = time (0);
+
+          while (! error_state)
+            {
+              if (true)
+                {
+                  gh_manager::auto_lock guard;
+
+                  graphics_handle handle = gh_manager::lookup (h);
+
+                  if (handle.ok ())
+                    {
+                      if (! pname.empty () && waitfor_results[id])
+                        break;
+                    }
+                  else
+                    break;
+                }
+
+#if defined (WIN32) && ! defined (__CYGWIN__)
+              Sleep (100);
+#else
+              octave_usleep (100000);
+#endif
+
+              OCTAVE_QUIT;
+
+              command_editor::run_event_hooks ();
+
+              if (timeout > 0)
+                {
+                  if (start + timeout < time (0))
+                    break;
+                }
+            }
+        }
+      else
+        error ("waitfor: invalid handle value.");
+    }
+  else
+    print_usage ();
+
+  return octave_value ();
+}
--- a/src/graphics.h.in	Wed Nov 23 00:20:00 2011 -0500
+++ b/src/graphics.h.in	Wed Nov 23 08:59:25 2011 +0000
@@ -333,7 +333,7 @@
 
 class property;
 
-enum listener_mode { POSTSET, PERSISTENT };
+enum listener_mode { POSTSET, PERSISTENT, PREDELETE };
 
 class base_property
 {