changeset 25989:0ab70de0348e

Allow loading and saving of multiple graphic handles in a single file (bug #54924). * hgload.m: Rewrite docstring. Clarify comments. Use space after function and before opening parenthesis. Use isargout and only calculate second argument when required. Test whether override structure is empty before looping over input graphics handles for better performance. Use a for loop over possibly multiple handles and convert each one individually with struct2hdl. Place input validation BIST tests at end of file. Add new BIST test for non-existent file ending with extension ".fig". hgsave.m: Rewrite docstring. Use "all (ishghandle (h))" to verify that multiple inputs are all graphics handles. Remove input validation requiring h to be scalar. Use a for loop over graphics handles to save each handle into a struct array. * openfig.m: Rewrite docstring to clarify that multiple figures can be opened. * savefig.m: Rewrite docstring to clarify that multiple figures can be saved. Use "all (isfigure (...))" in input validation to check all input handles. Change validation error message for second input to reference the variable "FILENAME". Update BIST tests.
author Guillaume Flandin <guillaume.offline@gmail.com>
date Wed, 31 Oct 2018 12:11:05 -0700
parents 976e7346abf4
children 940aabcd6fdb
files scripts/plot/util/hgload.m scripts/plot/util/hgsave.m scripts/plot/util/openfig.m scripts/plot/util/savefig.m
diffstat 4 files changed, 80 insertions(+), 66 deletions(-) [+]
line wrap: on
line diff
--- a/scripts/plot/util/hgload.m	Fri Feb 12 01:23:43 2016 +0100
+++ b/scripts/plot/util/hgload.m	Wed Oct 31 12:11:05 2018 -0700
@@ -19,31 +19,31 @@
 ## -*- texinfo -*-
 ## @deftypefn  {} {@var{h} =} hgload (@var{filename})
 ## @deftypefnx {} {[@var{h}, @var{old_prop}] =} hgload (@var{filename}, @var{prop_struct})
-## Load the graphics object in @var{filename} into the graphics handle @var{h}.
+## Load the graphics objects in @var{filename} into a vector of graphics
+## handles @var{h}.
 ##
 ## If @var{filename} has no extension, Octave will try to find the file with
-## and without the standard extension of @file{.ofig}.
+## and without the default extension @file{.ofig}.
 ##
 ## If provided, the elements of structure @var{prop_struct} will be used to
-## override the properties of top-level objects stored in @var{filename} and
-## the old values will be stored in @var{old_prop}.  @var{old_prop} is a cell
-## array matching the size of @var{h}; each cell contains a structure of the
-## existing property names and values before being overridden.
+## override the properties of top-level objects stored in @var{filename}, and
+## the saved values from @var{filename} will be stored in @var{old_prop}.
+## @var{old_prop} is a cell array matching the size of @var{h}; each cell
+## contains a structure of the existing property names and values before being
+## overridden.
 ##
 ## @seealso{openfig, hgsave, struct2hdl}
 ## @end deftypefn
 
-## Author: Massimiliano Fasi
-
 function [h, old_prop] = hgload (filename, prop_struct = struct ())
 
-  ## Check input arguments
+  ## Check number of input arguments
   if (nargin == 0 || nargin > 2)
     print_usage ();
   endif
 
-  ## Check second input argument
-  if (! isstruct(prop_struct))
+  ## Check type of second input argument
+  if (! isstruct (prop_struct))
     error ("hgload: PROP_STRUCT must be a struct");
   endif
 
@@ -71,51 +71,64 @@
     error ("hgload: could not load hgsave-formatted object in file %s", filename);
   endif
 
-  ## Override properties of top-level object
-  old_prop = [];
+  ## Override properties of top-level objects
+  calc_old_prop = false;
+  if (isargout (2))
+    calc_old_prop = true;
+    old_prop = repmat ({[]}, 1, numel (hg));
+  endif
   fn_new = fieldnames (prop_struct);
-  fn_old = fieldnames (hg.properties);
-  for i = 1:numel (fn_new)
-    idx = ismember (tolower (fn_old), tolower (fn_new{i}));
-    if (any (idx))
-      old_prop.(fn_new{i}) = hg.properties.(fn_old{idx});
-      hg.properties.(fn_old{idx}) = prop_struct.(fn_new{i});
-    endif
-  endfor
-  old_prop = { old_prop };
+  if (! isempty (fn_new))
+    for i = 1:numel (hg)
+      fn_old = fieldnames (hg(i).properties);
+      for j = 1:numel (fn_new)
+        idx = ismember (tolower (fn_old), tolower (fn_new{j}));
+        if (any (idx))
+          if (calc_old_prop)
+            old_prop{i}.(fn_new{j}) = hg(i).properties.(fn_old{idx});
+          endif
+          hg(i).properties.(fn_old{idx}) = prop_struct.(fn_new{j});
+        endif
+      endfor
+    endfor
+  endif
 
   ## Build the graphics handle object
-  h = struct2hdl (hg);
+  h = zeros (1, numel (hg));
+  for i = 1:numel (hg)
+    h(i) = struct2hdl (hg(i));
+  endfor
 
 endfunction
 
 
 ## Functional test for hgload/hgsave pair is in hgsave.m
 
-## Test input validation
-%!error hgload ()
-%!error hgload (1, 2, 3)
-%!error hgload (1, {})
-%!error <unable to locate file> hgload ("%%_A_REALLY_UNLIKELY_FILENAME_%%")
-
-## Test second input and output
+## Test overriding saved properties with second input
 %!test
 %! unwind_protect
-%!   h1 = figure ("Visible", "off");
-%!   col = get (h1, "Color");
+%!   h1 = figure ("visible", "off");
+%!   col = get (h1, "color");
 %!   ftmp = [tempname() ".ofig"];
 %!   hgsave (h1, ftmp);
 %!   close (h1);
 %!   [h2, old] = hgload (ftmp);
 %!   assert (old, {[]});
-%!   [h3, old] = hgload (ftmp, struct ("Color", [1 0 0]));
-%!   assert (get (h3, "Color"), [1 0 0]);
+%!   [h3, old] = hgload (ftmp, struct ("color", [1 0 0]));
+%!   assert (get (h3, "color"), [1 0 0]);
 %!   assert (iscell (old) && numel (old) == 1);
-%!   assert (isstruct (old{1}) && isfield (old{1}, "Color"));
-%!   assert (old{1}.Color, col);
+%!   assert (isstruct (old{1}) && isfield (old{1}, "color"));
+%!   assert (old{1}.color, col);
 %! unwind_protect_cleanup
 %!   unlink (ftmp);
 %!   try, close (h1); end_try_catch
 %!   try, close (h2); end_try_catch
 %!   try, close (h3); end_try_catch
 %! end_unwind_protect
+
+## Test input validation
+%!error hgload ()
+%!error hgload (1, 2, 3)
+%!error <PROP_STRUCT must be a struct> hgload (1, {})
+%!error <unable to locate file> hgload ("%%_A_REALLY_UNLIKELY_FILENAME_%%")
+%!error <unable to locate file> hgload ("%%_A_REALLY_UNLIKELY_FILENAME_%%.fig")
--- a/scripts/plot/util/hgsave.m	Fri Feb 12 01:23:43 2016 +0100
+++ b/scripts/plot/util/hgsave.m	Wed Oct 31 12:11:05 2018 -0700
@@ -20,7 +20,7 @@
 ## @deftypefn  {} {} hgsave (@var{filename})
 ## @deftypefnx {} {} hgsave (@var{h}, @var{filename})
 ## @deftypefnx {} {} hgsave (@var{h}, @var{filename}, @var{fmt})
-## Save the graphics handle @var{h} to the file @var{filename} in the format
+## Save the graphics handle(s) @var{h} to the file @var{filename} in the format
 ## @var{fmt}.
 ##
 ## If unspecified, @var{h} is the current figure as returned by @code{gcf}.
@@ -28,7 +28,7 @@
 ## When @var{filename} does not have an extension the default filename
 ## extension @file{.ofig} will be appended.
 ##
-## If present, @var{fmt} should be one of the following:
+## If present, @var{fmt} must be one of the following:
 ##
 ## @itemize @bullet
 ## @item @option{-binary}, @option{-float-binary}
@@ -44,14 +44,14 @@
 ## @item @option{-zip}, @option{-z}
 ## @end itemize
 ##
-## When producing graphics for final publication use @code{print} or
-## @code{saveas}.  When it is important to be able to continue to edit a
-## figure as an Octave object, use @code{hgsave}/@code{hgload}.
-## @seealso{hgload, hdl2struct, saveas, print}
+## The default format is @option{-binary} to minimize storage.
+##
+## Programming Note: When producing graphics for final publication use
+## @code{print} or @code{saveas}.  When it is important to be able to continue
+## to edit a figure as an Octave object, use @code{hgsave}/@code{hgload}.
+## @seealso{hgload, hdl2struct, savefig, saveas, print}
 ## @end deftypefn
 
-## Author: Massimiliano Fasi
-
 function hgsave (h, filename, fmt = "-binary")
 
   if (nargin < 1 || nargin > 3)
@@ -65,26 +65,25 @@
     if (isempty (h))
       error ("hgsave: no current figure to save");
     endif
-  elseif (! (ishghandle (h) && ischar (filename)))
+  elseif (! (all (ishghandle (h)) && ischar (filename)))
     print_usage ();
   endif
 
-  if (! isscalar (h))
-    error ("hgsave: H must be a single graphics handle");
-  endif
-
   ## Check file extension
   [~, ~, ext] = fileparts (filename);
   if (isempty (ext))
     filename = [filename ".ofig"];
   endif
 
-  s_oct40 = hdl2struct (h);
+  for i = 1:numel (h)
+    s_oct40(i) = hdl2struct (h(i));
+  endfor
   save (fmt, filename, "s_oct40");
 
 endfunction
 
 
+## FIXME: Have to use gnuplot for printing figs that have never been visible.
 %!testif HAVE_MAGICK; any (strcmp ("gnuplot", available_graphics_toolkits ()))
 %! toolkit = graphics_toolkit ();
 %! graphics_toolkit ("gnuplot");
@@ -130,12 +129,13 @@
 ## Test input validation
 %!error hgsave ()
 %!error hgsave (1, 2, 3, 4)
-%!error hgsave ("abc", "def")
-%!error <H must be a single graphics handle>
+%!error <no current figure to save>
 %! unwind_protect
-%!   hf = figure ("visible", "off");
-%!   hax = axes ();
-%!   hgsave ([hf, hax], "foobar");
+%!  old_fig = get (0, "currentfigure");
+%!  set (0, "currentfigure", []);
+%!  hgsave ("foobar");
 %! unwind_protect_cleanup
-%!   close (hf);
+%!  set (0, "currentfigure", old_fig);
 %! end_unwind_protect
+%!error hgsave ([0, -1], "foobar")
+%!error hgsave (0, { "foobar" })
--- a/scripts/plot/util/openfig.m	Fri Feb 12 01:23:43 2016 +0100
+++ b/scripts/plot/util/openfig.m	Wed Oct 31 12:11:05 2018 -0700
@@ -23,8 +23,8 @@
 ## @deftypefnx {} {} openfig (@dots{}, @var{copies})
 ## @deftypefnx {} {} openfig (@dots{}, @var{visibility})
 ## @deftypefnx {} {@var{h} =} openfig (@dots{})
-## Open a saved figure window from @var{filename} and return its graphics
-## handle @var{h}.
+## Read saved figure window(s) from @var{filename} and return graphics
+## handle(s) @var{h}.
 ##
 ## By default, @var{filename} is @qcode{"Untitled.fig"}.  If a full path is not
 ## specified, the file opened will be the first one encountered in the load
@@ -43,7 +43,7 @@
 ## @var{visibility} is an optional input indicating whether to show the figure
 ## (@qcode{"visible"}) or not (@qcode{"invisible"}).  When @var{visibility} is
 ## specified as an input to @code{openfig} it overrides the visibility setting
-## stored stored in @var{filename}.
+## stored in @var{filename}.
 ##
 ## @seealso{open, hgload, savefig, struct2hdl}
 ## @end deftypefn
--- a/scripts/plot/util/savefig.m	Fri Feb 12 01:23:43 2016 +0100
+++ b/scripts/plot/util/savefig.m	Wed Oct 31 12:11:05 2018 -0700
@@ -23,7 +23,8 @@
 ## @deftypefnx {} {} savefig (@var{filename})
 ## @deftypefnx {} {} savefig (@var{h}, @var{filename})
 ## @deftypefnx {} {} savefig (@var{h}, @var{filename}, @qcode{"compact"})
-## Save graphics handle @var{h} to file @var{filename}.
+## Save figure windows specified by graphics handle(s) @var{h} to file
+## @var{filename}.
 #
 ## If unspecified, @var{h} is the current figure returned by @code{gcf}.
 ##
@@ -45,7 +46,7 @@
 
   ## Check input arguments
   if (nargin == 1)
-    if (isfigure (varargin{1}))
+    if (all (isfigure (varargin{1})))
       h = varargin{1};
     elseif (ischar (varargin{1}))
       filename = varargin{1};
@@ -53,12 +54,12 @@
       error ("savefig: first argument must be a figure handle or filename");
     endif
   elseif (nargin == 2 || nargin == 3)
-    if (! isfigure (varargin{1}))
+    if (! all (isfigure (varargin{1})))
       error ("savefig: H must be a valid figure handle");
     endif
     h = varargin{1};
     if (! ischar (varargin{2}))
-      error ("savefig: second argument must be a string");
+      error ("savefig: FILENAME must be a string");
     endif
     filename = varargin{2};
     # Input "compact" ignored (Matlab compatibility)
@@ -77,7 +78,7 @@
     filename = [filename ".fig"];
   endif
 
-  ## Save file
+  ## Save handles to file
   hgsave (h, filename);
 
 endfunction
@@ -98,8 +99,8 @@
 ## Test input validation
 %!error savefig (1,2,3,4)
 %!error <must be a figure handle or filename> savefig (struct ())
-%!error <H must be a valid figure handle> savefig (-1, "foobar")
-%!error <second argument must be a string>
+%!error <H must be a valid figure handle> savefig ([0, -1], "foobar")
+%!error <FILENAME must be a string>
 %! unwind_protect
 %!   h = figure ("visible", "off");
 %!   savefig (h, -1);