GNU Octave 7.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
graphics.cc
Go to the documentation of this file.
1////////////////////////////////////////////////////////////////////////
2//
3// Copyright (C) 2007-2022 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 <cctype>
31#include <cmath>
32#include <cstdint>
33#include <cstdlib>
34
35#include <algorithm>
36#include <iostream>
37#include <limits>
38#include <list>
39#include <map>
40#include <set>
41#include <string>
42#include <sstream>
43
44#include "cmd-edit.h"
45#include "file-ops.h"
46#include "file-stat.h"
47#include "oct-locbuf.h"
48#include "oct-time.h"
49
50#include "builtin-defun-decls.h"
51#include "defun.h"
52#include "display.h"
53#include "error.h"
54#include "graphics.h"
55#include "input.h"
56#include "interpreter-private.h"
57#include "interpreter.h"
58#include "ov.h"
59#include "ovl.h"
60#include "oct-map.h"
61#include "ov-fcn-handle.h"
62#include "pager.h"
63#include "parse.h"
64#include "text-engine.h"
65#include "text-renderer.h"
66#include "unwind-prot.h"
67#include "utils.h"
69
70OCTAVE_NAMESPACE_BEGIN
71
72// forward declarations
73static octave_value xget (const graphics_handle& h, const caseless_str& name);
74
75OCTAVE_NORETURN static
76void
77err_set_invalid (const std::string& pname)
78{
79 error ("set: invalid value for %s property", pname.c_str ());
80}
81
82// Check to see that PNAME matches just one of PNAMES uniquely.
83// Return the full name of the match, or an empty caseless_str object
84// if there is no match, or the match is ambiguous.
85
86static caseless_str
87validate_property_name (const std::string& who, const std::string& what,
88 const std::set<std::string>& pnames,
89 const caseless_str& pname)
90{
91 std::size_t len = pname.length ();
92 std::set<std::string> matches;
93
94 // Find exact or partial matches to property name
95 for (const auto& propnm : pnames)
96 {
97 if (pname.compare (propnm, len))
98 {
99 if (len == propnm.length ())
100 return pname; // Exact match.
101
102 matches.insert (propnm);
103 }
104 }
105
106 std::size_t num_matches = matches.size ();
107
108 if (num_matches == 0)
109 error ("%s: unknown %s property %s",
110 who.c_str (), what.c_str (), pname.c_str ());
111 else if (num_matches > 1)
112 {
113 string_vector sv (matches);
114
115 std::ostringstream os;
116
117 sv.list_in_columns (os);
118
119 std::string match_list = os.str ();
120
121 error ("%s: ambiguous %s property name %s; possible matches:\n\n%s",
122 who.c_str (), what.c_str (), pname.c_str (), match_list.c_str ());
123 }
124 else // num_matches == 1
125 {
126 // Exact match was handled above.
127 std::string possible_match = *(matches.begin ());
128
129 warning_with_id ("Octave:abbreviated-property-match",
130 "%s: allowing %s to match %s property %s",
131 who.c_str (), pname.c_str (), what.c_str (),
132 possible_match.c_str ());
133
134 return possible_match;
135 }
136
137 return caseless_str ();
138}
139
140static Matrix
142{
143 // The values below have been produced by viridis (64)(:)
144 // It would be nice to be able to feval the
145 // viridis function but since there is a static property object that includes
146 // a colormap_property object, we need to initialize this before main is
147 // even called, so calling an interpreted function is not possible.
148
149 const double cmapv[] =
150 {
151 2.67004010000000e-01, 2.72651720952381e-01, 2.77106307619048e-01,
152 2.80356151428571e-01, 2.82390045238095e-01, 2.83204606666667e-01,
153 2.82809341428571e-01, 2.81230763333333e-01, 2.78516153333333e-01,
154 2.74735528571429e-01, 2.69981791904762e-01, 2.64368580952381e-01,
155 2.58026184285714e-01, 2.51098684761905e-01, 2.43732853333333e-01,
156 2.36073294285714e-01, 2.28263191428571e-01, 2.20424955714286e-01,
157 2.12666598571429e-01, 2.05079113809524e-01, 1.97721880952381e-01,
158 1.90631350000000e-01, 1.83819438571429e-01, 1.77272360952381e-01,
159 1.70957518571429e-01, 1.64832915714286e-01, 1.58845368095238e-01,
160 1.52951235714286e-01, 1.47131626666667e-01, 1.41402210952381e-01,
161 1.35832975714286e-01, 1.30582113809524e-01, 1.25898377619048e-01,
162 1.22163105714286e-01, 1.19872409523810e-01, 1.19626570000000e-01,
163 1.22045948571429e-01, 1.27667691904762e-01, 1.36834947142857e-01,
164 1.49643331428571e-01, 1.65967274285714e-01, 1.85538397142857e-01,
165 2.08030450000000e-01, 2.33127309523809e-01, 2.60531475238095e-01,
166 2.90000730000000e-01, 3.21329971428571e-01, 3.54355250000000e-01,
167 3.88930322857143e-01, 4.24933143333333e-01, 4.62246770476190e-01,
168 5.00753620000000e-01, 5.40336957142857e-01, 5.80861172380952e-01,
169 6.22170772857143e-01, 6.64087320476191e-01, 7.06403823333333e-01,
170 7.48885251428571e-01, 7.91273132857143e-01, 8.33302102380952e-01,
171 8.74717527142857e-01, 9.15296319047619e-01, 9.54839555238095e-01,
172 9.93247890000000e-01, 4.87433000000000e-03, 2.58456800000000e-02,
173 5.09139004761905e-02, 7.42014957142857e-02, 9.59536042857143e-02,
174 1.16893314761905e-01, 1.37350195714286e-01, 1.57479940000000e-01,
175 1.77347967619048e-01, 1.96969168571429e-01, 2.16330337619048e-01,
176 2.35404660952381e-01, 2.54161735714286e-01, 2.72573219047619e-01,
177 2.90619516666667e-01, 3.08291041428571e-01, 3.25586450952381e-01,
178 3.42517215238095e-01, 3.59102207142857e-01, 3.75366067142857e-01,
179 3.91340913333333e-01, 4.07061480000000e-01, 4.22563764285714e-01,
180 4.37885543809524e-01, 4.53062984285714e-01, 4.68129543809524e-01,
181 4.83117059523810e-01, 4.98052961428571e-01, 5.12959473333333e-01,
182 5.27854311428571e-01, 5.42750087142857e-01, 5.57652481904762e-01,
183 5.72563073333333e-01, 5.87476284285714e-01, 6.02382410952381e-01,
184 6.17265840000000e-01, 6.32106955714286e-01, 6.46881817142857e-01,
185 6.61562926190476e-01, 6.76119717142857e-01, 6.90518987142857e-01,
186 7.04725181904762e-01, 7.18700950000000e-01, 7.32406441904762e-01,
187 7.45802021904762e-01, 7.58846480000000e-01, 7.71497934761905e-01,
188 7.83714033809524e-01, 7.95453081428571e-01, 8.06673890000000e-01,
189 8.17337565714286e-01, 8.27409135714286e-01, 8.36858167619048e-01,
190 8.45663399523809e-01, 8.53815582857143e-01, 8.61321019047619e-01,
191 8.68206316666667e-01, 8.74522215714286e-01, 8.80346158571429e-01,
192 8.85780083333333e-01, 8.90945338571429e-01, 8.95973498571429e-01,
193 9.01005800000000e-01, 9.06156570000000e-01, 3.29415190000000e-01,
194 3.53367293333333e-01, 3.76236064761905e-01, 3.97901482857143e-01,
195 4.18250757142857e-01, 4.37178920000000e-01, 4.54595888571429e-01,
196 4.70433883333333e-01, 4.84653865714286e-01, 4.97250492857143e-01,
197 5.08254501428571e-01, 5.17731949047619e-01, 5.25780221428571e-01,
198 5.32522206190476e-01, 5.38097133333333e-01, 5.42651800000000e-01,
199 5.46335411904762e-01, 5.49287148571429e-01, 5.51635008571429e-01,
200 5.53493173333333e-01, 5.54953478571429e-01, 5.56089070000000e-01,
201 5.56952166666667e-01, 5.57576145714286e-01, 5.57974025714286e-01,
202 5.58142745238095e-01, 5.58058673809524e-01, 5.57684744285714e-01,
203 5.56973310000000e-01, 5.55864478571429e-01, 5.54288677142857e-01,
204 5.52175699047619e-01, 5.49445382857143e-01, 5.46023368571429e-01,
205 5.41830633809524e-01, 5.36795616666667e-01, 5.30847985714286e-01,
206 5.23924198571429e-01, 5.15966779523810e-01, 5.06924262857143e-01,
207 4.96751861428571e-01, 4.85412122857143e-01, 4.72873300000000e-01,
208 4.59105875238095e-01, 4.44095883333333e-01, 4.27825852857143e-01,
209 4.10292713809524e-01, 3.91487632857143e-01, 3.71420688571429e-01,
210 3.50098750000000e-01, 3.27544678571429e-01, 3.03798967142857e-01,
211 2.78916748571429e-01, 2.53000856190476e-01, 2.26223670000000e-01,
212 1.98879439523810e-01, 1.71494930000000e-01, 1.45037631428572e-01,
213 1.21291048571429e-01, 1.03326155238095e-01, 9.53507900000000e-02,
214 1.00469958095238e-01, 1.17876387142857e-01, 1.43936200000000e-01
215 };
216
217 // It would be nice if Matrix had a ctor allowing to do the
218 // following without a copy
219 Matrix cmap (64, 3, 0.0);
220 std::copy (cmapv, cmapv + (64*3), cmap.fortran_vec ());
221 return cmap;
222}
223
224static double
226{
227 octave::display_info& dpy_info
228 = octave::__get_display_info__ ("default_screendepth");
229
230 return dpy_info.depth ();
231}
232
233static Matrix
235{
236 Matrix retval (1, 4);
237
238 octave::display_info& dpy_info
239 = octave::__get_display_info__ ("default_screensize");
240
241 retval(0) = 1.0;
242 retval(1) = 1.0;
243 retval(2) = dpy_info.width ();
244 retval(3) = dpy_info.height ();
245
246 return retval;
247}
248
249static double
251{
252 octave::display_info& dpy_info
253 = octave::__get_display_info__ ("default_screenpixelsperinch");
254
255 return (dpy_info.x_dpi () + dpy_info.y_dpi ()) / 2;
256}
257
258static Matrix
260{
261 Matrix retval (7, 3, 0.0);
262
263 retval(0, 1) = 0.447;
264 retval(0, 2) = 0.741;
265
266 retval(1, 0) = 0.850;
267 retval(1, 1) = 0.325;
268 retval(1, 2) = 0.098;
269
270 retval(2, 0) = 0.929;
271 retval(2, 1) = 0.694;
272 retval(2, 2) = 0.125;
273
274 retval(3, 0) = 0.494;
275 retval(3, 1) = 0.184;
276 retval(3, 2) = 0.556;
277
278 retval(4, 0) = 0.466;
279 retval(4, 1) = 0.674;
280 retval(4, 2) = 0.188;
281
282 retval(5, 0) = 0.301;
283 retval(5, 1) = 0.745;
284 retval(5, 2) = 0.933;
285
286 retval(6, 0) = 0.635;
287 retval(6, 1) = 0.078;
288 retval(6, 2) = 0.184;
289
290 return retval;
291}
292
293static Matrix
294default_lim (bool logscale = false)
295{
296 Matrix m (1, 2);
297
298 if (logscale)
299 {
300 m(0) = 0.1;
301 m(1) = 1.0;
302 }
303 else
304 {
305 m(0) = 0.0;
306 m(1) = 1.0;
307 }
308
309 return m;
310}
311
312static Matrix
314{
315 Matrix retval (1, 2);
316
317 retval(0) = 0;
318 retval(1) = 1;
319
320 return retval;
321}
322
323static Matrix
325{
326 Matrix retval (1, 4);
327
328 retval(0) = 0;
329 retval(1) = 1;
330 retval(2) = 1; // minimum positive
331 retval(3) = -octave::numeric_limits<double>::Inf (); // maximum negative
332
333 return retval;
334}
335
336static Matrix
338{
339 Matrix m (64, 64);
340
341 int i = 0;
342 for (int col = 0; col < 64; col++)
343 for (int row = 0; row < 64; row++)
344 {
345 m(col, row) = static_cast<double> (default_im_data[i]);
346 i++;
347 }
348
349 return m;
350}
351
352static Matrix
354{
355 Matrix m (3, 3);
356
357 for (int col = 0; col < 3; col++)
358 for (int row = 0; row < 3; row++)
359 m(row, col) = col+1;
360
361 return m;
362}
363
364static Matrix
366{
367 Matrix m (3, 3);
368
369 for (int row = 0; row < 3; row++)
370 for (int col = 0; col < 3; col++)
371 m(row, col) = row+1;
372
373 return m;
374}
375
376static Matrix
378{
379 Matrix m (3, 3, 0.0);
380
381 for (int row = 0; row < 3; row++)
382 m(row, row) = 1.0;
383
384 return m;
385}
386
387static Matrix
389{
390 return default_surface_zdata ();
391}
392
393static Matrix
395{
396 Matrix m (1, 3);
397
398 m(0) = 1.0;
399 m(1) = 2.0;
400 m(2) = 3.0;
401
402 return m;
403}
404
405static Matrix
407{
408 Matrix m (3, 2, 0.0);
409
410 m(1) = 1.0;
411 m(3) = 1.0;
412 m(4) = 1.0;
413
414 return m;
415}
416
417static Matrix
419{
420 Matrix m (3, 1, 0.0);
421
422 m(1) = 1.0;
423
424 return m;
425}
426
427static Matrix
429{
430 Matrix m (3, 1, 1.0);
431
432 m(2) = 0.0;
433
434 return m;
435}
436
437static Matrix
439{
440 Matrix m (1, 4);
441
442 m(0) = 0.13;
443 m(1) = 0.11;
444 m(2) = 0.775;
445 m(3) = 0.815;
446
447 return m;
448}
449
450static Matrix
452{
453 Matrix m (1, 4);
454
455 m(0) = 0.0;
456 m(1) = 0.0;
457 m(2) = 1.0;
458 m(3) = 1.0;
459
460 return m;
461}
462
463static Matrix
465{
466 Matrix m (1, 2);
467
468 m(0) = 0.0;
469 m(1) = 90.0;
470
471 return m;
472}
473
474static Matrix
476{
477 Matrix m (1, 6);
478
479 m(0) = 0.0;
480 m(1) = 0.2;
481 m(2) = 0.4;
482 m(3) = 0.6;
483 m(4) = 0.8;
484 m(5) = 1.0;
485
486 return m;
487}
488
489static Matrix
491{
492 Matrix m (1, 2);
493
494 m(0) = 0.01;
495 m(1) = 0.025;
496
497 return m;
498}
499
500static Matrix
502{
503 Matrix m (1, 4);
504
505 m(0) = 300;
506 m(1) = 200;
507 m(2) = 560;
508 m(3) = 420;
509
510 return m;
511}
512
513static Matrix
515{
516 Matrix m (1, 2);
517
518 m(0) = 8.5;
519 m(1) = 11.0;
520
521 return m;
522}
523
524static Matrix
526{
527 Matrix m (1, 4);
528
529 // Update if default_figure_position or default_figure_papersize change
530 m(0) = 1.3421852580027660;
531 m(1) = 3.3191389435020748;
532 m(2) = 5.8156294839944680;
533 m(3) = 4.3617221129958503;
534
535 return m;
536}
537
538static std::string
540{
541 octave::gtk_manager& gtk_mgr
542 = octave::__get_gtk_manager__ ("default_graphics_toolkit");
543
544 return gtk_mgr.default_toolkit ();
545}
546
547static Matrix
549{
550 Matrix retval (1, 4);
551
552 retval(0) = 0;
553 retval(1) = 0;
554 retval(2) = 80;
555 retval(3) = 30;
556
557 return retval;
558}
559
560static Matrix
562{
563 Matrix retval (1, 2);
564
565 retval(0) = 0.01;
566 retval(1) = 0.1;
567
568 return retval;
569}
570
571static Matrix
573{
574 Matrix retval (1, 4);
575
576 retval(0) = 0;
577 retval(1) = 0;
578 retval(2) = 1;
579 retval(3) = 1;
580
581 return retval;
582}
583
584static Matrix
586{
587 Matrix m (1, 3);
588
589 m(0) = 1.0;
590 m(1) = 0.0;
591 m(2) = 1.0;
592
593 return m;
594}
595
596static Matrix
598{
599 Matrix retval (1, 4);
600
601 retval(0) = 20;
602 retval(1) = 20;
603 retval(2) = 300;
604 retval(3) = 300;
605
606 return retval;
607}
608
609static Matrix
611{
612 Matrix retval (2, 3);
613 retval(0, 0) = 1;
614 retval(0, 1) = 1;
615 retval(0, 2) = 1;
616 retval(1, 0) = 0.94;
617 retval(1, 1) = 0.94;
618 retval(1, 2) = 0.94;
619 return retval;
620}
621
622static graphics_handle
623make_graphics_handle (const std::string& go_name,
624 const graphics_handle& parent,
625 bool integer_figure_handle = false,
626 bool call_createfcn = true,
627 bool notify_toolkit = true)
628{
629 gh_manager& gh_mgr = octave::__get_gh_manager__ ("make_graphics_handle");
630
631 return gh_mgr.make_graphics_handle (go_name, parent, integer_figure_handle,
632 call_createfcn, notify_toolkit);
633}
634
635static double
636convert_font_size (double font_size, const caseless_str& from_units,
637 const caseless_str& to_units, double parent_height = 0)
638{
639 // Simple case where from_units == to_units
640
641 if (from_units.compare (to_units))
642 return font_size;
643
644 // Converts the given fontsize using the following transformation:
645 // <old_font_size> => points => <new_font_size>
646
647 double points_size = 0;
648 double res = 0;
649
650 if (from_units.compare ("points"))
651 points_size = font_size;
652 else
653 {
654 res = xget (0, "screenpixelsperinch").double_value ();
655
656 if (from_units.compare ("pixels"))
657 points_size = font_size * 72.0 / res;
658 else if (from_units.compare ("inches"))
659 points_size = font_size * 72.0;
660 else if (from_units.compare ("centimeters"))
661 points_size = font_size * 72.0 / 2.54;
662 else if (from_units.compare ("normalized"))
663 points_size = font_size * parent_height * 72.0 / res;
664 }
665
666 double new_font_size = 0;
667
668 if (to_units.compare ("points"))
669 new_font_size = points_size;
670 else
671 {
672 if (res <= 0)
673 res = xget (0, "screenpixelsperinch").double_value ();
674
675 if (to_units.compare ("pixels"))
676 new_font_size = points_size * res / 72.0;
677 else if (to_units.compare ("inches"))
678 new_font_size = points_size / 72.0;
679 else if (to_units.compare ("centimeters"))
680 new_font_size = points_size * 2.54 / 72.0;
681 else if (to_units.compare ("normalized"))
682 {
683 // Avoid setting font size to (0/0) = NaN
684
685 if (parent_height > 0)
686 new_font_size = points_size * res / (parent_height * 72.0);
687 }
688 }
689
690 return new_font_size;
691}
692
693static Matrix
694convert_position (const Matrix& pos, const caseless_str& from_units,
695 const caseless_str& to_units, const Matrix& parent_dim)
696{
697 Matrix retval (1, pos.numel (), 0.0);
698 double res = 0;
699 bool is_rectangle = (pos.numel () == 4);
700 bool is_2D = (pos.numel () == 2);
701
702 if (from_units.compare ("pixels"))
703 retval = pos;
704 else if (from_units.compare ("normalized"))
705 {
706 retval(0) = pos(0) * parent_dim(0) + 1;
707 retval(1) = pos(1) * parent_dim(1) + 1;
708 if (is_rectangle)
709 {
710 retval(2) = pos(2) * parent_dim(0);
711 retval(3) = pos(3) * parent_dim(1);
712 }
713 else if (! is_2D)
714 retval(2) = 0;
715 }
716 else if (from_units.compare ("characters"))
717 {
718 if (res <= 0)
719 res = xget (0, "screenpixelsperinch").double_value ();
720
721 double f = 0.0;
722
723 // FIXME: this assumes the system font is Helvetica 10pt
724 // (for which "x" requires 6x12 pixels at 74.951 pixels/inch)
725 f = 12.0 * res / 74.951;
726
727 if (f > 0)
728 {
729 retval(0) = 0.5 * pos(0) * f;
730 retval(1) = pos(1) * f;
731 if (is_rectangle)
732 {
733 retval(2) = 0.5 * pos(2) * f;
734 retval(3) = pos(3) * f;
735 }
736 else if (! is_2D)
737 retval(2) = 0;
738 }
739 }
740 else
741 {
742 if (res <= 0)
743 res = xget (0, "screenpixelsperinch").double_value ();
744
745 double f = 0.0;
746
747 if (from_units.compare ("points"))
748 f = res / 72.0;
749 else if (from_units.compare ("inches"))
750 f = res;
751 else if (from_units.compare ("centimeters"))
752 f = res / 2.54;
753
754 if (f > 0)
755 {
756 retval(0) = pos(0) * f + 1;
757 retval(1) = pos(1) * f + 1;
758 if (is_rectangle)
759 {
760 retval(2) = pos(2) * f;
761 retval(3) = pos(3) * f;
762 }
763 else if (! is_2D)
764 retval(2) = 0;
765 }
766 }
767
768 if (! to_units.compare ("pixels"))
769 {
770 if (to_units.compare ("normalized"))
771 {
772 retval(0) = (retval(0) - 1) / parent_dim(0);
773 retval(1) = (retval(1) - 1) / parent_dim(1);
774 if (is_rectangle)
775 {
776 retval(2) /= parent_dim(0);
777 retval(3) /= parent_dim(1);
778 }
779 else if (! is_2D)
780 retval(2) = 0;
781 }
782 else if (to_units.compare ("characters"))
783 {
784 if (res <= 0)
785 res = xget (0, "screenpixelsperinch").double_value ();
786
787 double f = 0.0;
788
789 f = 12.0 * res / 74.951;
790
791 if (f > 0)
792 {
793 retval(0) = 2 * retval(0) / f;
794 retval(1) = retval(1) / f;
795 if (is_rectangle)
796 {
797 retval(2) = 2 * retval(2) / f;
798 retval(3) = retval(3) / f;
799 }
800 else if (! is_2D)
801 retval(2) = 0;
802 }
803 }
804 else
805 {
806 if (res <= 0)
807 res = xget (0, "screenpixelsperinch").double_value ();
808
809 double f = 0.0;
810
811 if (to_units.compare ("points"))
812 f = res / 72.0;
813 else if (to_units.compare ("inches"))
814 f = res;
815 else if (to_units.compare ("centimeters"))
816 f = res / 2.54;
817
818 if (f > 0)
819 {
820 retval(0) = (retval(0) - 1) / f;
821 retval(1) = (retval(1) - 1) / f;
822 if (is_rectangle)
823 {
824 retval(2) /= f;
825 retval(3) /= f;
826 }
827 else if (! is_2D)
828 retval(2) = 0;
829 }
830 }
831 }
832
833 return retval;
834}
835
836static Matrix
838 const caseless_str& from_units,
839 const caseless_str& to_units)
840{
841 gh_manager& gh_mgr = octave::__get_gh_manager__ ("convert_text_position");
842
843 graphics_object go = gh_mgr.get_object (props.get___myhandle__ ());
844
845 graphics_object ax = go.get_ancestor ("axes");
846
847 Matrix retval;
848
849 if (ax.valid_object ())
850 {
851 const axes::properties& ax_props
852 = dynamic_cast<const axes::properties&> (ax.get_properties ());
853 graphics_xform ax_xform = ax_props.get_transform ();
854 bool is_rectangle = (pos.numel () == 4);
855 Matrix ax_bbox = ax_props.get_boundingbox (true),
856 ax_size = ax_bbox.extract_n (0, 2, 1, 2);
857
858 if (from_units.compare ("data"))
859 {
860 if (is_rectangle)
861 {
862 ColumnVector v1 = ax_xform.transform (pos(0), pos(1), 0),
863 v2 = ax_xform.transform (pos(0) + pos(2),
864 pos(1) + pos(3), 0);
865
866 retval.resize (1, 4);
867
868 retval(0) = v1(0) - ax_bbox(0) + 1;
869 retval(1) = ax_bbox(1) + ax_bbox(3) - v1(1) + 1;
870 retval(2) = v2(0) - v1(0);
871 retval(3) = v1(1) - v2(1);
872 }
873 else
874 {
875 ColumnVector v = ax_xform.transform (pos(0), pos(1), pos(2));
876
877 retval.resize (1, 3);
878
879 retval(0) = v(0) - ax_bbox(0) + 1;
880 retval(1) = ax_bbox(1) + ax_bbox(3) - v(1) + 1;
881 retval(2) = 0;
882 }
883 }
884 else
885 retval = convert_position (pos, from_units, "pixels", ax_size);
886
887 if (! to_units.compare ("pixels"))
888 {
889 if (to_units.compare ("data"))
890 {
891 if (is_rectangle)
892 {
893 ColumnVector v1, v2;
894 v1 = ax_xform.untransform (retval(0) + ax_bbox(0) - 1,
895 ax_bbox(1) + ax_bbox(3) - retval(1) + 1);
896 v2 = ax_xform.untransform (retval(0) + retval(2) + ax_bbox(0) - 1,
897 ax_bbox(1) + ax_bbox(3) - (retval(1) + retval(3)) + 1);
898
899 retval.resize (1, 4);
900
901 retval(0) = v1(0);
902 retval(1) = v1(1);
903 retval(2) = v2(0) - v1(0);
904 retval(3) = v2(1) - v1(1);
905 }
906 else
907 {
908 ColumnVector v;
909 v = ax_xform.untransform (retval(0) + ax_bbox(0) - 1,
910 ax_bbox(1) + ax_bbox(3) - retval(1) + 1);
911
912 retval.resize (1, 3);
913
914 retval(0) = v(0);
915 retval(1) = v(1);
916 retval(2) = v(2);
917 }
918 }
919 else
920 retval = convert_position (retval, "pixels", to_units, ax_size);
921 }
922 }
923
924 return retval;
925}
926
927// This function always returns the screensize in pixels
928static Matrix
930{
931 gh_manager& gh_mgr = octave::__get_gh_manager__ ("scren_size_pixels");
932
933 graphics_object obj = gh_mgr.get_object (0);
934
935 Matrix sz = obj.get ("screensize").matrix_value ();
936
937 return convert_position (sz, obj.get ("units").string_value (), "pixels",
938 sz.extract_n (0, 2, 1, 2)).extract_n (0, 2, 1, 2);
939}
940
941static double
943{
944 double retval = 1.0;
945
946 gh_manager& gh_mgr = octave::__get_gh_manager__ ("device_pixel_ratio");
947
948 graphics_object fig = gh_mgr.get_object (h).get_ancestor ("figure");
949
950 if (fig.valid_object ())
951 retval = fig.get ("__device_pixel_ratio__").double_value ();
952
953 return retval;
954}
955
956static void
957convert_cdata_2 (bool is_scaled, bool is_real, double clim_0, double clim_1,
958 const double *cmapv, double x, octave_idx_type lda,
959 octave_idx_type nc, octave_idx_type i, double *av)
960{
961 if (is_scaled)
962 x = octave::math::fix (nc * (x - clim_0) / (clim_1 - clim_0));
963 else if (is_real)
964 x = octave::math::fix (x - 1);
965
967 {
968 av[i] = x;
969 av[i+lda] = x;
970 av[i+2*lda] = x;
971 }
972 else
973 {
974 if (x < 0)
975 x = 0;
976 else if (x >= nc)
977 x = (nc - 1);
978
979 octave_idx_type idx = static_cast<octave_idx_type> (x);
980
981 av[i] = cmapv[idx];
982 av[i+lda] = cmapv[idx+nc];
983 av[i+2*lda] = cmapv[idx+2*nc];
984 }
985}
986
987template <typename T>
988void
989convert_cdata_1 (bool is_scaled, bool is_real, double clim_0, double clim_1,
990 const double *cmapv, const T *cv, octave_idx_type lda,
991 octave_idx_type nc, double *av)
992{
993 for (octave_idx_type i = 0; i < lda; i++)
994 convert_cdata_2 (is_scaled, is_real,
995 clim_0, clim_1, cmapv, cv[i], lda, nc, i, av);
996}
997
998static octave_value
999convert_cdata (const base_properties& props, const octave_value& cdata,
1000 bool is_scaled, int cdim)
1001{
1002 dim_vector dv (cdata.dims ());
1003
1004 // TrueColor data doesn't require conversion
1005 if (dv.ndims () == cdim && dv(cdim-1) == 3)
1006 return cdata;
1007
1008 Matrix cmap (1, 3, 0.0);
1009 Matrix clim (1, 2, 0.0);
1010
1011 gh_manager& gh_mgr = octave::__get_gh_manager__ ("convert_cdata");
1012
1013 graphics_object go = gh_mgr.get_object (props.get___myhandle__ ());
1014 graphics_object ax = go.get_ancestor ("axes");
1015
1016 if (ax.valid_object ())
1017 {
1018 Matrix _cmap = ax.get (caseless_str ("colormap")).matrix_value ();
1019
1020 cmap = _cmap;
1021
1022 if (is_scaled)
1023 {
1024 Matrix _clim = ax.get (caseless_str ("clim")).matrix_value ();
1025
1026 clim = _clim;
1027 }
1028 }
1029
1030 dv.resize (cdim);
1031 dv(cdim-1) = 3;
1032
1033 NDArray a (dv);
1034
1035 octave_idx_type lda = a.numel () / static_cast<octave_idx_type> (3);
1036 octave_idx_type nc = cmap.rows ();
1037
1038 double *av = a.fortran_vec ();
1039 const double *cmapv = cmap.data ();
1040
1041 double clim_0 = clim(0);
1042 double clim_1 = clim(1);
1043
1044 // FIXME: There is a lot of processing time spent just on data conversion
1045 // both here in graphics.cc and again in gl-render.cc. There must
1046 // be room for improvement! Here a macro expands to a templated
1047 // function which in turn calls another function (covert_cdata_2).
1048 // And in gl-render.cc (opengl_renderer::draw_image), only GLfloat
1049 // is supported anyways so there is another double for loop across
1050 // height and width to convert all of the input data to GLfloat.
1051
1052#define CONVERT_CDATA_1(ARRAY_T, VAL_FN, IS_REAL) \
1053 do \
1054 { \
1055 ARRAY_T tmp = cdata. VAL_FN ## array_value (); \
1056 \
1057 convert_cdata_1 (is_scaled, IS_REAL, clim_0, clim_1, cmapv, \
1058 tmp.data (), lda, nc, av); \
1059 } \
1060 while (0)
1061
1062 if (cdata.is_int8_type ())
1063 CONVERT_CDATA_1 (int8NDArray, int8_, false);
1064 else if (cdata.is_int16_type ())
1065 CONVERT_CDATA_1 (int16NDArray, int16_, false);
1066 else if (cdata.is_int32_type ())
1067 CONVERT_CDATA_1 (int32NDArray, int32_, false);
1068 else if (cdata.is_int64_type ())
1069 CONVERT_CDATA_1 (int64NDArray, int64_, false);
1070 else if (cdata.is_uint8_type ())
1071 CONVERT_CDATA_1 (uint8NDArray, uint8_, false);
1072 else if (cdata.is_uint16_type ())
1073 CONVERT_CDATA_1 (uint16NDArray, uint16_, false);
1074 else if (cdata.is_uint32_type ())
1075 CONVERT_CDATA_1 (uint32NDArray, uint32_, false);
1076 else if (cdata.is_uint64_type ())
1077 CONVERT_CDATA_1 (uint64NDArray, uint64_, false);
1078 else if (cdata.is_double_type ())
1079 CONVERT_CDATA_1 (NDArray, , true);
1080 else if (cdata.is_single_type ())
1081 CONVERT_CDATA_1 (FloatNDArray, float_, true);
1082 else if (cdata.islogical ())
1083 CONVERT_CDATA_1 (boolNDArray, bool_, false);
1084 else
1085 {
1086 // Don't throw an error; leads to an incomplete FLTK object (bug #46933).
1087 warning ("unsupported type for cdata (= %s). "
1088 "Valid types are int8, int16, int32, int64, uint8, uint16, "
1089 "uint32, uint64, double, single, and bool.",
1090 cdata.type_name ().c_str ());
1091 a = NDArray (dv, 0); // return 0 instead
1092 }
1093
1094#undef CONVERT_CDATA_1
1095
1096 return octave_value (a);
1097}
1098
1099template <typename T>
1100static void
1101get_array_limits (const Array<T>& m, double& emin, double& emax,
1102 double& eminp, double& emaxp)
1103{
1104 const T *data = m.data ();
1105 octave_idx_type n = m.numel ();
1106
1107 for (octave_idx_type i = 0; i < n; i++)
1108 {
1109 double e = double (data[i]);
1110
1111 // Don't need to test for NaN here as NaN>x and NaN<x is always false
1112 if (! octave::math::isinf (e))
1113 {
1114 if (e < emin)
1115 emin = e;
1116
1117 if (e > emax)
1118 emax = e;
1119
1120 if (e > 0 && e < eminp)
1121 eminp = e;
1122
1123 if (e < 0 && e > emaxp)
1124 emaxp = e;
1125 }
1126 }
1127}
1128
1129static bool
1131 caseless_str& rest)
1132{
1133 int len = name.length ();
1134 int offset = 0;
1135 bool result = false;
1136
1137 if (len >= 4)
1138 {
1139 caseless_str pfx = name.substr (0, 4);
1140
1141 if (pfx.compare ("axes") || pfx.compare ("line")
1142 || pfx.compare ("text"))
1143 offset = 4;
1144 else if (len >= 5)
1145 {
1146 pfx = name.substr (0, 5);
1147
1148 if (pfx.compare ("image") || pfx.compare ("patch"))
1149 offset = 5;
1150 else if (len >= 6)
1151 {
1152 pfx = name.substr (0, 6);
1153
1154 if (pfx.compare ("figure") || pfx.compare ("uimenu"))
1155 offset = 6;
1156 else if (len >= 7)
1157 {
1158 pfx = name.substr (0, 7);
1159
1160 if (pfx.compare ("surface") || pfx.compare ("scatter")
1161 || pfx.compare ("hggroup") || pfx.compare ("uipanel")
1162 || pfx.compare ("uitable"))
1163 offset = 7;
1164 else if (len >= 9)
1165 {
1166 pfx = name.substr (0, 9);
1167
1168 if (pfx.compare ("uicontrol")
1169 || pfx.compare ("uitoolbar"))
1170 offset = 9;
1171 else if (len >= 10)
1172 {
1173 pfx = name.substr (0, 10);
1174
1175 if (pfx.compare ("uipushtool"))
1176 offset = 10;
1177 else if (len >= 12)
1178 {
1179 pfx = name.substr (0, 12);
1180
1181 if (pfx.compare ("uitoggletool"))
1182 offset = 12;
1183 else if (len >= 13)
1184 {
1185 pfx = name.substr (0, 13);
1186
1187 if (pfx.compare ("uicontextmenu")
1188 || pfx.compare ("uibuttongroup"))
1189 offset = 13;
1190 }
1191 }
1192 }
1193 }
1194 }
1195 }
1196 }
1197
1198 if (offset > 0)
1199 {
1200 go_name = pfx;
1201 rest = name.substr (offset);
1202 result = true;
1203 }
1204 }
1205
1206 return result;
1207}
1208
1209static base_graphics_object *
1211 const graphics_handle& h = graphics_handle (),
1212 const graphics_handle& p = graphics_handle ())
1213{
1214 base_graphics_object *go = nullptr;
1215
1216 if (type.compare ("figure"))
1217 go = new figure (h, p);
1218 else if (type.compare ("axes"))
1219 go = new axes (h, p);
1220 else if (type.compare ("line"))
1221 go = new line (h, p);
1222 else if (type.compare ("text"))
1223 go = new text (h, p);
1224 else if (type.compare ("image"))
1225 go = new image (h, p);
1226 else if (type.compare ("light"))
1227 go = new light (h, p);
1228 else if (type.compare ("patch"))
1229 go = new patch (h, p);
1230 else if (type.compare ("scatter"))
1231 go = new scatter (h, p);
1232 else if (type.compare ("surface"))
1233 go = new surface (h, p);
1234 else if (type.compare ("hggroup"))
1235 go = new hggroup (h, p);
1236 else if (type.compare ("uimenu"))
1237 go = new uimenu (h, p);
1238 else if (type.compare ("uicontrol"))
1239 go = new uicontrol (h, p);
1240 else if (type.compare ("uipanel"))
1241 go = new uipanel (h, p);
1242 else if (type.compare ("uibuttongroup"))
1243 go = new uibuttongroup (h, p);
1244 else if (type.compare ("uicontextmenu"))
1245 go = new uicontextmenu (h, p);
1246 else if (type.compare ("uitable"))
1247 go = new uitable (h, p);
1248 else if (type.compare ("uitoolbar"))
1249 go = new uitoolbar (h, p);
1250 else if (type.compare ("uipushtool"))
1251 go = new uipushtool (h, p);
1252 else if (type.compare ("uitoggletool"))
1253 go = new uitoggletool (h, p);
1254 return go;
1255}
1256
1257// ---------------------------------------------------------------------
1258
1259bool
1260base_property::set (const octave_value& v, bool do_run, bool do_notify_toolkit)
1261{
1262 if (do_set (v))
1263 {
1264 // Notify graphics toolkit.
1265 if (m_id >= 0 && do_notify_toolkit)
1266 {
1267 gh_manager& gh_mgr
1268 = octave::__get_gh_manager__ ("base_property::set");
1269
1270 graphics_object go = gh_mgr.get_object (m_parent);
1271 if (go)
1272 go.update (m_id);
1273 }
1274
1275 // run listeners
1276 if (do_run)
1277 run_listeners (GCB_POSTSET);
1278
1279 return true;
1280 }
1281
1282 return false;
1283}
1284
1285void
1286base_property::run_listeners (listener_mode mode)
1287{
1288 const octave_value_list& l = m_listeners[mode];
1289
1290 gh_manager& gh_mgr
1291 = octave::__get_gh_manager__ ("base_property::run_listeners");
1292
1293 for (int i = 0; i < l.length (); i++)
1294 gh_mgr.execute_listener (m_parent, l(i));
1295}
1296
1297radio_values::radio_values (const std::string& opt_string)
1298 : m_default_val (), m_possible_vals ()
1299{
1300 std::size_t beg = 0;
1301 std::size_t len = opt_string.length ();
1302 bool done = len == 0;
1303
1304 while (! done)
1305 {
1306 std::size_t end = opt_string.find ('|', beg);
1307
1308 if (end == std::string::npos)
1309 {
1310 end = len;
1311 done = true;
1312 }
1313
1314 std::string t = opt_string.substr (beg, end-beg);
1315
1316 // Special case for '|' symbol itself
1317 if (t.empty () && opt_string[beg] == '|')
1318 {
1319 t = '|';
1320 end++;
1321 }
1322
1323 // Might want more error checking on parsing default value...
1324 if (t[0] == '{')
1325 {
1326 t = t.substr (1, t.length () - 2);
1327 m_default_val = t;
1328 }
1329 else if (beg == 0) // ensure default value
1330 m_default_val = t;
1331
1332 m_possible_vals.insert (t);
1333
1334 beg = end + 1;
1335 }
1336}
1337
1338std::string
1339radio_values::values_as_string (void) const
1340{
1341 std::string retval;
1342
1343 for (const auto& val : m_possible_vals)
1344 {
1345 if (retval.empty ())
1346 {
1347 if (val == default_value ())
1348 retval = '{' + val + '}';
1349 else
1350 retval = val;
1351 }
1352 else
1353 {
1354 if (val == default_value ())
1355 retval += " | {" + val + '}';
1356 else
1357 retval += " | " + val;
1358 }
1359 }
1360
1361 if (! retval.empty ())
1362 retval = "[ " + retval + " ]";
1363
1364 return retval;
1365}
1366
1367Cell
1368radio_values::values_as_cell (void) const
1369{
1370 octave_idx_type i = 0;
1371 Cell retval (nelem (), 1);
1372
1373 for (const auto& val : m_possible_vals)
1374 retval(i++) = std::string (val);
1375
1376 return retval;
1377}
1378
1379bool
1380color_values::str2rgb (const std::string& str_arg)
1381{
1382 bool retval = true;
1383
1384 double tmp_rgb[3] = {0, 0, 0};
1385
1386 std::string str = str_arg;
1387 unsigned int len = str.length ();
1388
1389 std::transform (str.begin (), str.end (), str.begin (), tolower);
1390
1391 // "blue" must precede black for Matlab compatibility
1392 if (str.compare (0, len, "blue", 0, len) == 0)
1393 tmp_rgb[2] = 1;
1394 else if (str.compare (0, len, "black", 0, len) == 0
1395 || str.compare (0, len, "k", 0, len) == 0)
1396 tmp_rgb[0] = tmp_rgb[1] = tmp_rgb[2] = 0;
1397 else if (str.compare (0, len, "red", 0, len) == 0)
1398 tmp_rgb[0] = 1;
1399 else if (str.compare (0, len, "green", 0, len) == 0)
1400 tmp_rgb[1] = 1;
1401 else if (str.compare (0, len, "yellow", 0, len) == 0)
1402 tmp_rgb[0] = tmp_rgb[1] = 1;
1403 else if (str.compare (0, len, "magenta", 0, len) == 0)
1404 tmp_rgb[0] = tmp_rgb[2] = 1;
1405 else if (str.compare (0, len, "cyan", 0, len) == 0)
1406 tmp_rgb[1] = tmp_rgb[2] = 1;
1407 else if (str.compare (0, len, "white", 0, len) == 0
1408 || str.compare (0, len, "w", 0, len) == 0)
1409 tmp_rgb[0] = tmp_rgb[1] = tmp_rgb[2] = 1;
1410 else if (str[0] == '#' && len == 7)
1411 {
1412 try
1413 {
1414 tmp_rgb[0] = static_cast<double> (stoi (str.substr (1, 2), nullptr, 16))
1415 / 255.0;
1416 tmp_rgb[1] = static_cast<double> (stoi (str.substr (3, 2), nullptr, 16))
1417 / 255.0;
1418 tmp_rgb[2] = static_cast<double> (stoi (str.substr (5, 2), nullptr, 16))
1419 / 255.0;
1420 }
1421 catch (const octave::execution_exception&)
1422 {
1423 retval = false;
1424 }
1425 }
1426 else if (str[0] == '#' && len == 4)
1427 {
1428 try
1429 {
1430 tmp_rgb[0] = static_cast<double> (stoi (str.substr (1, 1), nullptr, 16))
1431 / 15.0;
1432 tmp_rgb[1] = static_cast<double> (stoi (str.substr (2, 1), nullptr, 16))
1433 / 15.0;
1434 tmp_rgb[2] = static_cast<double> (stoi (str.substr (3, 1), nullptr, 16))
1435 / 15.0;
1436 }
1437 catch (const octave::execution_exception&)
1438 {
1439 retval = false;
1440 }
1441 }
1442 else
1443 retval = false;
1444
1445 if (retval)
1446 {
1447 for (int i = 0; i < 3; i++)
1448 m_rgb(i) = tmp_rgb[i];
1449 }
1450
1451 return retval;
1452}
1453
1454bool
1455color_property::do_set (const octave_value& val)
1456{
1457 if (val.is_string ())
1458 {
1459 std::string s = val.string_value ();
1460
1461 if (s.empty ())
1462 error (R"(invalid value for color property "%s")",
1463 get_name ().c_str ());
1464
1465 std::string match;
1466
1467 if (m_radio_val.contains (s, match))
1468 {
1469 if (m_current_type != radio_t || match != m_current_val)
1470 {
1471 if (s.length () != match.length ())
1472 warning_with_id ("Octave:abbreviated-property-match",
1473 "%s: allowing %s to match %s value %s",
1474 "set", s.c_str (), get_name ().c_str (),
1475 match.c_str ());
1476 m_current_val = match;
1477 m_current_type = radio_t;
1478 return true;
1479 }
1480 }
1481 else
1482 {
1483 try
1484 {
1485 color_values col (s);
1486
1487 if (m_current_type != color_t || col != m_color_val)
1488 {
1489 m_color_val = col;
1490 m_current_type = color_t;
1491 return true;
1492 }
1493 }
1494 catch (octave::execution_exception& ee)
1495 {
1496 error (ee, R"(invalid value for color property "%s" (value = %s))",
1497 get_name ().c_str (), s.c_str ());
1498 }
1499 }
1500 }
1501 else if (val.isnumeric ())
1502 {
1503 Matrix m = val.matrix_value ();
1504
1505 if (m.numel () != 3)
1506 error (R"(invalid value for color property "%s")",
1507 get_name ().c_str ());
1508
1509 color_values col (m(0), m(1), m(2));
1510
1511 if (m_current_type != color_t || col != m_color_val)
1512 {
1513 m_color_val = col;
1514 m_current_type = color_t;
1515 return true;
1516 }
1517 }
1518 else
1519 error (R"(invalid value for color property "%s")",
1520 get_name ().c_str ());
1521
1522 return false;
1523}
1524
1525bool
1526double_radio_property::do_set (const octave_value& val)
1527{
1528 if (val.is_string ())
1529 {
1530 std::string s = val.string_value ();
1531 std::string match;
1532
1533 if (s.empty () || ! m_radio_val.contains (s, match))
1534 error (R"(invalid value for double_radio property "%s")",
1535 get_name ().c_str ());
1536
1537 if (m_current_type != radio_t || match != m_current_val)
1538 {
1539 if (s.length () != match.length ())
1540 warning_with_id ("Octave:abbreviated-property-match",
1541 "%s: allowing %s to match %s value %s",
1542 "set", s.c_str (), get_name ().c_str (),
1543 match.c_str ());
1544 m_current_val = match;
1545 m_current_type = radio_t;
1546 return true;
1547 }
1548 }
1549 else if (val.is_scalar_type () && val.isreal ())
1550 {
1551 double new_dval = val.double_value ();
1552
1553 if (m_current_type != double_t || new_dval != m_dval)
1554 {
1555 m_dval = new_dval;
1556 m_current_type = double_t;
1557 return true;
1558 }
1559 }
1560 else
1561 error (R"(invalid value for double_radio property "%s")",
1562 get_name ().c_str ());
1563
1564 return false;
1565}
1566
1567bool
1568array_property::validate (const octave_value& v)
1569{
1570 bool xok = false;
1571
1572 // check value type
1573 if (m_type_constraints.size () > 0)
1574 {
1575 if (m_type_constraints.find (v.class_name ()) != m_type_constraints.end ())
1576 xok = true;
1577
1578 // check if complex is allowed (it's also of class "double", so
1579 // checking that alone is not enough to ensure real type)
1580 if (m_type_constraints.find ("real") != m_type_constraints.end ()
1581 && v.iscomplex ())
1582 xok = false;
1583 }
1584 else
1585 xok = v.isnumeric () || v.is_bool_scalar ();
1586
1587 if (xok && m_size_constraints.size () > 0)
1588 {
1589 dim_vector vdims = v.dims ();
1590 int vlen = vdims.ndims ();
1591
1592 xok = false;
1593
1594 // check dimensional size constraints until a match is found
1595 for (auto it = m_size_constraints.cbegin ();
1596 ! xok && it != m_size_constraints.cend ();
1597 ++it)
1598 {
1599 dim_vector itdims = (*it);
1600
1601 if (itdims.ndims () == vlen)
1602 {
1603 xok = true;
1604
1605 for (int i = 0; xok && i < vlen; i++)
1606 {
1607 if (itdims(i) > 0)
1608 {
1609 if (itdims(i) != vdims(i))
1610 xok = false;
1611 }
1612 else if (itdims(i) == 0)
1613 {
1614 if (! v.isempty ())
1615 xok = false;
1616 break;
1617 }
1618 }
1619 }
1620 }
1621 }
1622
1623 if (xok)
1624 {
1625 NDArray v_mat = v.array_value ();
1626 // Check min and max
1627 if (! octave::math::isnan (m_minval.first))
1628 {
1629 for (octave_idx_type i = 0; i < v_mat.numel (); i++)
1630 if (m_minval.second && m_minval.first > v_mat(i))
1631 error (R"(set: "%s" must be greater than or equal to %g)",
1632 get_name ().c_str (), m_minval.first);
1633 else if (! m_minval.second && m_minval.first >= v_mat(i))
1634 error (R"(set: "%s" must be greater than %g)",
1635 get_name ().c_str (), m_minval.first);
1636 }
1637
1638 if (! octave::math::isnan (m_maxval.first))
1639 {
1640 for (octave_idx_type i = 0; i < v_mat.numel (); i++)
1641 if (m_maxval.second && m_maxval.first < v_mat(i))
1642 error (R"(set: "%s" must be less than or equal to %g)",
1643 get_name ().c_str (), m_maxval.first);
1644 else if (! m_maxval.second && m_maxval.first <= v_mat(i))
1645 error (R"(set: "%s" must be less than %g)",
1646 get_name ().c_str (), m_maxval.first);
1647 }
1648
1649 if (m_finite_constraint == NO_CHECK) { /* do nothing */ }
1650 else if (m_finite_constraint == FINITE)
1651 {
1652 for (octave_idx_type i = 0; i < v_mat.numel (); i++)
1653 if (! octave::math::isfinite (v_mat(i)))
1654 error (R"(set: "%s" must be finite)", get_name ().c_str ());
1655 }
1656 else if (m_finite_constraint == NOT_NAN)
1657 {
1658 for (octave_idx_type i = 0; i < v_mat.numel (); i++)
1659 if (octave::math::isnan (v_mat(i)))
1660 error (R"(set: "%s" must not be nan)", get_name ().c_str ());
1661 }
1662 else if (m_finite_constraint == NOT_INF)
1663 {
1664 for (octave_idx_type i = 0; i < v_mat.numel (); i++)
1665 if (octave::math::isinf (v_mat(i)))
1666 error (R"(set: "%s" must not be infinite)", get_name ().c_str ());
1667 }
1668
1669 }
1670
1671 return xok;
1672}
1673
1674bool
1675array_property::is_equal (const octave_value& v) const
1676{
1677 if (m_data.type_name () == v.type_name ())
1678 {
1679 if (m_data.dims () == v.dims ())
1680 {
1681
1682#define CHECK_ARRAY_EQUAL(T, F, A) \
1683 { \
1684 if (m_data.numel () == 1) \
1685 return m_data.F ## scalar_value () == \
1686 v.F ## scalar_value (); \
1687 else \
1688 { \
1689 /* Keep copy of array_value to allow */ \
1690 /* sparse/bool arrays that are converted, to */ \
1691 /* not be deallocated early */ \
1692 const A m1 = m_data.F ## array_value (); \
1693 const T *d1 = m1.data (); \
1694 const A m2 = v.F ## array_value (); \
1695 const T *d2 = m2.data (); \
1696 \
1697 bool flag = true; \
1698 \
1699 for (int i = 0; flag && i < m_data.numel (); i++) \
1700 if (d1[i] != d2[i]) \
1701 flag = false; \
1702 \
1703 return flag; \
1704 } \
1705 }
1706
1707 if (m_data.is_double_type () || m_data.islogical ())
1708 CHECK_ARRAY_EQUAL (double, , NDArray)
1709 else if (m_data.is_single_type ())
1710 CHECK_ARRAY_EQUAL (float, float_, FloatNDArray)
1711 else if (m_data.is_int8_type ())
1713 else if (m_data.is_int16_type ())
1715 else if (m_data.is_int32_type ())
1717 else if (m_data.is_int64_type ())
1719 else if (m_data.is_uint8_type ())
1721 else if (m_data.is_uint16_type ())
1723 else if (m_data.is_uint32_type ())
1725 else if (m_data.is_uint64_type ())
1727 }
1728 }
1729
1730 return false;
1731}
1732
1733void
1734array_property::get_data_limits (void)
1735{
1736 m_min_val = m_min_pos = octave::numeric_limits<double>::Inf ();
1737 m_max_val = m_max_neg = -octave::numeric_limits<double>::Inf ();
1738
1739 if (! m_data.isempty ())
1740 {
1741 if (m_data.isinteger ())
1742 {
1743 if (m_data.is_int8_type ())
1744 get_array_limits (m_data.int8_array_value (),
1745 m_min_val, m_max_val, m_min_pos, m_max_neg);
1746 else if (m_data.is_uint8_type ())
1747 get_array_limits (m_data.uint8_array_value (),
1748 m_min_val, m_max_val, m_min_pos, m_max_neg);
1749 else if (m_data.is_int16_type ())
1750 get_array_limits (m_data.int16_array_value (),
1751 m_min_val, m_max_val, m_min_pos, m_max_neg);
1752 else if (m_data.is_uint16_type ())
1753 get_array_limits (m_data.uint16_array_value (),
1754 m_min_val, m_max_val, m_min_pos, m_max_neg);
1755 else if (m_data.is_int32_type ())
1756 get_array_limits (m_data.int32_array_value (),
1757 m_min_val, m_max_val, m_min_pos, m_max_neg);
1758 else if (m_data.is_uint32_type ())
1759 get_array_limits (m_data.uint32_array_value (),
1760 m_min_val, m_max_val, m_min_pos, m_max_neg);
1761 else if (m_data.is_int64_type ())
1762 get_array_limits (m_data.int64_array_value (),
1763 m_min_val, m_max_val, m_min_pos, m_max_neg);
1764 else if (m_data.is_uint64_type ())
1765 get_array_limits (m_data.uint64_array_value (),
1766 m_min_val, m_max_val, m_min_pos, m_max_neg);
1767 }
1768 else
1769 get_array_limits (m_data.array_value (),
1770 m_min_val, m_max_val, m_min_pos, m_max_neg);
1771 }
1772}
1773
1774bool
1775handle_property::do_set (const octave_value& v)
1776{
1777 // Users may want to use empty matrix to reset a handle property
1778 if (v.isempty ())
1779 {
1780 if (! get ().isempty ())
1781 {
1782 m_current_val = graphics_handle ();
1783 return true;
1784 }
1785 else
1786 return false;
1787 }
1788
1789 double dv = v.xdouble_value (R"(set: invalid graphics handle for property "%s")",
1790 get_name ().c_str ());
1791
1792 gh_manager& gh_mgr = octave::__get_gh_manager__ ("handle_property::do_set");
1793
1794 graphics_handle gh = gh_mgr.lookup (dv);
1795
1796 // Check the object type if necessary
1797 bool type_ok = true;
1798 if (gh.ok () && ! m_type_constraints.empty ())
1799 {
1800 type_ok = false;
1801 graphics_object obj = gh_mgr.get_object (gh);
1802
1803 for (const auto& type : m_type_constraints)
1804 if (obj.isa (type))
1805 {
1806 type_ok = true;
1807 break;
1808 }
1809 }
1810
1811 if (! octave::math::isnan (gh.value ()) && ! (gh.ok () && type_ok))
1812 {
1813 if (type_ok)
1814 error (R"(set: invalid graphics handle (= %g) for property "%s")",
1815 dv, get_name ().c_str ());
1816 else
1817 error (R"(set: invalid graphics object type for property "%s")",
1818 get_name ().c_str ());
1819 }
1820
1821 if (m_current_val != gh)
1822 {
1823 m_current_val = gh;
1824 return true;
1825 }
1826
1827 return false;
1828}
1829
1830/*
1831## Test validation of contextmenu property
1832%!test
1833%! hf = figure ("visible", "off");
1834%! unwind_protect
1835%! hax = axes ("parent", hf);
1836%! hpa = patch ("parent", hax);
1837%! try
1838%! set (hax, "contextmenu", hpa);
1839%! catch
1840%! err = lasterr ();
1841%! end_try_catch
1842%! assert (err, 'set: invalid graphics object type for property "contextmenu"');
1843%! unwind_protect_cleanup
1844%! delete (hf);
1845%! end_unwind_protect
1846*/
1847
1848Matrix
1849children_property::do_get_children (bool return_hidden) const
1850{
1851 Matrix retval (m_children_list.size (), 1);
1852 octave_idx_type k = 0;
1853
1854 gh_manager& gh_mgr
1855 = octave::__get_gh_manager__ ("children_property::do_get_children");
1856
1857 graphics_object go = gh_mgr.get_object (0);
1858
1860 = dynamic_cast<root_figure::properties&> (go.get_properties ());
1861
1862 if (! props.is_showhiddenhandles ())
1863 {
1864 for (const auto& hchild : m_children_list)
1865 {
1866 graphics_handle kid = hchild;
1867
1868 if (gh_mgr.is_handle_visible (kid))
1869 {
1870 if (! return_hidden)
1871 retval(k++) = hchild;
1872 }
1873 else if (return_hidden)
1874 retval(k++) = hchild;
1875 }
1876
1877 retval.resize (k, 1);
1878 }
1879 else
1880 {
1881 for (const auto& hchild : m_children_list)
1882 retval(k++) = hchild;
1883 }
1884
1885 return retval;
1886}
1887
1888void
1889children_property::do_delete_children (bool clear, bool from_root)
1890{
1891 gh_manager& gh_mgr
1892 = octave::__get_gh_manager__ ("children_property::do_delete_children");
1893
1894 if (from_root)
1895 {
1896 for (graphics_handle hchild : m_children_list)
1897 {
1898 graphics_object go = gh_mgr.get_object (hchild);
1899 if (go.valid_object ()
1900 && ! go.get_properties ().is_beingdeleted ())
1901 gh_mgr.free (hchild, from_root);
1902 }
1903 m_children_list.clear ();
1904 }
1905 else
1906 while (! m_children_list.empty ())
1907 {
1908 // gh_mgr.free removes hchild from children_list
1909 graphics_handle hchild = m_children_list.front ();
1910 graphics_object go = gh_mgr.get_object (hchild);
1911 if (go.valid_object ()
1912 && ! go.get_properties ().is_beingdeleted ())
1913 gh_mgr.free (hchild, from_root);
1914 }
1915
1916 // FIXME: children_list should be clear anyway at this point.
1917 if (clear)
1918 m_children_list.clear ();
1919}
1920
1921bool
1922callback_property::validate (const octave_value& v) const
1923{
1924 // case 1: empty matrix
1925 // case 2: function handle
1926 // case 3: string corresponding to known function name
1927 // case 4: string that can be eval()'ed
1928 // case 5: cell array with first element being a function handle
1929
1930 if (v.isempty ())
1931 return true;
1932 else if (v.is_function_handle ())
1933 return true;
1934 else if (v.is_string ())
1935 // complete validation will be done at execution-time
1936 return true;
1937 else if (v.iscell () && (v.rows () == 1 || v.columns () == 1)
1938 && v.cell_value ()(0).is_function_handle ())
1939 return true;
1940
1941 return false;
1942}
1943
1945{
1946public:
1947
1948 callback_props (void) : m_set () { }
1949
1951
1953
1954 ~callback_props (void) = default;
1955
1956 bool empty (void) const { return m_set.empty (); }
1957
1958 void insert (const callback_property *ptr)
1959 {
1960 m_set.insert (reinterpret_cast<intptr_t> (ptr));
1961 }
1962
1963 void erase (const callback_property *ptr)
1964 {
1965 m_set.erase (reinterpret_cast<intptr_t> (ptr));
1966 }
1967
1968 bool contains (const callback_property *ptr) const
1969 {
1970 return m_set.find (reinterpret_cast<intptr_t> (ptr)) != m_set.end ();
1971 }
1972
1973private:
1974
1975 std::set<intptr_t> m_set;
1976};
1977
1978// Elements of this set are pointers to currently executing
1979// callback_property objects. Used to determine handle visibility
1980// inside callback functions.
1981
1983
1984void
1985callback_property::execute (const octave_value& data) const
1986{
1987 // We are executing a callback function, so allow handles that have
1988 // their handlevisibility property set to "callback" to be visible.
1989
1990 octave::unwind_action executing_callbacks_cleanup
1991 ([=] () { executing_callbacks.erase (this); });
1992
1993 if (! executing_callbacks.contains (this))
1994 {
1996
1997 if (m_callback.is_defined () && ! m_callback.isempty ())
1998 {
1999 gh_manager& gh_mgr
2000 = octave::__get_gh_manager__ ("callback_property::execute");
2001
2002 gh_mgr.execute_callback (get_parent (), m_callback, data);
2003 }
2004 }
2005}
2006
2007// Used to cache dummy graphics objects from which dynamic properties can be
2008// cloned.
2009static std::map<caseless_str, graphics_object> dprop_obj_map;
2010
2011property
2012property::create (const std::string& name, const graphics_handle& h,
2013 const caseless_str& type, const octave_value_list& args)
2014{
2015 property retval;
2016
2017 if (type.compare ("string"))
2018 {
2019 std::string sv = (args.length () > 0 ? args(0).string_value () : "");
2020
2021 retval = property (new string_property (name, h, sv));
2022 }
2023 else if (type.compare ("any"))
2024 {
2025 octave_value ov = (args.length () > 0 ? args(0)
2026 : octave_value (Matrix ()));
2027
2028 retval = property (new any_property (name, h, ov));
2029 }
2030 else if (type.compare ("radio"))
2031 {
2032 if (args.length () < 1)
2033 error ("addproperty: missing possible values for radio property");
2034
2035 std::string sv = args(0).xstring_value ("addproperty: argument for radio property must be a string");
2036
2037 retval = property (new radio_property (name, h, sv));
2038
2039 if (args.length () > 1)
2040 retval.set (args(1));
2041 }
2042 else if (type.compare ("double"))
2043 {
2044 double dv = (args.length () > 0 ? args(0).double_value () : 0.0);
2045
2046 retval = property (new double_property (name, h, dv));
2047 }
2048 else if (type.compare ("handle"))
2049 {
2050 double hv = (args.length () > 0 ? args(0).double_value ()
2052
2053 graphics_handle gh (hv);
2054
2055 retval = property (new handle_property (name, h, gh));
2056 }
2057 else if (type.compare ("boolean"))
2058 {
2059 retval = property (new bool_property (name, h, false));
2060
2061 if (args.length () > 0)
2062 retval.set (args(0));
2063 }
2064 else if (type.compare ("data"))
2065 {
2066 retval = property (new array_property (name, h, Matrix ()));
2067
2068 if (args.length () > 0)
2069 {
2070 retval.set (args(0));
2071 // FIXME: additional argument could define constraints,
2072 // but is this really useful?
2073 }
2074 }
2075 else if (type.compare ("color"))
2076 {
2077 color_values cv (0, 0, 0);
2078 radio_values rv;
2079
2080 if (args.length () > 1)
2081 rv = radio_values (args(1).string_value ());
2082
2083 retval = property (new color_property (name, h, cv, rv));
2084
2085 if (args.length () > 0 && ! args(0).isempty ())
2086 retval.set (args(0));
2087 else
2088 retval.set (rv.default_value ());
2089 }
2090 else
2091 {
2092 caseless_str go_name, go_rest;
2093
2094 if (! lookup_object_name (type, go_name, go_rest))
2095 error ("addproperty: unsupported type for dynamic property (= %s)",
2096 type.c_str ());
2097
2098 graphics_object go;
2099
2100 std::map<caseless_str, graphics_object>::const_iterator it
2101 = dprop_obj_map.find (go_name);
2102
2103 if (it == dprop_obj_map.end ())
2104 {
2105 base_graphics_object *bgo = make_graphics_object_from_type (go_name);
2106
2107 if (bgo)
2108 {
2109 go = graphics_object (bgo);
2110
2111 dprop_obj_map[go_name] = go;
2112 }
2113 }
2114 else
2115 go = it->second;
2116
2117 if (! go.valid_object ())
2118 error ("addproperty: invalid object type (= %s)",
2119 go_name.c_str ());
2120
2121 property prop = go.get_properties ().get_property (go_rest);
2122
2123 retval = prop.clone ();
2124
2125 retval.set_parent (h);
2126 retval.set_name (name);
2127
2128 if (args.length () > 0)
2129 retval.set (args(0));
2130 }
2131
2132 return retval;
2133}
2134
2135static void
2137{
2138 gh_manager& gh_mgr = octave::__get_gh_manager__ ("finalize_r");
2139
2140 graphics_object go = gh_mgr.get_object (h);
2141
2142 if (go)
2143 {
2144 Matrix children = go.get_properties ().get_all_children ();
2145
2146 for (int k = 0; k < children.numel (); k++)
2147 finalize_r (children(k));
2148
2149 go.finalize ();
2150 }
2151}
2152
2153static void
2155{
2156 gh_manager& gh_mgr = octave::__get_gh_manager__ ("initialize_r");
2157
2158 graphics_object go = gh_mgr.get_object (h);
2159
2160 if (go)
2161 {
2162 Matrix children = go.get_properties ().get_all_children ();
2163
2164 go.initialize ();
2165
2166 for (int k = 0; k < children.numel (); k++)
2167 initialize_r (children(k));
2168 }
2169}
2170
2171void
2172figure::properties::set_toolkit (const octave::graphics_toolkit& b)
2173{
2174 if (m_toolkit)
2175 finalize_r (get___myhandle__ ());
2176
2177 m_toolkit = b;
2178 m___graphics_toolkit__ = b.get_name ();
2179 m___plot_stream__ = Matrix ();
2180
2181 if (m_toolkit)
2182 initialize_r (get___myhandle__ ());
2183
2184 mark_modified ();
2185}
2186
2187void
2188figure::properties::set___mouse_mode__ (const octave_value& val_arg)
2189{
2190 std::string direction = "in";
2191
2192 octave_value val = val_arg;
2193
2194 if (val.is_string ())
2195 {
2196 std::string modestr = val.string_value ();
2197
2198 if (modestr == "zoom in")
2199 {
2200 val = modestr = "zoom";
2201 direction = "in";
2202 }
2203 else if (modestr == "zoom out")
2204 {
2205 val = modestr = "zoom";
2206 direction = "out";
2207 }
2208
2209 if (m___mouse_mode__.set (val, true))
2210 {
2211 std::string mode = m___mouse_mode__.current_value ();
2212
2213 octave_scalar_map pm = get___pan_mode__ ().scalar_map_value ();
2214 pm.setfield ("Enable", mode == "pan" ? "on" : "off");
2215 set___pan_mode__ (pm);
2216
2217 octave_scalar_map rm = get___rotate_mode__ ().scalar_map_value ();
2218 rm.setfield ("Enable", mode == "rotate" ? "on" : "off");
2219 set___rotate_mode__ (rm);
2220
2221 octave_scalar_map zm = get___zoom_mode__ ().scalar_map_value ();
2222 zm.setfield ("Enable", mode == "zoom" ? "on" : "off");
2223 zm.setfield ("Direction", direction);
2224 set___zoom_mode__ (zm);
2225
2226 mark_modified ();
2227 }
2228 else if (modestr == "zoom")
2229 {
2230 octave_scalar_map zm = get___zoom_mode__ ().scalar_map_value ();
2231 std::string curr_direction
2232 = zm.getfield ("Direction").string_value ();
2233
2234 if (direction != curr_direction)
2235 {
2236 zm.setfield ("Direction", direction);
2237 set___zoom_mode__ (zm);
2238
2239 mark_modified ();
2240 }
2241 }
2242 }
2243}
2244
2245void
2246figure::properties::update_handlevisibility (void)
2247{
2248 if (! is_handle_visible ())
2249 {
2250 gh_manager& gh_mgr
2251 = octave::__get_gh_manager__ ("figure::properties::update_handlevisibility");
2252
2253 octave_value cf = gh_mgr.get_object (0).get ("currentfigure");
2254
2255 if (! cf.isempty () && cf.double_value () == m___myhandle__)
2256 {
2257 octave::autolock guard (gh_mgr.graphics_lock ());
2258
2259 octave_value kids = gh_mgr.get_object (0).get ("children");
2260
2261 if (kids.isempty ())
2262 gh_mgr.get_object (0).set ("currentfigure", Matrix ());
2263 else
2264 {
2265 NDArray kidsarray = kids.array_value ();
2266 gh_mgr.get_object (0).set ("currentfigure", kidsarray(0));
2267 }
2268 }
2269 }
2270
2271 base_properties::update_handlevisibility ();
2272}
2273
2274static void
2276{
2277 gh_manager& gh_mgr = octave::__get_gh_manager__ ("update_text_pos");
2278
2279 graphics_object go = gh_mgr.get_object (h);
2280
2281 if (go.isa ("text"))
2282 {
2284 = dynamic_cast<text::properties&> (go.get_properties ());
2285 tp.update_font ();
2286 tp.update_text_extent ();
2287 }
2288 else if (go.isa ("figure") || go.isa ("uipanel") || go.isa ("axes")
2289 || go.isa ("hggroup"))
2290 {
2291 Matrix ch = go.get_properties ().get_all_children ();
2292 for (octave_idx_type ii = 0; ii < ch.numel (); ii++)
2294
2295 if (go.isa ("axes"))
2296 {
2298 = dynamic_cast<axes::properties&> (go.get_properties ());
2299 ap.update_font ();
2300 ap.sync_positions ();
2301 }
2302 }
2303}
2304
2305void
2306figure::properties::update___device_pixel_ratio__ (void)
2307{
2308 update_text_pos (get___myhandle__ ());
2309}
2310
2311// ---------------------------------------------------------------------
2312
2313void
2314property_list::set (const caseless_str& name, const octave_value& val)
2315{
2316 std::size_t offset = 0;
2317
2318 std::size_t len = name.length ();
2319
2320 if (len > 4)
2321 {
2322 caseless_str pfx = name.substr (0, 4);
2323
2324 if (pfx.compare ("axes") || pfx.compare ("line")
2325 || pfx.compare ("text"))
2326 offset = 4;
2327 else if (len > 5)
2328 {
2329 pfx = name.substr (0, 5);
2330
2331 if (pfx.compare ("image") || pfx.compare ("patch"))
2332 offset = 5;
2333 else if (len > 6)
2334 {
2335 pfx = name.substr (0, 6);
2336
2337 if (pfx.compare ("figure") || pfx.compare ("uimenu"))
2338 offset = 6;
2339 else if (len > 7)
2340 {
2341 pfx = name.substr (0, 7);
2342
2343 if (pfx.compare ("surface") || pfx.compare ("scatter")
2344 || pfx.compare ("hggroup")|| pfx.compare ("uipanel")
2345 || pfx.compare ("uitable"))
2346 offset = 7;
2347 else if (len > 9)
2348 {
2349 pfx = name.substr (0, 9);
2350
2351 if (pfx.compare ("uicontrol")
2352 || pfx.compare ("uitoolbar"))
2353 offset = 9;
2354 else if (len > 10)
2355 {
2356 pfx = name.substr (0, 10);
2357
2358 if (pfx.compare ("uipushtool"))
2359 offset = 10;
2360 else if (len > 12)
2361 {
2362 pfx = name.substr (0, 12);
2363
2364 if (pfx.compare ("uitoogletool"))
2365 offset = 12;
2366 else if (len > 13)
2367 {
2368 pfx = name.substr (0, 13);
2369
2370 if (pfx.compare ("uicontextmenu")
2371 || pfx.compare ("uibuttongroup"))
2372 offset = 13;
2373 }
2374 }
2375 }
2376 }
2377 }
2378 }
2379 }
2380
2381 if (offset > 0)
2382 {
2383 // FIXME: should we validate property names and values here?
2384
2385 std::string pname = name.substr (offset);
2386
2387 std::transform (pfx.begin (), pfx.end (), pfx.begin (), tolower);
2388 std::transform (pname.begin (), pname.end (), pname.begin (),
2389 tolower);
2390
2391 bool has_property = false;
2392 if (pfx == "axes")
2393 has_property = axes::properties::has_core_property (pname);
2394 else if (pfx == "figure")
2395 has_property = figure::properties::has_core_property (pname);
2396 else if (pfx == "line")
2397 has_property = line::properties::has_core_property (pname);
2398 else if (pfx == "text")
2399 has_property = text::properties::has_core_property (pname);
2400 else if (pfx == "image")
2401 has_property = image::properties::has_core_property (pname);
2402 else if (pfx == "patch")
2403 has_property = patch::properties::has_core_property (pname);
2404 else if (pfx == "scatter")
2405 has_property = scatter::properties::has_core_property (pname);
2406 else if (pfx == "surface")
2407 has_property = surface::properties::has_core_property (pname);
2408 else if (pfx == "hggroup")
2409 has_property = hggroup::properties::has_core_property (pname);
2410 else if (pfx == "uimenu")
2411 has_property = uimenu::properties::has_core_property (pname);
2412 else if (pfx == "uicontrol")
2413 has_property = uicontrol::properties::has_core_property (pname);
2414 else if (pfx == "uibuttongroup")
2415 has_property = uibuttongroup::properties::has_core_property (pname);
2416 else if (pfx == "uipanel")
2417 has_property = uipanel::properties::has_core_property (pname);
2418 else if (pfx == "uicontextmenu")
2419 has_property = uicontextmenu::properties::has_core_property (pname);
2420 else if (pfx == "uitable")
2421 has_property = uitable::properties::has_core_property (pname);
2422 else if (pfx == "uitoolbar")
2423 has_property = uitoolbar::properties::has_core_property (pname);
2424 else if (pfx == "uipushtool")
2425 has_property = uipushtool::properties::has_core_property (pname);
2426
2427 if (! has_property)
2428 error ("invalid %s property '%s'", pfx.c_str (), pname.c_str ());
2429
2430 bool remove = false;
2431 if (val.is_string ())
2432 {
2433 std::string sval = val.string_value ();
2434
2435 remove = (sval == "remove");
2436 }
2437
2438 pval_map_type& pval_map = m_plist_map[pfx];
2439
2440 if (remove)
2441 {
2442 auto p = pval_map.find (pname);
2443
2444 if (p != pval_map.end ())
2445 pval_map.erase (p);
2446 }
2447 else
2448 pval_map[pname] = val;
2449 }
2450 }
2451
2452 if (offset == 0)
2453 error ("invalid default property specification");
2454}
2455
2458{
2459 octave_value retval;
2460
2461 std::size_t offset = 0;
2462
2463 std::size_t len = name.length ();
2464
2465 if (len > 4)
2466 {
2467 caseless_str pfx = name.substr (0, 4);
2468
2469 if (pfx.compare ("axes") || pfx.compare ("line")
2470 || pfx.compare ("text"))
2471 offset = 4;
2472 else if (len > 5)
2473 {
2474 pfx = name.substr (0, 5);
2475
2476 if (pfx.compare ("image") || pfx.compare ("patch"))
2477 offset = 5;
2478 else if (len > 6)
2479 {
2480 pfx = name.substr (0, 6);
2481
2482 if (pfx.compare ("figure") || pfx.compare ("uimenu"))
2483 offset = 6;
2484 else if (len > 7)
2485 {
2486 pfx = name.substr (0, 7);
2487
2488 if (pfx.compare ("surface") || pfx.compare ("scatter")
2489 || pfx.compare ("hggroup") || pfx.compare ("uipanel")
2490 || pfx.compare ("uitable"))
2491 offset = 7;
2492 else if (len > 9)
2493 {
2494 pfx = name.substr (0, 9);
2495
2496 if (pfx.compare ("uicontrol")
2497 || pfx.compare ("uitoolbar"))
2498 offset = 9;
2499 else if (len > 10)
2500 {
2501 pfx = name.substr (0, 10);
2502
2503 if (pfx.compare ("uipushtool"))
2504 offset = 10;
2505 else if (len > 12)
2506 {
2507 pfx = name.substr (0, 12);
2508
2509 if (pfx.compare ("uitoggletool"))
2510 offset = 12;
2511 else if (len > 13)
2512 {
2513 pfx = name.substr (0, 13);
2514
2515 if (pfx.compare ("uicontextmenu")
2516 || pfx.compare ("uibuttongroup"))
2517 offset = 13;
2518 }
2519 }
2520 }
2521 }
2522 }
2523 }
2524 }
2525
2526 if (offset > 0)
2527 {
2528 std::string pname = name.substr (offset);
2529
2530 std::transform (pfx.begin (), pfx.end (), pfx.begin (), tolower);
2531 std::transform (pname.begin (), pname.end (), pname.begin (),
2532 tolower);
2533
2534 plist_map_const_iterator p = find (pfx);
2535
2536 if (p != end ())
2537 {
2538 const pval_map_type& pval_map = p->second;
2539
2540 pval_map_const_iterator q = pval_map.find (pname);
2541
2542 if (q != pval_map.end ())
2543 retval = q->second;
2544 }
2545 }
2546 }
2547
2548 return retval;
2549}
2550
2552property_list::as_struct (const std::string& prefix_arg) const
2553{
2555
2556 for (auto p = begin (); p != end (); p++)
2557 {
2558 std::string prefix = prefix_arg + p->first;
2559
2560 for (const auto& prop_val : p->second)
2561 m.assign (prefix + prop_val.first, prop_val.second);
2562 }
2563
2564 return m;
2565}
2566
2567// Set properties given as a cs-list of name, value pairs.
2568
2569void
2570graphics_object::set (const octave_value_list& args)
2571{
2572 int nargin = args.length ();
2573
2574 if (nargin == 0)
2575 error ("graphics_object::set: Nothing to set");
2576
2577 for (int i = 0; i < nargin; )
2578 {
2579 if (args(i).isstruct () )
2580 {
2581 set (args(i).map_value ());
2582 i++;
2583 }
2584 else if (i < nargin - 1)
2585 {
2586 caseless_str pname = args(i).xstring_value ("set: argument %d must be a property name", i);
2587 octave_value val = args(i+1);
2588 set_value_or_default (pname, val);
2589 i += 2;
2590 }
2591 else
2592 error ("set: invalid number of arguments");
2593 }
2594}
2595
2596/*
2597## test set with name, value pairs
2598%!test
2599%! hf = figure ("visible", "off");
2600%! h = plot (1:10, 10:-1:1);
2601%! set (h, "linewidth", 10, "marker", "x");
2602%! lw = get (h, "linewidth");
2603%! mk = get (h, "marker");
2604%! close (hf);
2605%! assert (lw, 10);
2606%! assert (mk, "x");
2607*/
2608
2609// Set properties given in two cell arrays containing names and values.
2610void
2611graphics_object::set (const Array<std::string>& pnames,
2612 const Cell& values, octave_idx_type row)
2613{
2614 if (pnames.numel () != values.columns ())
2615 error ("set: number of names must match number of value columns "
2616 "(%" OCTAVE_IDX_TYPE_FORMAT " != %" OCTAVE_IDX_TYPE_FORMAT ")",
2617 pnames.numel (), values.columns ());
2618
2619 octave_idx_type k = pnames.columns ();
2620
2621 for (octave_idx_type column = 0; column < k; column++)
2622 {
2623 caseless_str pname = pnames(column);
2624 octave_value val = values(row, column);
2625
2626 set_value_or_default (pname, val);
2627 }
2628}
2629
2630/*
2631## test set with cell array arguments
2632%!test
2633%! hf = figure ("visible", "off");
2634%! h = plot (1:10, 10:-1:1);
2635%! set (h, {"linewidth", "marker"}, {10, "x"});
2636%! lw = get (h, "linewidth");
2637%! mk = get (h, "marker");
2638%! close (hf);
2639%! assert (lw, 10);
2640%! assert (mk, "x");
2641
2642## test set with multiple handles and cell array arguments
2643%!test
2644%! hf = figure ("visible", "off");
2645%! unwind_protect
2646%! h = plot (1:10, 10:-1:1, 1:10, 1:10);
2647%! set (h, {"linewidth", "marker"}, {10, "x"; 5, "o"});
2648%! assert (get (h, "linewidth"), {10; 5});
2649%! assert (get (h, "marker"), {"x"; "o"});
2650%! set (h, {"linewidth", "marker"}, {10, "x"});
2651%! assert (get (h, "linewidth"), {10; 10});
2652%! assert (get (h, "marker"), {"x"; "x"});
2653%! unwind_protect_cleanup
2654%! close (hf);
2655%! end_unwind_protect;
2656
2657%!error <set: number of graphics handles must match number of value rows>
2658%! hf = figure ("visible", "off");
2659%! unwind_protect
2660%! h = plot (1:10, 10:-1:1, 1:10, 1:10);
2661%! set (h, {"linewidth", "marker"}, {10, "x"; 5, "o"; 7, "."});
2662%! unwind_protect_cleanup
2663%! close (hf);
2664%! end_unwind_protect
2665
2666%!error <set: number of names must match number of value columns>
2667%! hf = figure ("visible", "off");
2668%! unwind_protect
2669%! h = plot (1:10, 10:-1:1, 1:10, 1:10);
2670%! set (h, {"linewidth"}, {10, "x"; 5, "o"});
2671%! unwind_protect_cleanup
2672%! close (hf);
2673%! end_unwind_protect
2674*/
2675
2676// Set properties given in a struct array
2677void
2678graphics_object::set (const octave_map& m)
2679{
2680 for (octave_idx_type p = 0; p < m.nfields (); p++)
2681 {
2682 // FIXME: Would it be better to extract all the keys at once rather than
2683 // repeatedly call keys() inside a for loop?
2684 caseless_str pname = m.keys ()[p];
2685
2686 octave_value val = octave_value (m.contents (pname).elem (m.numel () - 1));
2687
2688 set_value_or_default (pname, val);
2689 }
2690}
2691
2692/*
2693## test set ticklabels for compatibility
2694%!test
2695%! hf = figure ("visible", "off");
2696%! set (gca (), "xticklabel", [0, 0.2, 0.4, 0.6, 0.8, 1]);
2697%! xticklabel = get (gca (), "xticklabel");
2698%! close (hf);
2699%! assert (class (xticklabel), "char");
2700%! assert (size (xticklabel), [6, 3]);
2701
2702%!test
2703%! hf = figure ("visible", "off");
2704%! set (gca (), "xticklabel", "0|0.2|0.4|0.6|0.8|1");
2705%! xticklabel = get (gca (), "xticklabel");
2706%! close (hf);
2707%! assert (class (xticklabel), "char");
2708%! assert (size (xticklabel), [6, 3]);
2709
2710%!test
2711%! hf = figure ("visible", "off");
2712%! set (gca (), "xticklabel", ["0 "; "0.2"; "0.4"; "0.6"; "0.8"; "1 "]);
2713%! xticklabel = get (gca (), "xticklabel");
2714%! close (hf);
2715%! assert (class (xticklabel), "char");
2716%! assert (size (xticklabel), [6, 3]);
2717
2718%!test
2719%! hf = figure ("visible", "off");
2720%! set (gca (), "xticklabel", {"0", "0.2", "0.4", "0.6", "0.8", "1"});
2721%! xticklabel = get (gca (), "xticklabel");
2722%! close (hf);
2723%! assert (class (xticklabel), "cell");
2724%! assert (size (xticklabel), [6, 1]);
2725*/
2726
2727/*
2728## test set with struct arguments
2729%!test
2730%! hf = figure ("visible", "off");
2731%! unwind_protect
2732%! h = plot (1:10, 10:-1:1);
2733%! set (h, struct ("linewidth", 10, "marker", "x"));
2734%! assert (get (h, "linewidth"), 10);
2735%! assert (get (h, "marker"), "x");
2736%! h = plot (1:10, 10:-1:1, 1:10, 1:10);
2737%! set (h, struct ("linewidth", {5, 10}));
2738%! assert (get (h, "linewidth"), {10; 10});
2739%! unwind_protect_cleanup
2740%! close (hf);
2741%! end_unwind_protect
2742
2743## test ordering
2744%!test
2745%! markchanged = @(h, foobar, name) set (h, "userdata", [get(h,"userdata"); {name}]);
2746%! hf = figure ("visible", "off");
2747%! unwind_protect
2748%! h = line ();
2749%! set (h, "userdata", {});
2750%! addlistener (h, "color", {markchanged, "color"});
2751%! addlistener (h, "linewidth", {markchanged, "linewidth"});
2752%! ## "linewidth" first
2753%! props.linewidth = 2;
2754%! props.color = "r";
2755%! set (h, props);
2756%! assert (get (h, "userdata"), fieldnames (props));
2757%! clear props;
2758%! clf ();
2759%! h = line ();
2760%! set (h, "userdata", {});
2761%! addlistener (h, "color", {markchanged, "color"});
2762%! addlistener (h, "linewidth", {markchanged, "linewidth"});
2763%! ## "color" first
2764%! props.color = "r";
2765%! props.linewidth = 2;
2766%! set (h, props);
2767%! assert (get (h, "userdata"), fieldnames (props));
2768%! unwind_protect_cleanup
2769%! close (hf);
2770%! end_unwind_protect
2771*/
2772
2773// Set a property to a value or to its (factory) default value.
2774
2775void
2776graphics_object::set_value_or_default (const caseless_str& pname,
2777 const octave_value& val)
2778{
2779 if (val.is_string () && val.rows () == 1)
2780 {
2781 std::string sval = val.string_value ();
2782
2783 octave_value default_val;
2784
2785 if (sval == "default")
2786 {
2787 default_val = get_default (pname);
2788
2789 m_rep->set (pname, default_val);
2790 }
2791 else if (sval == "factory")
2792 {
2793 default_val = get_factory_default (pname);
2794
2795 m_rep->set (pname, default_val);
2796 }
2797 else
2798 {
2799 // Matlab specifically uses "\default" to escape string setting
2800 if (sval == R"(\default)")
2801 m_rep->set (pname, "default");
2802 else if (sval == R"(\factory)")
2803 m_rep->set (pname, "factory");
2804 else
2805 m_rep->set (pname, val);
2806 }
2807 }
2808 else
2809 m_rep->set (pname, val);
2810}
2811
2812/*
2813## test setting of default values
2814%!test
2815%! old_lw = get (0, "defaultlinelinewidth");
2816%! unwind_protect
2817%! hf = figure ("visible", "off");
2818%! h = plot (1:10, 10:-1:1);
2819%! set (0, "defaultlinelinewidth", 20);
2820%! set (h, "linewidth", "default");
2821%! assert (get (h, "linewidth"), 20);
2822%! set (h, "linewidth", "factory");
2823%! assert (get (h, "linewidth"), 0.5);
2824%! unwind_protect_cleanup
2825%! close (hf);
2826%! set (0, "defaultlinelinewidth", old_lw);
2827%! end_unwind_protect
2828*/
2829
2830static double
2832{
2833 static double maxrand = RAND_MAX + 2.0;
2834
2835 return (rand () + 1.0) / maxrand;
2836}
2837
2839gh_manager::get_handle (bool integer_figure_handle)
2840{
2841 graphics_handle retval;
2842
2843 if (integer_figure_handle)
2844 {
2845 // Figure handles are positive integers corresponding
2846 // to the figure number.
2847
2848 // We always want the lowest unused figure number.
2849
2850 retval = 1;
2851
2852 while (m_handle_map.find (retval) != m_handle_map.end ())
2853 retval++;
2854 }
2855 else
2856 {
2857 // Other graphics handles are negative integers plus some random
2858 // fractional part. To avoid running out of integers, we recycle the
2859 // integer part but tack on a new random part each time.
2860
2861 auto p = m_handle_free_list.begin ();
2862
2863 if (p != m_handle_free_list.end ())
2864 {
2865 retval = *p;
2866 m_handle_free_list.erase (p);
2867 }
2868 else
2869 {
2870 retval = graphics_handle (m_next_handle);
2871
2872 m_next_handle = std::ceil (m_next_handle) - 1.0 - make_handle_fraction ();
2873 }
2874 }
2875
2876 return retval;
2877}
2878
2879static bool
2880isfigure (double val)
2881{
2882 gh_manager& gh_mgr = octave::__get_gh_manager__ ("isfigure");
2883
2884 graphics_object go = gh_mgr.get_object (val);
2885
2886 return go && go.isa ("figure");
2887}
2888
2889void
2890gh_manager::free (const graphics_handle& h, bool from_root)
2891{
2892 if (h.ok ())
2893 {
2894 if (h.value () == 0)
2895 error ("graphics_handle::free: can't delete root object");
2896
2897 auto p = m_handle_map.find (h);
2898
2899 if (p == m_handle_map.end ())
2900 error ("graphics_handle::free: invalid object %g", h.value ());
2901
2902 base_properties& bp = p->second.get_properties ();
2903
2904 if (! p->second.valid_object () || bp.is_beingdeleted ())
2905 return;
2906
2907 graphics_handle parent_h = p->second.get_parent ();
2908 graphics_object parent_go = nullptr;
2909 if (! from_root || isfigure (h.value ()))
2910 parent_go = get_object (parent_h);
2911
2912 bp.set_beingdeleted (true);
2913
2914 // delete listeners before invalidating object
2915 p->second.remove_all_listeners ();
2916
2917 bp.delete_children (true, from_root);
2918
2919 // NOTE: Call the delete function while the object's state is still valid.
2920 octave_value val = bp.get_deletefcn ();
2921
2922 bp.execute_deletefcn ();
2923
2924 // Notify graphics toolkit.
2925 p->second.finalize ();
2926
2927
2928 // NOTE: Call remove_child before erasing the go from the map if not
2929 // removing from groot.
2930 // A callback function might have already deleted the parent
2931 if ((! from_root || isfigure (h.value ())) && parent_go.valid_object ()
2932 && h.ok ())
2933 parent_go.remove_child (h);
2934
2935 // Note: this will be valid only for first explicitly deleted
2936 // object. All its children will then have an
2937 // unknown graphics toolkit.
2938
2939 // Graphics handles for non-figure objects are negative
2940 // integers plus some random fractional part. To avoid
2941 // running out of integers, we recycle the integer part
2942 // but tack on a new random part each time.
2943
2944 m_handle_map.erase (p);
2945
2946 if (h.value () < 0)
2947 m_handle_free_list.insert
2948 (std::ceil (h.value ()) - make_handle_fraction ());
2949 }
2950}
2951
2952void
2953gh_manager::renumber_figure (const graphics_handle& old_gh,
2954 const graphics_handle& new_gh)
2955{
2956 auto p = m_handle_map.find (old_gh);
2957
2958 if (p == m_handle_map.end ())
2959 error ("graphics_handle::free: invalid object %g", old_gh.value ());
2960
2961 graphics_object go = p->second;
2962
2963 m_handle_map.erase (p);
2964
2965 m_handle_map[new_gh] = go;
2966
2967 if (old_gh.value () < 0)
2968 m_handle_free_list.insert (std::ceil (old_gh.value ())
2970
2971 for (auto& hfig : m_figure_list)
2972 {
2973 if (hfig == old_gh)
2974 {
2975 hfig = new_gh;
2976 break;
2977 }
2978 }
2979}
2980
2981static void
2982xset (const graphics_handle& h, const caseless_str& pname,
2983 const octave_value& val)
2984{
2985 gh_manager& gh_mgr = octave::__get_gh_manager__ ("xset");
2986
2987 graphics_object go = gh_mgr.get_object (h);
2988
2989 go.set (pname, val);
2990}
2991
2992static void
2994{
2995 if (args.length () > 0)
2996 {
2997 gh_manager& gh_mgr = octave::__get_gh_manager__ ("xset");
2998
2999 graphics_object go = gh_mgr.get_object (h);
3000
3001 go.set (args);
3002 }
3003}
3004
3005static octave_value
3006xget (const graphics_handle& h, const caseless_str& pname)
3007{
3008 gh_manager& gh_mgr = octave::__get_gh_manager__ ("xget");
3009
3010 graphics_object go = gh_mgr.get_object (h);
3011
3012 return go.get (pname);
3013}
3014
3015static graphics_handle
3016reparent (const octave_value& ov, const std::string& who,
3017 const std::string& pname, const graphics_handle& new_parent,
3018 bool adopt = true)
3019{
3020 double hv = ov.xdouble_value ("%s: %s must be a graphics handle",
3021 who.c_str (), pname.c_str ());
3022
3023 gh_manager& gh_mgr = octave::__get_gh_manager__ ("reparent");
3024
3025 graphics_handle h = gh_mgr.lookup (hv);
3026
3027 if (! h.ok ())
3028 error ("%s: invalid graphics handle (= %g) for %s",
3029 who.c_str (), hv, pname.c_str ());
3030
3031 graphics_object go = gh_mgr.get_object (h);
3032
3033 graphics_handle parent_h = go.get_parent ();
3034
3035 graphics_object parent_go = gh_mgr.get_object (parent_h);
3036
3037 parent_go.remove_child (h);
3038
3039 if (adopt)
3040 go.set ("parent", new_parent.value ());
3041 else
3042 go.reparent (new_parent);
3043
3044 return h;
3045}
3046
3047// This function is NOT equivalent to the scripting language function gcf.
3049gcf (void)
3050{
3051 octave_value val = xget (0, "currentfigure");
3052
3054 : val.double_value ();
3055}
3056
3057// This function is NOT equivalent to the scripting language function gca.
3059gca (void)
3060{
3061 octave_value val = xget (gcf (), "currentaxes");
3062
3064 : val.double_value ();
3065}
3066
3067static void
3068delete_graphics_object (const graphics_handle& h, bool from_root = false)
3069{
3070 if (h.ok ())
3071 {
3072 gh_manager& gh_mgr
3073 = octave::__get_gh_manager__ ("delete_graphics_object");
3074
3075 graphics_object go = gh_mgr.get_object (h);
3076
3077 // Don't do recursive deleting, due to callbacks
3078 if (! go.get_properties ().is_beingdeleted ())
3079 {
3080 // NOTE: Freeing the handle also calls any deletefcn. It also calls
3081 // the parent's delete_child function.
3082
3083 gh_mgr.free (h, from_root || go.isa ("figure"));
3084
3085 Vdrawnow_requested = true;
3086 }
3087 }
3088}
3089
3090static void
3091delete_graphics_object (double val, bool from_root = false)
3092{
3093 gh_manager& gh_mgr = octave::__get_gh_manager__ ("delete_graphics_object");
3094
3095 delete_graphics_object (gh_mgr.lookup (val), from_root || isfigure (val));
3096}
3097
3098// Flag to stop redraws due to callbacks while deletion is in progress.
3099static bool delete_executing = false;
3100
3101static void
3102delete_graphics_objects (const NDArray vals, bool from_root = false)
3103{
3104 // Prevent redraw of partially deleted objects.
3106
3107 for (octave_idx_type i = 0; i < vals.numel (); i++)
3108 delete_graphics_object (vals.elem (i), from_root);
3109}
3110
3111static void
3113{
3114 octave_value closerequestfcn = xget (h, "closerequestfcn");
3115
3116 gh_manager& gh_mgr = octave::__get_gh_manager__ ("close_figure");
3117
3118 gh_mgr.execute_callback (h, closerequestfcn);
3119}
3120
3121static void
3123{
3124 // Remove the deletefcn and closerequestfcn callbacks
3125 // and delete the object directly.
3126
3127 xset (h, "deletefcn", Matrix ());
3128 xset (h, "closerequestfcn", Matrix ());
3129
3130 delete_graphics_object (h, true);
3131}
3132
3133void
3134gh_manager::close_all_figures (void)
3135{
3136 // FIXME: should we process or discard pending events?
3137
3138 m_event_queue.clear ();
3139
3140 // Don't use m_figure_list_iterator because we'll be removing elements
3141 // from the list elsewhere.
3142
3143 Matrix hlist = figure_handle_list (true);
3144
3145 for (octave_idx_type i = 0; i < hlist.numel (); i++)
3146 {
3147 graphics_handle h = lookup (hlist(i));
3148
3149 if (h.ok ())
3150 close_figure (h);
3151 }
3152
3153 // They should all be closed now. If not, force them to close.
3154
3155 hlist = figure_handle_list (true);
3156
3157 for (octave_idx_type i = 0; i < hlist.numel (); i++)
3158 {
3159 graphics_handle h = lookup (hlist(i));
3160
3161 if (h.ok ())
3163 }
3164
3165 // None left now, right?
3166
3167 hlist = figure_handle_list (true);
3168
3169 if (hlist.numel () != 0)
3170 warning ("gh_manager::close_all_figures: some graphics elements failed to close");
3171
3172 // Clear all callback objects from our list.
3173
3174 m_callback_objects.clear ();
3175}
3176
3177static void
3178adopt (const graphics_handle& parent_h, const graphics_handle& h)
3179{
3180 gh_manager& gh_mgr = octave::__get_gh_manager__ ("adopt");
3181
3182 graphics_object parent_go = gh_mgr.get_object (parent_h);
3183
3184 parent_go.adopt (h);
3185}
3186
3187static bool
3189{
3190 return h.ok ();
3191}
3192
3193static bool
3194ishghandle (double val)
3195{
3196 gh_manager& gh_mgr = octave::__get_gh_manager__ ("ishghandle");
3197
3198 graphics_handle h = gh_mgr.lookup (val);
3199
3200 return h.ok ();
3201}
3202
3203static octave_value
3205{
3206 octave_value retval = false;
3207
3208 if (val.is_real_scalar () && ishghandle (val.double_value ()))
3209 retval = true;
3210 else if (val.isnumeric () && val.isreal ())
3211 {
3212 const NDArray handles = val.array_value ();
3213
3214 boolNDArray result (handles.dims ());
3215
3216 for (octave_idx_type i = 0; i < handles.numel (); i++)
3217 result.xelem (i) = ishghandle (handles(i));
3218
3219 retval = result;
3220 }
3221
3222 return retval;
3223}
3224
3225static void
3227{
3228 gh_manager& gh_mgr = octave::__get_gh_manager__ ("xcreatefcn");
3229
3230 graphics_object go = gh_mgr.get_object (h);
3231
3232 go.get_properties ().execute_createfcn ();
3233}
3234
3235static void
3237{
3238 gh_manager& gh_mgr = octave::__get_gh_manager__ ("xinitialize");
3239
3240 graphics_object go = gh_mgr.get_object (h);
3241
3242 if (go)
3243 go.initialize ();
3244}
3245
3246// ---------------------------------------------------------------------
3247
3248static int
3249toggle_warn (std::string id, bool on, int state = -1)
3250{
3251 if (! on)
3252 {
3255 }
3256 else
3257 {
3258 if (state == 1)
3259 octave::set_warning_state (id, "on");
3260 else if (state == 2)
3261 octave::set_warning_state (id, "error");
3262 }
3263 return state;
3264}
3265
3266static void
3268 property_list::pval_map_type factory_pval)
3269{
3270 gh_manager& gh_mgr
3271 = octave::__get_gh_manager__ ("xreset_default_properties");
3272
3273 graphics_object go = gh_mgr.get_object (h);
3274
3275 // Replace factory defaults by user defined ones
3276 std::string go_name = go.get_properties ().graphics_object_name ();
3277 property_list::pval_map_type pval;
3278 go.build_user_defaults_map (pval, go_name);
3279
3280 for (const auto& p : pval)
3281 factory_pval[p.first] = p.second;
3282
3283 // Save warning state of "Octave:deprecated-property"
3284 int state = toggle_warn ("Octave:deprecated-property", false);
3285
3286 // Reset defaults
3287 for (const auto& p : factory_pval)
3288 {
3289 std::string pname = p.first;
3290
3291 // Don't reset internal properties and handle_properties
3292 if (! go.has_readonly_property (pname)
3293 && pname.find ("__") != 0 && pname.find ("current") != 0
3294 && pname != "uicontextmenu" && pname != "parent")
3295 {
3296 // Store *mode prop/val in order to set them last
3297 if (pname.find ("mode") == (pname.length () - 4))
3298 pval[pname] = p.second;
3299 else
3300 go.set (pname, p.second);
3301 }
3302 }
3303
3304 // set *mode properties
3305 for (const auto& p : pval)
3306 go.set (p.first, p.second);
3307
3308 toggle_warn ("Octave:deprecated-property", true, state);
3309}
3310
3311// ---------------------------------------------------------------------
3312
3313void
3314base_properties::set_from_list (base_graphics_object& bgo,
3315 property_list& defaults)
3316{
3317 std::string go_name = graphics_object_name ();
3318
3319 property_list::plist_map_const_iterator plist = defaults.find (go_name);
3320
3321 if (plist != defaults.end ())
3322 {
3323 const property_list::pval_map_type pval_map = plist->second;
3324
3325 for (const auto& prop_val : pval_map)
3326 {
3327 std::string pname = prop_val.first;
3328
3329 try
3330 {
3331 bgo.set (pname, prop_val.second);
3332 }
3333 catch (octave::execution_exception& ee)
3334 {
3335 error (ee, "error setting default property %s", pname.c_str ());
3336 }
3337 }
3338 }
3339}
3340
3341/*
3342## test defaults are set in the order they were stored
3343%!test
3344%! set (0, "defaultfigureunits", "normalized");
3345%! set(0, "defaultfigureposition", [0.7 0 0.3 0.3]);
3346%! hf = figure ("visible", "off");
3347%! tol = 20 * eps;
3348%! unwind_protect
3349%! assert (get (hf, "position"), [0.7 0 0.3 0.3], tol);
3350%! unwind_protect_cleanup
3351%! close (hf);
3352%! set (0, "defaultfigureunits", "remove");
3353%! set (0, "defaultfigureposition", "remove");
3354%! end_unwind_protect
3355*/
3356
3358base_properties::get_dynamic (const caseless_str& pname) const
3359{
3360 std::map<caseless_str, property, cmp_caseless_str>::const_iterator it
3361 = m_all_props.find (pname);
3362
3363 if (it == m_all_props.end ())
3364 error (R"(get: unknown property "%s")", pname.c_str ());
3365
3366 return it->second.get ();
3367}
3368
3370base_properties::get_dynamic (bool all) const
3371{
3373
3374 for (std::map<caseless_str, property, cmp_caseless_str>::const_iterator
3375 it = m_all_props.begin (); it != m_all_props.end (); ++it)
3376 if (all || ! it->second.is_hidden ())
3377 m.assign (it->second.get_name (), it->second.get ());
3378
3379 return m;
3380}
3381
3382std::set<std::string>
3383base_properties::dynamic_property_names (void) const
3384{
3385 return m_dynamic_properties;
3386}
3387
3388bool
3389base_properties::has_dynamic_property (const std::string& pname) const
3390{
3391 const std::set<std::string>& dynprops = dynamic_property_names ();
3392
3393 if (dynprops.find (pname) != dynprops.end ())
3394 return true;
3395 else
3396 return m_all_props.find (pname) != m_all_props.end ();
3397}
3398
3399void
3400base_properties::set_dynamic (const caseless_str& pname,
3401 const octave_value& val)
3402{
3403 auto it = m_all_props.find (pname);
3404
3405 if (it == m_all_props.end ())
3406 error (R"(set: unknown property "%s")", pname.c_str ());
3407
3408 it->second.set (val);
3409
3410 m_dynamic_properties.insert (pname);
3411
3412 mark_modified ();
3413}
3414
3415property
3416base_properties::get_property_dynamic (const caseless_str& pname) const
3417{
3418 std::map<caseless_str, property, cmp_caseless_str>::const_iterator it
3419 = m_all_props.find (pname);
3420
3421 if (it == m_all_props.end ())
3422 error (R"(get_property: unknown property "%s")", pname.c_str ());
3423
3424 return it->second;
3425}
3426
3427void
3428base_properties::set_parent (const octave_value& val)
3429{
3430 double hp = val.xdouble_value ("set: parent must be a graphics handle");
3431 if (hp == m___myhandle__)
3432 error ("set: can not set object parent to be object itself");
3433
3434 gh_manager& gh_mgr
3435 = octave::__get_gh_manager__ ("base_properties::set_parent");
3436
3437 graphics_handle new_parent = gh_mgr.lookup (hp);
3438 if (! new_parent.ok ())
3439 error ("set: invalid graphics handle (= %g) for parent", hp);
3440
3441 // Remove child from current parent
3442 graphics_object old_parent_go;
3443 old_parent_go = gh_mgr.get_object (get_parent ());
3444
3445 if (old_parent_go.get_handle () != hp)
3446 old_parent_go.remove_child (m___myhandle__);
3447 else
3448 return; // Do nothing more
3449
3450 // Check new parent's parent is not this child to avoid recursion
3451 graphics_object new_parent_go;
3452 new_parent_go = gh_mgr.get_object (new_parent);
3453 if (new_parent_go.get_parent () == m___myhandle__)
3454 {
3455 // new parent's parent gets child's original parent
3456 new_parent_go.get_properties ().set_parent (get_parent ().as_octave_value ());
3457 }
3458
3459 // Set parent property to new_parent and do adoption
3460 m_parent = new_parent.as_octave_value ();
3461 octave::adopt (m_parent.handle_value (), m___myhandle__);
3462}
3463
3464/*
3465%!test
3466%! hf = figure ("visible", "off");
3467%! unwind_protect
3468%! hax = gca ();
3469%! set (hax, "parent", gcf ());
3470%! assert (gca (), hax);
3471%! unwind_protect_cleanup
3472%! close (hf);
3473%! end_unwind_protect
3474*/
3475
3476void
3477base_properties::mark_modified (void)
3478{
3479 // Mark existing object as modified
3480 m___modified__ = "on";
3481
3482 // Attempt to mark parent object as modified if it exists
3483
3484 gh_manager& gh_mgr
3485 = octave::__get_gh_manager__ ("base_properties::mark_modified");
3486
3487 graphics_object parent_go = gh_mgr.get_object (get_parent ());
3488
3489 if (parent_go)
3490 parent_go.mark_modified ();
3491}
3492
3493void
3494base_properties::override_defaults (base_graphics_object& obj)
3495{
3496 gh_manager& gh_mgr
3497 = octave::__get_gh_manager__ ("base_properties::override_defaults");
3498
3499 graphics_object parent_go = gh_mgr.get_object (get_parent ());
3500
3501 if (parent_go)
3502 parent_go.override_defaults (obj);
3503}
3504
3505void
3506base_properties::update_axis_limits (const std::string& axis_type) const
3507{
3508 gh_manager& gh_mgr
3509 = octave::__get_gh_manager__ ("base_properties::update_axis_limits");
3510
3511 graphics_object go = gh_mgr.get_object (m___myhandle__);
3512
3513 if (go)
3514 go.update_axis_limits (axis_type);
3515}
3516
3517void
3518base_properties::update_axis_limits (const std::string& axis_type,
3519 const graphics_handle& h) const
3520{
3521 gh_manager& gh_mgr
3522 = octave::__get_gh_manager__ ("base_properties::update_axis_limits");
3523
3524 graphics_object go = gh_mgr.get_object (m___myhandle__);
3525
3526 if (go)
3527 go.update_axis_limits (axis_type, h);
3528}
3529
3530void
3531base_properties::update_contextmenu (void) const
3532{
3533 if (m_contextmenu.get ().isempty ())
3534 return;
3535
3536 gh_manager& gh_mgr
3537 = octave::__get_gh_manager__ ("base_properties::update_contextmenu");
3538
3539 graphics_object go = gh_mgr.get_object (m_contextmenu.get ());
3540
3541 if (go && go.isa ("uicontextmenu"))
3542 {
3544 = reinterpret_cast<uicontextmenu::properties&> (go.get_properties ());
3545 props.add_dependent_obj (m___myhandle__);
3546 }
3547}
3548
3549bool
3551{
3552 return (m_handlevisibility.is ("on")
3553 || (! executing_callbacks.empty () && ! m_handlevisibility.is ("off")));
3554}
3555
3557base_properties::get_toolkit (void) const
3558{
3559 gh_manager& gh_mgr
3560 = octave::__get_gh_manager__ ("base_properties::get_toolkit");
3561
3562 graphics_object go = gh_mgr.get_object (get_parent ());
3563
3564 if (go)
3565 return go.get_toolkit ();
3566 else
3567 return octave::graphics_toolkit ();
3568}
3569
3570void
3571base_properties::update_boundingbox (void)
3572{
3573 Matrix kids = get_children ();
3574
3575 gh_manager& gh_mgr
3576 = octave::__get_gh_manager__ ("base_properties::update_boundingbox");
3577
3578 for (int i = 0; i < kids.numel (); i++)
3579 {
3580 graphics_object go = gh_mgr.get_object (kids(i));
3581
3582 if (go.valid_object ())
3583 go.get_properties ().update_boundingbox ();
3584 }
3585}
3586
3587void
3588base_properties::update_autopos (const std::string& elem_type)
3589{
3590 gh_manager& gh_mgr
3591 = octave::__get_gh_manager__ ("base_properties::update_autopos");
3592
3593 graphics_object parent_go = gh_mgr.get_object (get_parent ());
3594
3595 if (parent_go.valid_object ())
3596 parent_go.get_properties ().update_autopos (elem_type);
3597}
3598
3599void
3600base_properties::update_handlevisibility (void)
3601{
3602 if (is_handle_visible ())
3603 return;
3604
3605 // This object should not be the figure "currentobject"
3606
3607 gh_manager& gh_mgr
3608 = octave::__get_gh_manager__ ("base_properties::update_handlevisibility");
3609
3610 graphics_object go (gh_mgr.get_object (get___myhandle__ ()));
3611
3612 graphics_object fig (go.get_ancestor ("figure"));
3613
3614 if (fig.valid_object ())
3615 {
3616 octave_value co = fig.get ("currentobject");
3617 if (! co.isempty () && co.double_value () == m___myhandle__)
3618 {
3619 octave::autolock guard (gh_mgr.graphics_lock ());
3620
3621 auto& fig_props = dynamic_cast<figure::properties&> (fig.get_properties ());
3622 fig_props.set_currentobject (Matrix ());
3623 }
3624 }
3625}
3626
3627/*
3628## test current figure and current axes have visible handles
3629%!test
3630%! hf1 = figure ("visible", "off");
3631%! hf2 = figure ("visible", "off");
3632%! hax1 = axes ();
3633%! hax2 = axes ();
3634%! unwind_protect
3635%! assert (get (0, "currentfigure"), hf2);
3636%! assert (get (hf2, "currentaxes"), hax2);
3637%! set (hf2, "handlevisibility", "off");
3638%! assert (get (0, "currentfigure"), hf1);
3639%! set (hax2, "handlevisibility", "off");
3640%! assert (get (hf2, "currentaxes"), hax1);
3641%! assert (get (hf2, "currentobject"), []);
3642%! unwind_protect_cleanup
3643%! close ([hf1, hf2]);
3644%! end_unwind_protect;
3645*/
3646
3647/*
3648## test current callback object have visible handle
3649%!test
3650%! hf = figure ("handlevisibility", "off", "visible", "off");
3651%! hax = axes ("parent", hf, "handlevisibility", "off");
3652%! unwind_protect
3653%! fcn = @(h, ~) setappdata (h, "testdata", gcbo ());
3654%! addlistener (hf, "color", fcn);
3655%! addlistener (hax, "color", fcn);
3656%! set (hf, "color", "b");
3657%! set (hax, "color", "b");
3658%! assert (getappdata (hf, "testdata"), hf)
3659%! assert (getappdata (hax, "testdata"), hax)
3660%! unwind_protect_cleanup
3661%! close (hf);
3662%! end_unwind_protect;
3663*/
3664
3665void
3666base_properties::add_listener (const caseless_str& pname,
3667 const octave_value& val,
3668 listener_mode mode)
3669{
3670 property p = get_property (pname);
3671
3672 if (p.ok ())
3673 p.add_listener (val, mode);
3674}
3675
3676void
3677base_properties::delete_listener (const caseless_str& pname,
3678 const octave_value& val,
3679 listener_mode mode)
3680{
3681 property p = get_property (pname);
3682
3683 if (p.ok ())
3684 p.delete_listener (val, mode);
3685}
3686
3687void
3688base_properties::get_children_of_type (const caseless_str& chtype,
3689 bool get_invisible,
3690 bool traverse,
3691 std::list<graphics_object>& children_list) const
3692{
3693 gh_manager& gh_mgr
3694 = octave::__get_gh_manager__ ("base_properties::get_children_of_type");
3695
3696 Matrix ch = get_children ();
3697
3698 for (octave_idx_type i = 0; i < ch.numel (); i++)
3699 {
3700 graphics_handle hkid = gh_mgr.lookup (ch(i));
3701
3702 if (hkid.ok ())
3703 {
3704 graphics_object go = gh_mgr.get_object (hkid);
3705 if ( get_invisible || go.get_properties ().is_visible () )
3706 {
3707 if (go.isa (chtype))
3708 children_list.push_back (go);
3709 else if (traverse && go.isa ("hggroup"))
3710 go.get_properties ().get_children_of_type (chtype,
3711 get_invisible,
3712 traverse,
3713 children_list);
3714 }
3715 }
3716 }
3717}
3718
3719// ---------------------------------------------------------------------
3720
3721void
3722base_graphics_object::update_axis_limits (const std::string& axis_type)
3723{
3724 if (! valid_object ())
3725 error ("base_graphics_object::update_axis_limits: invalid graphics object");
3726
3727 gh_manager& gh_mgr
3728 = octave::__get_gh_manager__ ("base_graphics_object::update_axis_limits");
3729
3730 graphics_object parent_go = gh_mgr.get_object (get_parent ());
3731
3732 if (parent_go)
3733 parent_go.update_axis_limits (axis_type);
3734}
3735
3736void
3737base_graphics_object::update_axis_limits (const std::string& axis_type,
3738 const graphics_handle& h)
3739{
3740 if (! valid_object ())
3741 error ("base_graphics_object::update_axis_limits: invalid graphics object");
3742
3743 gh_manager& gh_mgr
3744 = octave::__get_gh_manager__ ("base_graphics_object::update_axis_limits");
3745
3746 graphics_object parent_go = gh_mgr.get_object (get_parent ());
3747
3748 if (parent_go)
3749 parent_go.update_axis_limits (axis_type, h);
3750}
3751
3752void
3753base_graphics_object::remove_all_listeners (void)
3754{
3755 int state = toggle_warn ("Octave:deprecated-property", false);
3756 octave_map m = get (true).map_value ();
3757 toggle_warn ("Octave:deprecated-property", true, state);
3758
3759 for (const auto& pm : m)
3760 {
3761 // FIXME: there has to be a better way. I think we want to
3762 // ask whether it is OK to delete the listener for the given
3763 // property. How can we know in advance that it will be OK?
3764
3766
3768
3769 try
3770 {
3771 property p = get_properties ().get_property (pm.first);
3772
3773 if (p.ok ())
3774 p.delete_listener ();
3775 }
3776 catch (const octave::execution_exception&)
3777 {
3778 octave::interpreter& interp
3779 = octave::__get_interpreter__ ("remove_all_listeners");
3780
3781 interp.recover_from_exception ();
3782 }
3783 }
3784}
3785
3786void
3787base_graphics_object::build_user_defaults_map (property_list::pval_map_type& def,
3788 const std::string go_name) const
3789{
3790 property_list local_defaults = get_defaults_list ();
3791 const auto it = local_defaults.find (go_name);
3792
3793 if (it != local_defaults.end ())
3794 {
3795 property_list::pval_map_type pval_lst = it->second;
3796 for (const auto& prop_val : pval_lst)
3797 {
3798 std::string pname = prop_val.first;
3799 if (def.find (pname) == def.end ())
3800 def[pname] = prop_val.second;
3801 }
3802 }
3803
3804 gh_manager& gh_mgr
3805 = octave::__get_gh_manager__ ("base_graphics_object::build_user_defaults_map");
3806
3807 graphics_object parent_go = gh_mgr.get_object (get_parent ());
3808
3809 if (parent_go)
3810 parent_go.build_user_defaults_map (def, go_name);
3811}
3812
3813void
3814base_graphics_object::reset_default_properties (void)
3815{
3816 if (valid_object ())
3817 {
3818 gh_manager& gh_mgr
3819 = octave::__get_gh_manager__ ("base_graphics_object::reset_default_properties");
3820
3821 property_list::pval_map_type factory_pval
3822 = gh_mgr.get_object (0).get_factory_defaults_list ().find (type ())->second;
3823
3824 remove_all_listeners ();
3825 xreset_default_properties (get_handle (), factory_pval);
3826 }
3827}
3828
3829std::string
3830base_graphics_object::values_as_string (void)
3831{
3832 if (! valid_object ())
3833 error ("base_graphics_object::values_as_string: invalid graphics object");
3834
3835 std::string retval;
3836 octave_map m = get ().map_value ();
3837
3838 gh_manager& gh_mgr
3839 = octave::__get_gh_manager__ ("base_graphics_object::values_as_string");
3840
3841 graphics_object go = gh_mgr.get_object (get_handle ());
3842
3843 for (const auto& pm : m)
3844 {
3845 const auto& pname = pm.first;
3846 if (pname != "children" && ! go.has_readonly_property (pname))
3847 {
3848 property p = get_properties ().get_property (pname);
3849
3850 if (p.ok () && ! p.is_hidden ())
3851 {
3852 retval += "\n\t" + std::string (pname) + ": ";
3853 if (p.is_radio ())
3854 retval += p.values_as_string ();
3855 }
3856 }
3857 }
3858
3859 if (! retval.empty ())
3860 retval += '\n';
3861
3862 return retval;
3863}
3864
3865std::string
3866base_graphics_object::value_as_string (const std::string& prop)
3867{
3868 std::string retval;
3869
3870 if (! valid_object ())
3871 error ("base_graphics_object::value_as_string: invalid graphics object");
3872
3873 gh_manager& gh_mgr
3874 = octave::__get_gh_manager__ ("base_graphics_object::value_as_string");
3875
3876 graphics_object go = gh_mgr.get_object (get_handle ());
3877
3878 if (prop != "children" && ! go.has_readonly_property (prop))
3879 {
3880 property p = get_properties ().get_property (prop);
3881
3882 if (p.ok () && ! p.is_hidden ())
3883 {
3884 if (p.is_radio ())
3885 retval += p.values_as_string ();
3886 }
3887 }
3888
3889 if (! retval.empty ())
3890 retval += '\n';
3891
3892 return retval;
3893}
3894
3896base_graphics_object::values_as_struct (void)
3897{
3898 octave_scalar_map retval;
3899
3900 if (! valid_object ())
3901 error ("base_graphics_object::values_as_struct: invalid graphics object");
3902
3903 octave_scalar_map m = get ().scalar_map_value ();
3904
3905 gh_manager& gh_mgr
3906 = octave::__get_gh_manager__ ("base_graphics_object::values_as_struct");
3907
3908 graphics_object go = gh_mgr.get_object (get_handle ());
3909
3910 for (const auto& pm : m)
3911 {
3912 const auto& pname = pm.first;
3913 if (pname != "children" && ! go.has_readonly_property (pname))
3914 {
3915 property p = get_properties ().get_property (pname);
3916
3917 if (p.ok () && ! p.is_hidden ())
3918 {
3919 if (p.is_radio ())
3920 retval.assign (p.get_name (), p.values_as_cell ());
3921 else
3922 retval.assign (p.get_name (), Cell ());
3923 }
3924 }
3925 }
3926
3927 return retval;
3928}
3929
3930/*
3931%!test
3932%! hfig = figure ("visible", "off");
3933%! unwind_protect
3934%! hax = axes ();
3935%! ret = set (hax, "tightinset");
3936%! assert (isempty (ret));
3937%! ret = set (hax, "type");
3938%! assert (isempty (ret));
3939%! ret = set (hfig, "tag");
3940%! assert (isempty (ret));
3941%! ret = set (0, "commandwindowsize");
3942%! assert (isempty (ret));
3943%! ret = set (0);
3944%! assert (! isfield (ret, "commandwindowsize"));
3945%! unwind_protect_cleanup
3946%! close (hfig);
3947%! end_unwind_protect
3948*/
3949
3950graphics_object
3951graphics_object::get_ancestor (const std::string& obj_type) const
3952{
3953 if (valid_object ())
3954 {
3955 if (isa (obj_type))
3956 return *this;
3957 else
3958 {
3959 gh_manager& gh_mgr
3960 = octave::__get_gh_manager__ ("graphics_object::get_ancestor");
3961
3962 return gh_mgr.get_object (get_parent ()).get_ancestor (obj_type);
3963 }
3964 }
3965 else
3966 return graphics_object ();
3967}
3968
3969// ---------------------------------------------------------------------
3970
3971#include "graphics-props.cc"
3972
3973// ---------------------------------------------------------------------
3974
3975void
3976root_figure::properties::set_callbackobject (const octave_value& v)
3977{
3978 graphics_handle val (v);
3979
3980 if (octave::math::isnan (val.value ()))
3981 m_callbackobject = graphics_handle ();
3982 else if (ishghandle (val))
3983 m_callbackobject = val;
3984 else
3985 err_set_invalid ("callbackobject");
3986}
3987
3988void
3989root_figure::properties::set_currentfigure (const octave_value& v)
3990{
3991 graphics_handle val (v);
3992
3993 if (octave::math::isnan (val.value ()) || ishghandle (val))
3994 {
3995 m_currentfigure = val;
3996
3997 if (val.ok ())
3998 {
3999 gh_manager& gh_mgr
4000 = octave::__get_gh_manager__ ("root_figure::properties::set_currentfigure");
4001
4002 gh_mgr.push_figure (val);
4003 }
4004 }
4005 else
4006 err_set_invalid ("currentfigure");
4007}
4008
4009void
4010figure::properties::set_integerhandle (const octave_value& val)
4011{
4012 if (m_integerhandle.set (val, true))
4013 {
4014 bool int_fig_handle = m_integerhandle.is_on ();
4015
4016 gh_manager& gh_mgr
4017 = octave::__get_gh_manager__ ("figure::properties::set_integerhandle");
4018
4019 graphics_object this_go = gh_mgr.get_object (m___myhandle__);
4020
4021 graphics_handle old_myhandle = m___myhandle__;
4022
4023 m___myhandle__ = gh_mgr.get_handle (int_fig_handle);
4024
4025 gh_mgr.renumber_figure (old_myhandle, m___myhandle__);
4026
4027 graphics_object parent_go = gh_mgr.get_object (get_parent ());
4028
4029 base_properties& props = parent_go.get_properties ();
4030
4031 props.renumber_child (old_myhandle, m___myhandle__);
4032
4033 Matrix kids = get_children ();
4034
4035 for (octave_idx_type i = 0; i < kids.numel (); i++)
4036 {
4037 graphics_object kid = gh_mgr.get_object (kids(i));
4038
4039 kid.get_properties ().renumber_parent (m___myhandle__);
4040 }
4041
4042 graphics_handle cf = gh_mgr.current_figure ();
4043
4044 if (m___myhandle__ == cf)
4045 xset (0, "currentfigure", m___myhandle__.value ());
4046
4047 this_go.update (m_integerhandle.get_id ());
4048
4049 mark_modified ();
4050 }
4051}
4052
4053// FIXME: This should update monitorpositions and pointerlocation, but as these
4054// properties aren't yet used, it doesn't matter that they aren't set either.
4055void
4056root_figure::properties::update_units (void)
4057{
4058 std::string xunits = get_units ();
4059
4060 Matrix scrn_sz = default_screensize ();
4061
4062 double dpi = get_screenpixelsperinch ();
4063
4064 if (xunits == "pixels")
4065 {
4066 // Most common case (default).
4067 // Don't need to convert anything, but short-circuit if/else tree.
4068 }
4069 else if (xunits == "normalized")
4070 {
4071 scrn_sz = Matrix (1, 4, 1.0);
4072 scrn_sz(0) = 0;
4073 scrn_sz(1) = 0;
4074 }
4075 else if (xunits == "inches")
4076 {
4077 scrn_sz(0) = 0;
4078 scrn_sz(1) = 0;
4079 scrn_sz(2) /= dpi;
4080 scrn_sz(3) /= dpi;
4081 }
4082 else if (xunits == "centimeters")
4083 {
4084 scrn_sz(0) = 0;
4085 scrn_sz(1) = 0;
4086 scrn_sz(2) *= 2.54 / dpi;
4087 scrn_sz(3) *= 2.54 / dpi;
4088 }
4089 else if (xunits == "points")
4090 {
4091 scrn_sz(0) = 0;
4092 scrn_sz(1) = 0;
4093 scrn_sz(2) *= 72 / dpi;
4094 scrn_sz(3) *= 72 / dpi;
4095 }
4096 else if (xunits == "characters")
4097 {
4098 scrn_sz(0) = 0;
4099 scrn_sz(1) = 0;
4100 // FIXME: this assumes the system font is Helvetica 10pt
4101 // (for which "x" requires 6x12 pixels at 74.951 pixels/inch)
4102 scrn_sz(2) *= 74.951 / 12.0 / dpi;
4103 scrn_sz(3) *= 74.951 / 12.0 / dpi;
4104 }
4105
4106 set_screensize (scrn_sz);
4107}
4108
4109Matrix
4110root_figure::properties::get_boundingbox (bool, const Matrix&) const
4111{
4112 Matrix screen_size = screen_size_pixels ();
4113 Matrix pos = Matrix (1, 4, 0.0);
4114
4115 pos(2) = screen_size(0);
4116 pos(3) = screen_size(1);
4117
4118 return pos;
4119}
4120
4121/*
4122%!test
4123%! old_units = get (0, "units");
4124%! unwind_protect
4125%! set (0, "units", "pixels");
4126%! sz = get (0, "screensize") - [1, 1, 0, 0];
4127%! dpi = get (0, "screenpixelsperinch");
4128%! set (0, "units", "inches");
4129%! assert (get (0, "screensize"), sz / dpi, 0.5 / dpi);
4130%! set (0, "units", "centimeters");
4131%! assert (get (0, "screensize"), sz / dpi * 2.54, 0.5 / dpi * 2.54);
4132%! set (0, "units", "points");
4133%! assert (get (0, "screensize"), sz / dpi * 72, 0.5 / dpi * 72);
4134%! set (0, "units", "normalized");
4135%! assert (get (0, "screensize"), [0.0, 0.0, 1.0, 1.0]);
4136%! set (0, "units", "pixels");
4137%! assert (get (0, "screensize"), sz + [1, 1, 0, 0]);
4138%! set (0, "units", "characters");
4139%! assert (get (0, "screensize"), sz / dpi * (74.951 / 12.0), 0.5 / dpi * (74.951 / 12.0));
4140%! unwind_protect_cleanup
4141%! set (0, "units", old_units);
4142%! end_unwind_protect
4143*/
4144
4145void
4146root_figure::properties::remove_child (const graphics_handle& h, bool)
4147{
4148 gh_manager& gh_mgr
4149 = octave::__get_gh_manager__ ("root_figure::properties::remove_child");
4150
4151 gh_mgr.pop_figure (h);
4152
4153 graphics_handle cf = gh_mgr.current_figure ();
4154
4155 xset (0, "currentfigure", cf.value ());
4156
4157 base_properties::remove_child (h, true);
4158}
4159
4160void
4161root_figure::reset_default_properties (void)
4162{
4163 // empty list of local defaults
4164 m_default_properties = property_list ();
4165
4166 remove_all_listeners ();
4167 xreset_default_properties (get_handle (),
4168 m_properties.factory_defaults ());
4169}
4170
4171// ---------------------------------------------------------------------
4172
4173void
4174figure::properties::set_currentaxes (const octave_value& val)
4175{
4176 graphics_handle hax (val);
4177
4178 if (octave::math::isnan (hax.value ()) || ishghandle (hax))
4179 m_currentaxes = hax;
4180 else
4181 err_set_invalid ("currentaxes");
4182}
4183
4184void
4185figure::properties::remove_child (const graphics_handle& h, bool from_root)
4186{
4187 base_properties::remove_child (h, from_root);
4188
4189 if (h == m_currentaxes.handle_value ())
4190 {
4191 graphics_handle new_currentaxes;
4192
4193 Matrix kids = get_children ();
4194
4195 gh_manager& gh_mgr
4196 = octave::__get_gh_manager__ ("root_figure::properties::remove_child");
4197
4198 for (octave_idx_type i = 0; i < kids.numel (); i++)
4199 {
4200 graphics_handle kid = kids(i);
4201
4202 graphics_object go = gh_mgr.get_object (kid);
4203
4204 if (go.isa ("axes"))
4205 {
4206 new_currentaxes = kid;
4207 break;
4208 }
4209 }
4210
4211 m_currentaxes = new_currentaxes;
4212 }
4213}
4214
4216figure::properties::get_number (void) const
4217{
4218 if (m_integerhandle.is_on ())
4219 return m___myhandle__.value ();
4220 else
4221 return Matrix ();
4222}
4223
4225figure::properties::get_toolkit (void) const
4226{
4227 return m_toolkit;
4228}
4229
4230void
4231figure::properties::set___graphics_toolkit__ (const octave_value& val)
4232{
4233 if (! val.is_string ())
4234 error ("set___graphics_toolkit__: toolkit must be a string");
4235
4236 std::string nm = val.string_value ();
4237
4238 octave::gtk_manager& gtk_mgr
4239 = octave::__get_gtk_manager__ ("figure::properties::set___graphics_toolkit__");
4240
4241 octave::graphics_toolkit b = gtk_mgr.find_toolkit (nm);
4242
4243 if (b.get_name () != nm)
4244 error ("set___graphics_toolkit__: invalid graphics toolkit");
4245
4246 if (nm != get___graphics_toolkit__ ())
4247 {
4248 set_toolkit (b);
4249 mark_modified ();
4250 }
4251}
4252
4253void
4255{
4257
4258 if (! get_currentaxes ().ok ())
4259 {
4260 gh_manager& gh_mgr
4261 = octave::__get_gh_manager__ ("figure::properties::adopt");
4262
4263 graphics_object go = gh_mgr.get_object (h);
4264
4265 if (go.type () == "axes")
4266 set_currentaxes (h.as_octave_value ());
4267 }
4268}
4269
4270/*
4271%!test
4272%! hf1 = figure ("visible", "off");
4273%! ax1 = subplot (1,2,1);
4274%! ax2 = subplot (1,2,2);
4275%! hf2 = figure ("visible", "off");
4276%! unwind_protect
4277%! set (ax2, "parent", hf2);
4278%! assert (get (hf2, "currentaxes"), ax2);
4279%! assert (get (hf1, "currentaxes"), ax1);
4280%! set (ax1, "parent", hf2);
4281%! assert (get (hf2, "currentaxes"), ax2);
4282%! unwind_protect_cleanup
4283%! close (hf1);
4284%! close (hf2);
4285%! end_unwind_protect
4286*/
4287
4288void
4289figure::properties::set_visible (const octave_value& val)
4290{
4291 std::string sval = val.string_value ();
4292
4293 if (sval == "on")
4294 xset (0, "currentfigure", m___myhandle__.value ());
4295
4296 m_visible = val;
4297}
4298
4299Matrix
4300figure::properties::get_boundingbox (bool internal, const Matrix&) const
4301{
4302 Matrix screen_size = screen_size_pixels ();
4303 Matrix pos = (internal ?
4304 get_position ().matrix_value () :
4305 get_outerposition ().matrix_value ());
4306
4307 pos = convert_position (pos, get_units (), "pixels", screen_size);
4308
4309 pos(0)--;
4310 pos(1)--;
4311 pos(1) = screen_size(1) - pos(1) - pos(3);
4312
4313 return pos;
4314}
4315
4316Matrix
4317figure::properties::bbox2position (const Matrix& bb) const
4318{
4319 Matrix screen_size = screen_size_pixels ();
4320 Matrix pos = bb;
4321
4322 pos(1) = screen_size(1) - pos(1) - pos(3);
4323 pos(1)++;
4324 pos(0)++;
4325 pos = convert_position (pos, "pixels", get_units (), screen_size);
4326 return pos;
4327}
4328
4329void
4330figure::properties::set_boundingbox (const Matrix& bb, bool internal,
4331 bool do_notify_toolkit)
4332{
4333 Matrix screen_size = screen_size_pixels ();
4334 Matrix pos = bbox2position (bb);
4335
4336 if (internal)
4337 set_position (pos, do_notify_toolkit);
4338 else
4339 set_outerposition (pos, do_notify_toolkit);
4340}
4341
4342Matrix
4343figure::properties::map_from_boundingbox (double x, double y) const
4344{
4345 Matrix bb = get_boundingbox (true);
4346 Matrix pos (1, 2, 0.0);
4347
4348 pos(0) = x;
4349 pos(1) = y;
4350
4351 pos(1) = bb(3) - pos(1);
4352 pos(0)++;
4353 pos = convert_position (pos, "pixels", get_units (),
4354 bb.extract_n (0, 2, 1, 2));
4355
4356 return pos;
4357}
4358
4359Matrix
4360figure::properties::map_to_boundingbox (double x, double y) const
4361{
4362 Matrix bb = get_boundingbox (true);
4363 Matrix pos (1, 2, 0.0);
4364
4365 pos(0) = x;
4366 pos(1) = y;
4367
4368 pos = convert_position (pos, get_units (), "pixels",
4369 bb.extract_n (0, 2, 1, 2));
4370 pos(0)--;
4371 pos(1) = bb(3) - pos(1);
4372
4373 return pos;
4374}
4375
4376void
4377figure::properties::set_position (const octave_value& v,
4378 bool do_notify_toolkit)
4379{
4380 Matrix old_bb, new_bb;
4381 bool modified = false;
4382
4383 old_bb = get_boundingbox (true);
4384 modified = m_position.set (v, false, do_notify_toolkit);
4385 new_bb = get_boundingbox (true);
4386
4387 if (old_bb != new_bb)
4388 {
4389 if (old_bb(2) != new_bb(2) || old_bb(3) != new_bb(3))
4390 {
4391 gh_manager& gh_mgr
4392 = octave::__get_gh_manager__ ("figure::properties::set_position");
4393
4394 if (! get_resizefcn ().isempty ())
4395 gh_mgr.post_callback (m___myhandle__, "resizefcn");
4396
4397 if (! get_sizechangedfcn ().isempty ())
4398 gh_mgr.post_callback (m___myhandle__, "sizechangedfcn");
4399
4400 update_boundingbox ();
4401 }
4402 }
4403
4404 if (modified)
4405 {
4406 m_position.run_listeners (GCB_POSTSET);
4407 mark_modified ();
4408 }
4409
4410 if (m_paperpositionmode.is ("auto"))
4411 m_paperposition.set (get_auto_paperposition ());
4412}
4413
4414void
4415figure::properties::set_outerposition (const octave_value& v,
4416 bool do_notify_toolkit)
4417{
4418 if (m_outerposition.set (v, true, do_notify_toolkit))
4419 mark_modified ();
4420}
4421
4422void
4423figure::properties::set_paperunits (const octave_value& val)
4424{
4425 caseless_str punits = val.string_value ();
4426 caseless_str ptype = get_papertype ();
4427
4428 if (punits.compare ("normalized") && ptype.compare ("<custom>"))
4429 error ("set: can't set paperunits to normalized when papertype is custom");
4430
4431 caseless_str old_paperunits = get_paperunits ();
4432 if (m_paperunits.set (val, true))
4433 {
4434 update_paperunits (old_paperunits);
4435 mark_modified ();
4436 }
4437}
4438
4439void
4440figure::properties::set_papertype (const octave_value& val)
4441{
4442 caseless_str ptype = val.string_value ();
4443 caseless_str punits = get_paperunits ();
4444
4445 if (punits.compare ("normalized") && ptype.compare ("<custom>"))
4446 error ("set: can't set paperunits to normalized when papertype is custom");
4447
4448 if (m_papertype.set (val, true))
4449 {
4450 update_papertype ();
4451 mark_modified ();
4452 }
4453}
4454
4455static Matrix
4457{
4458 Matrix retval (1, 2, 1.0);
4459
4460 if (! punits.compare ("normalized"))
4461 {
4462 double in2units;
4463 double mm2units;
4464
4465 if (punits.compare ("inches"))
4466 {
4467 in2units = 1.0;
4468 mm2units = 1 / 25.4;
4469 }
4470 else if (punits.compare ("centimeters"))
4471 {
4472 in2units = 2.54;
4473 mm2units = 1 / 10.0;
4474 }
4475 else // points
4476 {
4477 in2units = 72.0;
4478 mm2units = 72.0 / 25.4;
4479 }
4480
4481 if (ptype.compare ("usletter"))
4482 {
4483 retval(0) = 8.5 * in2units;
4484 retval(1) = 11.0 * in2units;
4485 }
4486 else if (ptype.compare ("uslegal"))
4487 {
4488 retval(0) = 8.5 * in2units;
4489 retval(1) = 14.0 * in2units;
4490 }
4491 else if (ptype.compare ("tabloid"))
4492 {
4493 retval(0) = 11.0 * in2units;
4494 retval(1) = 17.0 * in2units;
4495 }
4496 else if (ptype.compare ("a0"))
4497 {
4498 retval(0) = 841.0 * mm2units;
4499 retval(1) = 1189.0 * mm2units;
4500 }
4501 else if (ptype.compare ("a1"))
4502 {
4503 retval(0) = 594.0 * mm2units;
4504 retval(1) = 841.0 * mm2units;
4505 }
4506 else if (ptype.compare ("a2"))
4507 {
4508 retval(0) = 420.0 * mm2units;
4509 retval(1) = 594.0 * mm2units;
4510 }
4511 else if (ptype.compare ("a3"))
4512 {
4513 retval(0) = 297.0 * mm2units;
4514 retval(1) = 420.0 * mm2units;
4515 }
4516 else if (ptype.compare ("a4"))
4517 {
4518 retval(0) = 210.0 * mm2units;
4519 retval(1) = 297.0 * mm2units;
4520 }
4521 else if (ptype.compare ("a5"))
4522 {
4523 retval(0) = 148.0 * mm2units;
4524 retval(1) = 210.0 * mm2units;
4525 }
4526 else if (ptype.compare ("b0"))
4527 {
4528 retval(0) = 1029.0 * mm2units;
4529 retval(1) = 1456.0 * mm2units;
4530 }
4531 else if (ptype.compare ("b1"))
4532 {
4533 retval(0) = 728.0 * mm2units;
4534 retval(1) = 1028.0 * mm2units;
4535 }
4536 else if (ptype.compare ("b2"))
4537 {
4538 retval(0) = 514.0 * mm2units;
4539 retval(1) = 728.0 * mm2units;
4540 }
4541 else if (ptype.compare ("b3"))
4542 {
4543 retval(0) = 364.0 * mm2units;
4544 retval(1) = 514.0 * mm2units;
4545 }
4546 else if (ptype.compare ("b4"))
4547 {
4548 retval(0) = 257.0 * mm2units;
4549 retval(1) = 364.0 * mm2units;
4550 }
4551 else if (ptype.compare ("b5"))
4552 {
4553 retval(0) = 182.0 * mm2units;
4554 retval(1) = 257.0 * mm2units;
4555 }
4556 else if (ptype.compare ("arch-a"))
4557 {
4558 retval(0) = 9.0 * in2units;
4559 retval(1) = 12.0 * in2units;
4560 }
4561 else if (ptype.compare ("arch-b"))
4562 {
4563 retval(0) = 12.0 * in2units;
4564 retval(1) = 18.0 * in2units;
4565 }
4566 else if (ptype.compare ("arch-c"))
4567 {
4568 retval(0) = 18.0 * in2units;
4569 retval(1) = 24.0 * in2units;
4570 }
4571 else if (ptype.compare ("arch-d"))
4572 {
4573 retval(0) = 24.0 * in2units;
4574 retval(1) = 36.0 * in2units;
4575 }
4576 else if (ptype.compare ("arch-e"))
4577 {
4578 retval(0) = 36.0 * in2units;
4579 retval(1) = 48.0 * in2units;
4580 }
4581 else if (ptype.compare ("a"))
4582 {
4583 retval(0) = 8.5 * in2units;
4584 retval(1) = 11.0 * in2units;
4585 }
4586 else if (ptype.compare ("b"))
4587 {
4588 retval(0) = 11.0 * in2units;
4589 retval(1) = 17.0 * in2units;
4590 }
4591 else if (ptype.compare ("c"))
4592 {
4593 retval(0) = 17.0 * in2units;
4594 retval(1) = 22.0 * in2units;
4595 }
4596 else if (ptype.compare ("d"))
4597 {
4598 retval(0) = 22.0 * in2units;
4599 retval(1) = 34.0 * in2units;
4600 }
4601 else if (ptype.compare ("e"))
4602 {
4603 retval(0) = 34.0 * in2units;
4604 retval(1) = 43.0 * in2units;
4605 }
4606 }
4607
4608 return retval;
4609}
4610
4611Matrix
4612figure::properties::get_auto_paperposition (void)
4613{
4614 Matrix pos = get_position ().matrix_value ();
4615 Matrix sz;
4616
4617 caseless_str funits = get_units ();
4618 caseless_str punits = get_paperunits ();
4619
4620 // Convert position from figure units to paperunits
4621 if (funits == "normalized" || punits == "normalized")
4622 {
4623 sz = screen_size_pixels ();
4624 pos = convert_position (pos, funits, "inches", sz);
4625
4626 if (punits == "normalized")
4627 sz = papersize_from_type ("points", get_papertype ());
4628
4629 pos = convert_position (pos, "inches", punits, sz);
4630 }
4631 else
4632 pos = convert_position (pos, funits, punits, sz);
4633
4634 // Center the figure on the page
4635 sz = get_papersize ().matrix_value ();
4636
4637 pos(0) = sz(0)/2 - pos(2)/2;
4638 pos(1) = sz(1)/2 - pos(3)/2;
4639
4640 return pos;
4641}
4642
4643/*
4644%!test
4645%! hf = figure ("visible", "off", "paperpositionmode", "auto");
4646%! in_pos = [0 0 4 5];
4647%! tol = 20 * eps ();
4648%! unwind_protect
4649%! ## paperpositionmode "auto" converts figure size to paper units
4650%! set (hf, "units", "inches");
4651%! set (hf, "position", in_pos);
4652%! set (hf, "paperunits", "centimeters");
4653%! psz = get (hf, "papersize");
4654%! fsz = in_pos(3:4) * 2.54;
4655%! pos = [(psz/2 - fsz/2) fsz];
4656%! set (hf, "paperpositionmode", "auto");
4657%! assert (get (hf, "paperposition"), pos, tol);
4658%! unwind_protect_cleanup
4659%! close (hf);
4660%! end_unwind_protect
4661
4662%!test
4663%! hf = figure ("visible", "off", "paperpositionmode", "auto");
4664%! in_pos = [0 0 4 5];
4665%! tol = 20 * eps ();
4666%! unwind_protect
4667%! ## likewise with normalized units
4668%! set (hf, "units", "inches");
4669%! set (hf, "position", in_pos);
4670%! psz = get (hf, "papersize");
4671%! set (hf, "paperunits", "normalized");
4672%! fsz = in_pos(3:4) ./ psz;
4673%! pos = [([0.5 0.5] - fsz/2) fsz];
4674%! assert (get (hf, "paperposition"), pos, tol);
4675%! unwind_protect_cleanup
4676%! close (hf);
4677%! end_unwind_protect
4678
4679%!test
4680%! hf = figure ("visible", "off", "paperpositionmode", "auto");
4681%! in_pos = [0 0 4 5];
4682%! tol = 20 * eps ();
4683%! unwind_protect
4684%! ## changing papertype updates paperposition
4685%! set (hf, "units", "inches");
4686%! set (hf, "position", in_pos);
4687%! set (hf, "papertype", "a4");
4688%! psz = get (hf, "papersize");
4689%! fsz = in_pos(3:4);
4690%! pos = [(psz/2 - fsz/2) fsz];
4691%! assert (get (hf, "paperposition"), pos, tol);
4692%! unwind_protect_cleanup
4693%! close (hf);
4694%! end_unwind_protect
4695
4696%!test
4697%! hf = figure ("visible", "off", "paperpositionmode", "auto");
4698%! in_pos = [0 0 4 5];
4699%! tol = 20 * eps ();
4700%! unwind_protect
4701%! ## lanscape updates paperposition
4702%! set (hf, "units", "inches");
4703%! set (hf, "position", in_pos);
4704%! set (hf, "paperorientation", "landscape");
4705%! psz = get (hf, "papersize");
4706%! fsz = in_pos(3:4);
4707%! pos = [(psz/2 - fsz/2) fsz];
4708%! assert (get (hf, "paperposition"), pos, tol);
4709%! unwind_protect_cleanup
4710%! close (hf);
4711%! end_unwind_protect
4712
4713%!test
4714%! hf = figure ("visible", "off", "paperpositionmode", "auto");
4715%! in_pos = [0 0 4 5];
4716%! unwind_protect
4717%! ## back to manual mode
4718%! set (hf, "paperposition", in_pos * 1.1);
4719%! assert (get (hf, "paperpositionmode"), "manual");
4720%! assert (get (hf, "paperposition"), in_pos * 1.1);
4721%! unwind_protect_cleanup
4722%! close (hf);
4723%! end_unwind_protect
4724*/
4725
4726void
4727figure::properties::update_paperunits (const caseless_str& old_paperunits)
4728{
4729 Matrix pos = get_paperposition ().matrix_value ();
4730 Matrix sz = get_papersize ().matrix_value ();
4731
4732 pos(0) /= sz(0);
4733 pos(1) /= sz(1);
4734 pos(2) /= sz(0);
4735 pos(3) /= sz(1);
4736
4737 std::string porient = get_paperorientation ();
4738 caseless_str punits = get_paperunits ();
4739 caseless_str ptype = get_papertype ();
4740
4741 if (ptype.compare ("<custom>"))
4742 {
4743 if (old_paperunits.compare ("centimeters"))
4744 {
4745 sz(0) /= 2.54;
4746 sz(1) /= 2.54;
4747 }
4748 else if (old_paperunits.compare ("points"))
4749 {
4750 sz(0) /= 72.0;
4751 sz(1) /= 72.0;
4752 }
4753
4754 if (punits.compare ("centimeters"))
4755 {
4756 sz(0) *= 2.54;
4757 sz(1) *= 2.54;
4758 }
4759 else if (punits.compare ("points"))
4760 {
4761 sz(0) *= 72.0;
4762 sz(1) *= 72.0;
4763 }
4764 }
4765 else
4766 {
4767 sz = papersize_from_type (punits, ptype);
4768 if (porient == "landscape")
4769 std::swap (sz(0), sz(1));
4770 }
4771
4772 pos(0) *= sz(0);
4773 pos(1) *= sz(1);
4774 pos(2) *= sz(0);
4775 pos(3) *= sz(1);
4776
4777 m_papersize.set (octave_value (sz));
4778 m_paperposition.set (octave_value (pos));
4779}
4780
4781void
4782figure::properties::update_papertype (void)
4783{
4784 std::string typ = get_papertype ();
4785 if (typ != "<custom>")
4786 {
4787 Matrix sz = papersize_from_type (get_paperunits (), typ);
4788 if (get_paperorientation () == "landscape")
4789 std::swap (sz(0), sz(1));
4790 // Call papersize.set rather than set_papersize to avoid loops
4791 // between update_papersize and update_papertype.
4792 m_papersize.set (octave_value (sz));
4793 }
4794
4795 if (m_paperpositionmode.is ("auto"))
4796 m_paperposition.set (get_auto_paperposition ());
4797}
4798
4799void
4800figure::properties::update_papersize (void)
4801{
4802 Matrix sz = get_papersize ().matrix_value ();
4803 if (sz(0) > sz(1))
4804 {
4805 std::swap (sz(0), sz(1));
4806 m_papersize.set (octave_value (sz));
4807 m_paperorientation.set (octave_value ("landscape"));
4808 }
4809 else
4810 {
4811 m_paperorientation.set ("portrait");
4812 }
4813
4814 std::string punits = get_paperunits ();
4815 if (punits == "centimeters")
4816 {
4817 sz(0) /= 2.54;
4818 sz(1) /= 2.54;
4819 }
4820 else if (punits == "points")
4821 {
4822 sz(0) /= 72.0;
4823 sz(1) /= 72.0;
4824 }
4825 if (punits == "normalized")
4826 {
4827 if (get_papertype () == "<custom>")
4828 error ("set: can't set the papertype to <custom> when the paperunits is normalized");
4829 }
4830 else
4831 {
4832 // FIXME: The papersizes info is also in papersize_from_type().
4833 // Both should be rewritten to avoid the duplication.
4834 // Don't Repeat Yourself (DRY) principle.
4835 std::string ptype = "<custom>";
4836 const double mm2in = 1.0 / 25.4;
4837 const double tol = 0.01;
4838
4839 if (std::abs (sz(0) - 8.5) + std::abs (sz(1) - 11.0) < tol)
4840 ptype = "usletter";
4841 else if (std::abs (sz(0) - 8.5) + std::abs (sz(1) - 14.0) < tol)
4842 ptype = "uslegal";
4843 else if (std::abs (sz(0) - 11.0) + std::abs (sz(1) - 17.0) < tol)
4844 ptype = "tabloid";
4845 else if (std::abs (sz(0) - 841.0 * mm2in)
4846 + std::abs (sz(1) - 1198.0 * mm2in) < tol)
4847 ptype = "a0";
4848 else if (std::abs (sz(0) - 594.0 * mm2in)
4849 + std::abs (sz(1) - 841.0 * mm2in) < tol)
4850 ptype = "a1";
4851 else if (std::abs (sz(0) - 420.0 * mm2in)
4852 + std::abs (sz(1) - 594.0 * mm2in) < tol)
4853 ptype = "a2";
4854 else if (std::abs (sz(0) - 297.0 * mm2in)
4855 + std::abs (sz(1) - 420.0 * mm2in) < tol)
4856 ptype = "a3";
4857 else if (std::abs (sz(0) - 210.0 * mm2in)
4858 + std::abs (sz(1) - 297.0 * mm2in) < tol)
4859 ptype = "a4";
4860 else if (std::abs (sz(0) - 148.0 * mm2in)
4861 + std::abs (sz(1) - 210.0 * mm2in) < tol)
4862 ptype = "a5";
4863 else if (std::abs (sz(0) - 1029.0 * mm2in)
4864 + std::abs (sz(1) - 1456.0 * mm2in) < tol)
4865 ptype = "b0";
4866 else if (std::abs (sz(0) - 728.0 * mm2in)
4867 + std::abs (sz(1) - 1028.0 * mm2in) < tol)
4868 ptype = "b1";
4869 else if (std::abs (sz(0) - 514.0 * mm2in)
4870 + std::abs (sz(1) - 728.0 * mm2in) < tol)
4871 ptype = "b2";
4872 else if (std::abs (sz(0) - 364.0 * mm2in)
4873 + std::abs (sz(1) - 514.0 * mm2in) < tol)
4874 ptype = "b3";
4875 else if (std::abs (sz(0) - 257.0 * mm2in)
4876 + std::abs (sz(1) - 364.0 * mm2in) < tol)
4877 ptype = "b4";
4878 else if (std::abs (sz(0) - 182.0 * mm2in)
4879 + std::abs (sz(1) - 257.0 * mm2in) < tol)
4880 ptype = "b5";
4881 else if (std::abs (sz(0) - 9.0)
4882 + std::abs (sz(1) - 12.0) < tol)
4883 ptype = "arch-a";
4884 else if (std::abs (sz(0) - 12.0)
4885 + std::abs (sz(1) - 18.0) < tol)
4886 ptype = "arch-b";
4887 else if (std::abs (sz(0) - 18.0)
4888 + std::abs (sz(1) - 24.0) < tol)
4889 ptype = "arch-c";
4890 else if (std::abs (sz(0) - 24.0)
4891 + std::abs (sz(1) - 36.0) < tol)
4892 ptype = "arch-d";
4893 else if (std::abs (sz(0) - 36.0)
4894 + std::abs (sz(1) - 48.0) < tol)
4895 ptype = "arch-e";
4896 else if (std::abs (sz(0) - 8.5)
4897 + std::abs (sz(1) - 11.0) < tol)
4898 ptype = "a";
4899 else if (std::abs (sz(0) - 11.0)
4900 + std::abs (sz(1) - 17.0) < tol)
4901 ptype = "b";
4902 else if (std::abs (sz(0) - 17.0)
4903 + std::abs (sz(1) - 22.0) < tol)
4904 ptype = "c";
4905 else if (std::abs (sz(0) - 22.0)
4906 + std::abs (sz(1) - 34.0) < tol)
4907 ptype = "d";
4908 else if (std::abs (sz(0) - 34.0)
4909 + std::abs (sz(1) - 43.0) < tol)
4910 ptype = "e";
4911 // Call papertype.set rather than set_papertype to avoid loops between
4912 // update_papersize and update_papertype
4913 m_papertype.set (ptype);
4914 }
4915 if (punits == "centimeters")
4916 {
4917 sz(0) *= 2.54;
4918 sz(1) *= 2.54;
4919 }
4920 else if (punits == "points")
4921 {
4922 sz(0) *= 72.0;
4923 sz(1) *= 72.0;
4924 }
4925 if (get_paperorientation () == "landscape")
4926 {
4927 std::swap (sz(0), sz(1));
4928 m_papersize.set (octave_value (sz));
4929 }
4930
4931 if (m_paperpositionmode.is ("auto"))
4932 m_paperposition.set (get_auto_paperposition ());
4933}
4934
4935/*
4936%!test
4937%! hf = figure ("visible", "off");
4938%! unwind_protect
4939%! set (hf, "paperunits", "inches");
4940%! set (hf, "papersize", [5, 4]);
4941%! set (hf, "paperunits", "points");
4942%! assert (get (hf, "papersize"), [5, 4] * 72, 1);
4943%! papersize = get (hf, "papersize");
4944%! set (hf, "papersize", papersize + 1);
4945%! set (hf, "papersize", papersize);
4946%! assert (get (hf, "papersize"), [5, 4] * 72, 1);
4947%! unwind_protect_cleanup
4948%! close (hf);
4949%! end_unwind_protect
4950
4951%!test
4952%! hf = figure ("visible", "off");
4953%! unwind_protect
4954%! set (hf, "paperunits", "inches");
4955%! set (hf, "papersize", [5, 4]);
4956%! set (hf, "paperunits", "centimeters");
4957%! assert (get (hf, "papersize"), [5, 4] * 2.54, 2.54/72);
4958%! papersize = get (hf, "papersize");
4959%! set (hf, "papersize", papersize + 1);
4960%! set (hf, "papersize", papersize);
4961%! assert (get (hf, "papersize"), [5, 4] * 2.54, 2.54/72);
4962%! unwind_protect_cleanup
4963%! close (hf);
4964%! end_unwind_protect
4965*/
4966
4967void
4968figure::properties::update_paperorientation (void)
4969{
4970 std::string porient = get_paperorientation ();
4971 Matrix sz = get_papersize ().matrix_value ();
4972 if ((sz(0) > sz(1) && porient == "portrait")
4973 || (sz(0) < sz(1) && porient == "landscape"))
4974 {
4975 std::swap (sz(0), sz(1));
4976 // Call papertype.set rather than set_papertype to avoid loops
4977 // between update_papersize and update_papertype
4978 m_papersize.set (octave_value (sz));
4979 }
4980
4981 if (m_paperpositionmode.is ("auto"))
4982 m_paperposition.set (get_auto_paperposition ());
4983}
4984
4985/*
4986%!test
4987%! hf = figure ("visible", "off");
4988%! unwind_protect
4989%! tol = 100 * eps ();
4990%! ## UPPER case and MiXed case is part of test and should not be changed.
4991%! set (hf, "paperorientation", "PORTRAIT");
4992%! set (hf, "paperunits", "inches");
4993%! set (hf, "papertype", "USletter");
4994%! assert (get (hf, "papersize"), [8.5, 11.0], tol);
4995%! set (hf, "paperorientation", "Landscape");
4996%! assert (get (hf, "papersize"), [11.0, 8.5], tol);
4997%! set (hf, "paperunits", "centimeters");
4998%! assert (get (hf, "papersize"), [11.0, 8.5] * 2.54, tol);
4999%! set (hf, "papertype", "a4");
5000%! assert (get (hf, "papersize"), [29.7, 21.0], tol);
5001%! set (hf, "paperunits", "inches", "papersize", [8.5, 11.0]);
5002%! assert (get (hf, "papertype"), "usletter");
5003%! assert (get (hf, "paperorientation"), "portrait");
5004%! set (hf, "papersize", [11.0, 8.5]);
5005%! assert (get (hf, "papertype"), "usletter");
5006%! assert (get (hf, "paperorientation"), "landscape");
5007%! unwind_protect_cleanup
5008%! close (hf);
5009%! end_unwind_protect
5010*/
5011
5012void
5013figure::properties::set_units (const octave_value& val)
5014{
5015 caseless_str old_units = get_units ();
5016
5017 if (m_units.set (val, true))
5018 {
5019 update_units (old_units);
5020 mark_modified ();
5021 }
5022}
5023
5024void
5025figure::properties::update_units (const caseless_str& old_units)
5026{
5027 m_position.set (convert_position (get_position ().matrix_value (),
5028 old_units, get_units (),
5029 screen_size_pixels ()), false);
5030}
5031
5032/*
5033%!test
5034%! hf = figure ("visible", "off");
5035%! old_units = get (0, "units");
5036%! unwind_protect
5037%! set (0, "units", "pixels");
5038%! rsz = get (0, "screensize");
5039%! set (gcf (), "units", "pixels");
5040%! fsz = get (gcf (), "position");
5041%! set (gcf (), "units", "normalized");
5042%! pos = get (gcf (), "position");
5043%! assert (pos, (fsz - [1, 1, 0, 0]) ./ rsz([3, 4, 3, 4]));
5044%! unwind_protect_cleanup
5045%! close (hf);
5046%! set (0, "units", old_units);
5047%! end_unwind_protect
5048*/
5049
5050std::string
5051figure::properties::get_title (void) const
5052{
5053 std::string title;
5054 if (! get_number ().isempty () && is_numbertitle ())
5055 {
5056 std::ostringstream os;
5057 std::string nm = get_name ();
5058
5059 os << "Figure " << m___myhandle__.value ();
5060 if (! nm.empty ())
5061 os << ": " << get_name ();
5062
5063 title = os.str ();
5064 }
5065 else
5066 title = get_name ();
5067
5068 // Qt will use QCoreApplication name (set in main-window.cc)
5069 // if the name is empty, so force blank.
5070 if (title.empty ())
5071 title = " ";
5072
5073 return title;
5074}
5075
5077figure::get_default (const caseless_str& name) const
5078{
5079 octave_value retval = m_default_properties.lookup (name);
5080
5081 if (retval.is_undefined ())
5082 {
5083 graphics_handle parent_h = get_parent ();
5084
5085 gh_manager& gh_mgr = octave::__get_gh_manager__ ("figure::get_default");
5086
5087 graphics_object parent_go = gh_mgr.get_object (parent_h);
5088
5089 retval = parent_go.get_default (name);
5090 }
5091
5092 return retval;
5093}
5094
5095void
5096figure::reset_default_properties (void)
5097{
5098 // empty list of local defaults
5099 m_default_properties = property_list ();
5100 property_list::pval_map_type plist = m_properties.factory_defaults ();
5101
5102 plist.erase ("units");
5103 plist.erase ("position");
5104 plist.erase ("outerposition");
5105 plist.erase ("paperunits");
5106 plist.erase ("paperposition");
5107 plist.erase ("windowstyle");
5108
5109 remove_all_listeners ();
5110 xreset_default_properties (get_handle (), plist);
5111}
5112
5113// ---------------------------------------------------------------------
5114
5115void
5116axes::properties::init (void)
5117{
5118 m_position.add_constraint (dim_vector (1, 4));
5119 m_outerposition.add_constraint (dim_vector (1, 4));
5120 m_tightinset.add_constraint (dim_vector (1, 4));
5121 m_looseinset.add_constraint (dim_vector (1, 4));
5122 m_colororder.add_constraint (dim_vector (-1, 3));
5123 m_dataaspectratio.add_constraint (3);
5124 m_dataaspectratio.add_constraint ("min", 0, false);
5125 m_dataaspectratio.add_constraint (FINITE);
5126 m_plotboxaspectratio.add_constraint (3);
5127 m_plotboxaspectratio.add_constraint ("min", 0, false);
5128 m_plotboxaspectratio.add_constraint (FINITE);
5129 // FIXME: Should these use dimension vectors? Currently can set 'xlim' to
5130 // any matrix size, but only first two elements are used.
5131 m_alim.add_constraint (2);
5132 m_alim.add_constraint (NOT_NAN);
5133 m_clim.add_constraint (2);
5134 m_clim.add_constraint (NOT_NAN);
5135 m_xlim.add_constraint (2);
5136 m_xlim.add_constraint (NOT_NAN);
5137 m_ylim.add_constraint (2);
5138 m_ylim.add_constraint (NOT_NAN);
5139 m_zlim.add_constraint (2);
5140 m_zlim.add_constraint (NOT_NAN);
5141 m_xtick.add_constraint (dim_vector (1, -1));
5142 m_xtick.add_constraint (FINITE);
5143 m_ytick.add_constraint (dim_vector (1, -1));
5144 m_ytick.add_constraint (FINITE);
5145 m_ztick.add_constraint (dim_vector (1, -1));
5146 m_ztick.add_constraint (FINITE);
5147 m_ticklength.add_constraint (dim_vector (1, 2));
5148 Matrix vw (1, 2, 0);
5149 vw(1) = 90;
5150 m_view = vw;
5151 m_view.add_constraint (dim_vector (1, 2));
5152 m_cameraposition.add_constraint (3);
5153 m_cameraposition.add_constraint (FINITE);
5154 m_cameratarget.add_constraint (3);
5155 m_cameratarget.add_constraint (FINITE);
5156 Matrix upv (1, 3, 0.0);
5157 upv(2) = 1.0;
5158 m_cameraupvector = upv;
5159 m_cameraupvector.add_constraint (3);
5160 m_cameraupvector.add_constraint (FINITE);
5161 m_cameraviewangle.add_constraint (FINITE);
5162 m_currentpoint.add_constraint (dim_vector (2, 3));
5163
5164 // Range constraints for double properties
5165 m_fontsize.add_constraint ("min", 0.0, false);
5166 m_gridalpha.add_constraint ("min", 0.0, true);
5167 m_gridalpha.add_constraint ("max", 1.0, true);
5168 m_labelfontsizemultiplier.add_constraint ("min", 0.0, false);
5169 m_linewidth.add_constraint ("min", 0.0, false);
5170 m_minorgridalpha.add_constraint ("min", 0.0, true);
5171 m_minorgridalpha.add_constraint ("max", 1.0, true);
5172 m_titlefontsizemultiplier.add_constraint ("min", 0.0, false);
5173
5174 // No constraints for hidden transform properties
5175 update_font ();
5176
5177 m_x_zlim.resize (1, 2);
5178
5179 m_sx = "linear";
5180 m_sy = "linear";
5181 m_sz = "linear";
5182
5183 calc_ticklabels (m_xtick, m_xticklabel, m_xscale.is ("log"),
5184 xaxislocation_is ("origin"),
5185 m_yscale.is ("log") ? 2 : (yaxislocation_is ("origin") ? 0 :
5186 (yaxislocation_is ("left") ? -1 : 1)), m_xlim);
5187 calc_ticklabels (m_ytick, m_yticklabel, m_yscale.is ("log"),
5188 yaxislocation_is ("origin"),
5189 m_xscale.is ("log") ? 2 : (xaxislocation_is ("origin") ? 0 :
5190 (xaxislocation_is ("bottom") ? -1 : 1)), m_ylim);
5191 calc_ticklabels (m_ztick, m_zticklabel, m_zscale.is ("log"), false, 2, m_zlim);
5192
5193 xset (m_xlabel.handle_value (), "handlevisibility", "off");
5194 xset (m_ylabel.handle_value (), "handlevisibility", "off");
5195 xset (m_zlabel.handle_value (), "handlevisibility", "off");
5196 xset (m_title.handle_value (), "handlevisibility", "off");
5197
5198 xset (m_xlabel.handle_value (), "horizontalalignment", "center");
5199 xset (m_xlabel.handle_value (), "horizontalalignmentmode", "auto");
5200 xset (m_ylabel.handle_value (), "horizontalalignment", "center");
5201 xset (m_ylabel.handle_value (), "horizontalalignmentmode", "auto");
5202 xset (m_zlabel.handle_value (), "horizontalalignment", "right");
5203 xset (m_zlabel.handle_value (), "horizontalalignmentmode", "auto");
5204 xset (m_title.handle_value (), "horizontalalignment", "center");
5205 xset (m_title.handle_value (), "horizontalalignmentmode", "auto");
5206
5207 xset (m_xlabel.handle_value (), "verticalalignment", "top");
5208 xset (m_xlabel.handle_value (), "verticalalignmentmode", "auto");
5209 xset (m_ylabel.handle_value (), "verticalalignment", "bottom");
5210 xset (m_ylabel.handle_value (), "verticalalignmentmode", "auto");
5211 xset (m_title.handle_value (), "verticalalignment", "bottom");
5212 xset (m_title.handle_value (), "verticalalignmentmode", "auto");
5213
5214 xset (m_ylabel.handle_value (), "rotation", 90.0);
5215 xset (m_ylabel.handle_value (), "rotationmode", "auto");
5216
5217 xset (m_zlabel.handle_value (), "visible", "off");
5218
5219 xset (m_xlabel.handle_value (), "clipping", "off");
5220 xset (m_ylabel.handle_value (), "clipping", "off");
5221 xset (m_zlabel.handle_value (), "clipping", "off");
5222 xset (m_title.handle_value (), "clipping", "off");
5223
5224 xset (m_xlabel.handle_value (), "__autopos_tag__", "xlabel");
5225 xset (m_ylabel.handle_value (), "__autopos_tag__", "ylabel");
5226 xset (m_zlabel.handle_value (), "__autopos_tag__", "zlabel");
5227 xset (m_title.handle_value (), "__autopos_tag__", "title");
5228
5229 double fs = m_labelfontsizemultiplier.double_value () *
5230 m_fontsize.double_value ();
5231 xset (m_xlabel.handle_value (), "fontsize", octave_value (fs));
5232 xset (m_ylabel.handle_value (), "fontsize", octave_value (fs));
5233 xset (m_zlabel.handle_value (), "fontsize", octave_value (fs));
5234 fs = m_titlefontsizemultiplier.double_value () * m_fontsize.double_value ();
5235 xset (m_title.handle_value (), "fontsize", octave_value (fs));
5236 xset (m_title.handle_value (), "fontweight", m_titlefontweight.get ());
5237
5238 adopt (m_xlabel.handle_value ());
5239 adopt (m_ylabel.handle_value ());
5240 adopt (m_zlabel.handle_value ());
5241 adopt (m_title.handle_value ());
5242
5243 Matrix tlooseinset = default_axes_position ();
5244 tlooseinset(2) = 1-tlooseinset(0)-tlooseinset(2);
5245 tlooseinset(3) = 1-tlooseinset(1)-tlooseinset(3);
5246 m_looseinset = tlooseinset;
5247}
5248
5249/*
5250## Test validation of axes double properties range
5251%!test
5252%! hf = figure ("visible", "off");
5253%! unwind_protect
5254%! hax = axes ("parent", hf);
5255%! try
5256%! set (hax, "linewidth", -1);
5257%! catch
5258%! err = lasterr ();
5259%! end_try_catch
5260%! assert (err, 'set: "linewidth" must be greater than 0');
5261%! try
5262%! set (hax, "minorgridalpha", 1.5);
5263%! catch
5264%! err = lasterr ();
5265%! end_try_catch
5266%! assert (err, 'set: "minorgridalpha" must be less than or equal to 1');
5267%! unwind_protect_cleanup
5268%! delete (hf);
5269%! end_unwind_protect
5270*/
5271
5272Matrix
5273axes::properties::calc_tightbox (const Matrix& init_pos)
5274{
5275 Matrix pos = init_pos;
5276
5277 gh_manager& gh_mgr
5278 = octave::__get_gh_manager__ ("axes::properties::calc_tightbox");
5279
5280 graphics_object go = gh_mgr.get_object (get_parent ());
5281
5282 Matrix parent_bb = go.get_properties ().get_boundingbox (true);
5283
5284 // FIXME: The layout should be clean at this stage and we should not have to
5285 // update ticks and labels positions here again. See bug #48718.
5286 update_ticklength ();
5287
5288 Matrix ext = get_extent (true, true);
5289 ext(1) = parent_bb(3) - ext(1) - ext(3);
5290 ext(0)++;
5291 ext(1)++;
5292 ext = convert_position (ext, "pixels", get_units (),
5293 parent_bb.extract_n (0, 2, 1, 2));
5294 if (ext(0) < pos(0))
5295 {
5296 pos(2) += pos(0)-ext(0);
5297 pos(0) = ext(0);
5298 }
5299 if (ext(0)+ext(2) > pos(0)+pos(2))
5300 pos(2) = ext(0)+ext(2)-pos(0);
5301
5302 if (ext(1) < pos(1))
5303 {
5304 pos(3) += pos(1)-ext(1);
5305 pos(1) = ext(1);
5306 }
5307 if (ext(1)+ext(3) > pos(1)+pos(3))
5308 pos(3) = ext(1)+ext(3)-pos(1);
5309
5310 return pos;
5311}
5312
5313void
5314axes::properties::sync_positions (void)
5315{
5316 // First part is equivalent to 'update_tightinset ()'
5317 if (m_positionconstraint.is ("innerposition"))
5318 update_position ();
5319 else
5320 update_outerposition ();
5321 caseless_str old_units = get_units ();
5322 set_units ("normalized");
5323 Matrix pos = m_position.get ().matrix_value ();
5324 Matrix outpos = m_outerposition.get ().matrix_value ();
5325 Matrix tightpos = calc_tightbox (pos);
5326 Matrix tinset (1, 4, 1.0);
5327 tinset(0) = pos(0)-tightpos(0);
5328 tinset(1) = pos(1)-tightpos(1);
5329 tinset(2) = tightpos(0)+tightpos(2)-pos(0)-pos(2);
5330 tinset(3) = tightpos(1)+tightpos(3)-pos(1)-pos(3);
5331 m_tightinset = tinset;
5332 set_units (old_units);
5333 update_transform ();
5334 if (m_positionconstraint.is ("innerposition"))
5335 update_position ();
5336 else
5337 update_outerposition ();
5338}
5339
5340/*
5341%!testif HAVE_OPENGL, HAVE_QT; have_window_system () && any (strcmp ("qt", available_graphics_toolkits ()))
5342%! hf = figure ("visible", "off");
5343%! graphics_toolkit (hf, "qt");
5344%! unwind_protect
5345%! subplot (2,1,1); plot (rand (10,1)); subplot (2,1,2); plot (rand (10,1));
5346%! hax = findall (gcf (), "type", "axes");
5347%! positions = cell2mat (get (hax, "position"));
5348%! outerpositions = cell2mat (get (hax, "outerposition"));
5349%! looseinsets = cell2mat (get (hax, "looseinset"));
5350%! tightinsets = cell2mat (get (hax, "tightinset"));
5351%! subplot (2,1,1); plot (rand (10,1)); subplot (2,1,2); plot (rand (10,1));
5352%! hax = findall (gcf (), "type", "axes");
5353%! assert (cell2mat (get (hax, "position")), positions, 1e-4);
5354%! assert (cell2mat (get (hax, "outerposition")), outerpositions, 1e-4);
5355%! assert (cell2mat (get (hax, "looseinset")), looseinsets, 1e-4);
5356%! assert (cell2mat (get (hax, "tightinset")), tightinsets, 1e-4);
5357%! unwind_protect_cleanup
5358%! close (hf);
5359%! end_unwind_protect
5360
5361%!testif HAVE_OPENGL, HAVE_QT; have_window_system () && any (strcmp ("qt", available_graphics_toolkits ()))
5362%! hf = figure ("visible", "off");
5363%! graphics_toolkit (hf, "qt");
5364%! fpos = get (hf, "position");
5365%! unwind_protect
5366%! plot (rand (3));
5367%! position = get (gca, "position");
5368%! outerposition = get (gca, "outerposition");
5369%! looseinset = get (gca, "looseinset");
5370%! tightinset = get (gca, "tightinset");
5371%! set (hf, "position", [fpos(1:2), 2*fpos(3:4)]);
5372%! set (hf, "position", fpos);
5373%! assert (get (gca, "outerposition"), outerposition, 0.001);
5374%! assert (get (gca, "position"), position, 0.001);
5375%! assert (get (gca, "looseinset"), looseinset, 0.001);
5376%! assert (get (gca, "tightinset"), tightinset, 0.001);
5377%! unwind_protect_cleanup
5378%! close (hf);
5379%! end_unwind_protect
5380
5381%!testif HAVE_OPENGL, HAVE_QT; have_window_system () && any (strcmp ("qt", available_graphics_toolkits ()))
5382%! hf = figure ("visible", "off");
5383%! graphics_toolkit (hf, "qt");
5384%! fpos = get (hf, "position");
5385%! set (gca, "positionconstraint", "innerposition");
5386%! unwind_protect
5387%! plot (rand (3));
5388%! position = get (gca, "position");
5389%! outerposition = get (gca, "outerposition");
5390%! looseinset = get (gca, "looseinset");
5391%! tightinset = get (gca, "tightinset");
5392%! set (hf, "position", [fpos(1:2), 2*fpos(3:4)]);
5393%! set (hf, "position", fpos);
5394%! assert (get (gca, "position"), position, 0.001);
5395%! assert (get (gca, "outerposition"), outerposition, 0.001);
5396%! assert (get (gca, "looseinset"), looseinset, 0.001);
5397%! assert (get (gca, "tightinset"), tightinset, 0.001);
5398%! unwind_protect_cleanup
5399%! close (hf);
5400%! end_unwind_protect
5401*/
5402
5403void
5404axes::properties::set_text_child (handle_property& hp,
5405 const std::string& who,
5406 const octave_value& v)
5407{
5408 if (v.is_string ())
5409 {
5410 xset (hp.handle_value (), "string", v);
5411 return;
5412 }
5413
5414 graphics_handle val;
5415
5416 gh_manager& gh_mgr
5417 = octave::__get_gh_manager__ ("axes::properties::set_text_child");
5418
5419 graphics_object go = gh_mgr.get_object (gh_mgr.lookup (v));
5420
5421 if (go.isa ("text"))
5422 val = octave::reparent (v, "set", who, m___myhandle__, false);
5423 else
5424 {
5425 std::string cname = v.class_name ();
5426
5427 error ("set: expecting text graphics object or character string for %s property, found %s",
5428 who.c_str (), cname.c_str ());
5429 }
5430
5431 xset (val, "handlevisibility", "off");
5432
5433 gh_mgr.free (hp.handle_value ());
5434
5435 hp = val;
5436
5437 adopt (hp.handle_value ());
5438}
5439
5440void
5441axes::properties::set_xlabel (const octave_value& v)
5442{
5443 set_text_child (m_xlabel, "xlabel", v);
5444 xset (m_xlabel.handle_value (), "positionmode", "auto");
5445 xset (m_xlabel.handle_value (), "rotationmode", "auto");
5446 xset (m_xlabel.handle_value (), "horizontalalignmentmode", "auto");
5447 xset (m_xlabel.handle_value (), "verticalalignmentmode", "auto");
5448 xset (m_xlabel.handle_value (), "clipping", "off");
5449 xset (m_xlabel.handle_value (), "color", get_xcolor ());
5450 xset (m_xlabel.handle_value (), "__autopos_tag__", "xlabel");
5451 update_xlabel_position ();
5452}
5453
5454void
5455axes::properties::set_ylabel (const octave_value& v)
5456{
5457 set_text_child (m_ylabel, "ylabel", v);
5458 xset (m_ylabel.handle_value (), "positionmode", "auto");
5459 xset (m_ylabel.handle_value (), "rotationmode", "auto");
5460 xset (m_ylabel.handle_value (), "horizontalalignmentmode", "auto");
5461 xset (m_ylabel.handle_value (), "verticalalignmentmode", "auto");
5462 xset (m_ylabel.handle_value (), "clipping", "off");
5463 xset (m_ylabel.handle_value (), "color", get_ycolor ());
5464 xset (m_ylabel.handle_value (), "__autopos_tag__", "ylabel");
5465 update_ylabel_position ();
5466}
5467
5468void
5469axes::properties::set_zlabel (const octave_value& v)
5470{
5471 set_text_child (m_zlabel, "zlabel", v);
5472 xset (m_zlabel.handle_value (), "positionmode", "auto");
5473 xset (m_zlabel.handle_value (), "rotationmode", "auto");
5474 xset (m_zlabel.handle_value (), "horizontalalignmentmode", "auto");
5475 xset (m_zlabel.handle_value (), "verticalalignmentmode", "auto");
5476 xset (m_zlabel.handle_value (), "clipping", "off");
5477 xset (m_zlabel.handle_value (), "color", get_zcolor ());
5478 xset (m_zlabel.handle_value (), "__autopos_tag__", "zlabel");
5479 update_zlabel_position ();
5480}
5481
5482void
5483axes::properties::set_title (const octave_value& v)
5484{
5485 set_text_child (m_title, "title", v);
5486 xset (m_title.handle_value (), "positionmode", "auto");
5487 xset (m_title.handle_value (), "horizontalalignment", "center");
5488 xset (m_title.handle_value (), "horizontalalignmentmode", "auto");
5489 xset (m_title.handle_value (), "verticalalignment", "bottom");
5490 xset (m_title.handle_value (), "verticalalignmentmode", "auto");
5491 xset (m_title.handle_value (), "clipping", "off");
5492 xset (m_title.handle_value (), "__autopos_tag__", "title");
5493 update_title_position ();
5494}
5495
5496void
5497axes::properties::set_defaults (base_graphics_object& bgo,
5498 const std::string& mode)
5499{
5500 // FIXME: Should this have all properties in it?
5501 // Including ones we do don't implement?
5502
5503 // FIXME: This function is probably never called without mode == "reset"
5504 // Check that this is the case with an assert statement (1/6/2017).
5505 // If there are reports of problems then figure out what code is
5506 // calling it with the mode set to something else.
5507 assert (mode == "reset");
5508
5509 Matrix tlim (1, 2, 0.0);
5510 tlim(1) = 1;
5511 m_alim = tlim;
5512 m_xlim = tlim;
5513 m_ylim = tlim;
5514 m_zlim = tlim;
5515
5516 m_alimmode = "auto";
5517 m_climmode = "auto";
5518 m_xlimmode = "auto";
5519 m_ylimmode = "auto";
5520 m_zlimmode = "auto";
5521
5522 m_ambientlightcolor = Matrix (1, 3, 1.0);
5523
5524 m_box = "off";
5525 m_boxstyle = "back";
5526
5527 // Note: camera properties (not mode) will be set in update_transform
5528 m_camerapositionmode = "auto";
5529 m_cameratargetmode = "auto";
5530 m_cameraupvectormode = "auto";
5531 m_cameraviewanglemode = "auto";
5532
5533 Matrix cl (1, 2, 0.0);
5534 cl(1) = 1;
5535 m_clim = cl;
5536
5537 m_clippingstyle = "3dbox";
5538
5539 m_color = color_values ("white");
5540 m_colororder = default_colororder ();
5541 m_colororderindex = 1.0;
5542
5543 // Note: dataspectratio (not mode) will be set through update_aspectratios
5544 m_dataaspectratiomode = "auto";
5545
5546 m_fontangle = "normal";
5547 m_fontname = OCTAVE_DEFAULT_FONTNAME;
5548 m_fontsize = 10;
5549 m_fontunits = "points";
5550 m_fontsmoothing = "on";
5551 m_fontweight = "normal";
5552
5553 m_gridalpha = 0.15;
5554 m_gridalphamode = "auto";
5555 m_gridcolor = color_values (0.15, 0.15, 0.15);
5556 m_gridcolormode = "auto";
5557 m_gridlinestyle = "-";
5558
5559 m_labelfontsizemultiplier = 1.1;
5560
5561 m_layer = "bottom";
5562
5563 m_linestyleorder = "-";
5564 m_linestyleorderindex = 1.0;
5565
5566 m_linewidth = 0.5;
5567
5568 m_minorgridalpha = 0.25;
5569 m_minorgridalphamode = "auto";
5570 m_minorgridcolor = color_values (0.1, 0.1, 0.1);
5571 m_minorgridcolormode = "auto";
5572 m_minorgridlinestyle = ":";
5573
5574 m_nextplot = "replace";
5575
5576 // Note: plotboxaspectratio will be set through update_aspectratios
5577 m_plotboxaspectratiomode = "auto";
5578 m_projection = "orthographic";
5579
5580 m_sortmethod = "depth";
5581
5582 m_tickdir = "in";
5583 m_tickdirmode = "auto";
5584 m_ticklabelinterpreter = "tex";
5585 m_ticklength = default_axes_ticklength ();
5586
5587 m_tightinset = Matrix (1, 4, 0.0);
5588
5589 m_titlefontsizemultiplier = 1.1;
5590 m_titlefontweight = "bold";
5591
5592 Matrix tview (1, 2, 0.0);
5593 tview(1) = 90;
5594 m_view = tview;
5595
5596 m_xaxislocation = "bottom";
5597
5598 m_xcolor = color_values (0.15, 0.15, 0.15);
5599 m_xcolormode = "auto";
5600 m_xdir = "normal";
5601 m_xgrid = "off";
5602 m_xminorgrid = "off";
5603 m_xminortick = "off";
5604 m_xscale = "linear";
5605 m_xtick = Matrix ();
5606 m_xticklabel = "";
5607 m_xticklabelmode = "auto";
5608 m_xticklabelrotation = 0.0;
5609 m_xtickmode = "auto";
5610
5611 m_yaxislocation = "left";
5612
5613 m_ycolor = color_values (0.15, 0.15, 0.15);
5614 m_ycolormode = "auto";
5615 m_ydir = "normal";
5616 m_ygrid = "off";
5617 m_yminorgrid = "off";
5618 m_yminortick = "off";
5619 m_yscale = "linear";
5620 m_ytick = Matrix ();
5621 m_yticklabel = "";
5622 m_yticklabelmode = "auto";
5623 m_yticklabelrotation = 0.0;
5624 m_ytickmode = "auto";
5625
5626 m_zcolor = color_values (0.15, 0.15, 0.15);
5627 m_zcolormode = "auto";
5628 m_zdir = "normal";
5629 m_zgrid = "off";
5630 m_zminorgrid = "off";
5631 m_zminortick = "off";
5632 m_zscale = "linear";
5633 m_ztick = Matrix ();
5634 m_zticklabel = "";
5635 m_zticklabelmode = "auto";
5636 m_zticklabelrotation = 0.0;
5637 m_ztickmode = "auto";
5638
5639 m_sx = "linear";
5640 m_sy = "linear";
5641 m_sz = "linear";
5642
5643 m_visible = "on";
5644
5645 gh_manager& gh_mgr
5646 = octave::__get_gh_manager__ ("axes::properties::set_defaults");
5647
5648 graphics_object go = gh_mgr.get_object (m_xlabel.handle_value ());
5649 go.reset_default_properties ();
5650 go = gh_mgr.get_object (m_ylabel.handle_value ());
5651 go.reset_default_properties ();
5652 go = gh_mgr.get_object (m_zlabel.handle_value ());
5653 go.reset_default_properties ();
5654 go = gh_mgr.get_object (m_title.handle_value ());
5655 go.reset_default_properties ();
5656
5657 xset (m_xlabel.handle_value (), "handlevisibility", "off");
5658 xset (m_ylabel.handle_value (), "handlevisibility", "off");
5659 xset (m_zlabel.handle_value (), "handlevisibility", "off");
5660 xset (m_title.handle_value (), "handlevisibility", "off");
5661
5662 xset (m_xlabel.handle_value (), "horizontalalignment", "center");
5663 xset (m_xlabel.handle_value (), "horizontalalignmentmode", "auto");
5664 xset (m_ylabel.handle_value (), "horizontalalignment", "center");
5665 xset (m_ylabel.handle_value (), "horizontalalignmentmode", "auto");
5666 xset (m_zlabel.handle_value (), "horizontalalignment", "right");
5667 xset (m_zlabel.handle_value (), "horizontalalignmentmode", "auto");
5668 xset (m_title.handle_value (), "horizontalalignment", "center");
5669 xset (m_title.handle_value (), "horizontalalignmentmode", "auto");
5670
5671 xset (m_xlabel.handle_value (), "verticalalignment", "top");
5672 xset (m_xlabel.handle_value (), "verticalalignmentmode", "auto");
5673 xset (m_ylabel.handle_value (), "verticalalignment", "bottom");
5674 xset (m_ylabel.handle_value (), "verticalalignmentmode", "auto");
5675 xset (m_title.handle_value (), "verticalalignment", "bottom");
5676 xset (m_title.handle_value (), "verticalalignmentmode", "auto");
5677
5678 xset (m_ylabel.handle_value (), "rotation", 90.0);
5679 xset (m_ylabel.handle_value (), "rotationmode", "auto");
5680
5681 xset (m_zlabel.handle_value (), "visible", "off");
5682
5683 xset (m_xlabel.handle_value (), "clipping", "off");
5684 xset (m_ylabel.handle_value (), "clipping", "off");
5685 xset (m_zlabel.handle_value (), "clipping", "off");
5686 xset (m_title.handle_value (), "clipping", "off");
5687
5688 xset (m_xlabel.handle_value (), "__autopos_tag__", "xlabel");
5689 xset (m_ylabel.handle_value (), "__autopos_tag__", "ylabel");
5690 xset (m_zlabel.handle_value (), "__autopos_tag__", "zlabel");
5691 xset (m_title.handle_value (), "__autopos_tag__", "title");
5692
5693 double fs;
5694 fs = m_labelfontsizemultiplier.double_value () * m_fontsize.double_value ();
5695 xset (m_xlabel.handle_value (), "fontsize", octave_value (fs));
5696 xset (m_ylabel.handle_value (), "fontsize", octave_value (fs));
5697 xset (m_zlabel.handle_value (), "fontsize", octave_value (fs));
5698 fs = m_titlefontsizemultiplier.double_value () * m_fontsize.double_value ();
5699 xset (m_title.handle_value (), "fontsize", octave_value (fs));
5700 xset (m_title.handle_value (), "fontweight", m_titlefontweight.get ());
5701
5702 update_transform ();
5703 sync_positions ();
5704 override_defaults (bgo);
5705}
5706
5708axes::properties::get_colormap (void) const
5709{
5710 if (m___colormap__.get ().isempty ())
5711 {
5712 gh_manager& gh_mgr
5713 = octave::__get_gh_manager__ ("axes::properties::get_colormap");
5714
5715 graphics_object go (gh_mgr.get_object (get___myhandle__ ()));
5716 graphics_object go_f (go.get_ancestor ("figure"));
5717 figure::properties& figure_props
5718 = reinterpret_cast<figure::properties&> (go_f.get_properties ());
5719 return figure_props.get_colormap ();
5720 }
5721
5722 return get___colormap__ ();
5723}
5724
5725void
5726axes::properties::delete_text_child (handle_property& hp, bool from_root)
5727{
5728 graphics_handle h = hp.handle_value ();
5729
5730 gh_manager& gh_mgr
5731 = octave::__get_gh_manager__ ("axes::properties::delete_text_child");
5732
5733 if (h.ok ())
5734 {
5735 graphics_object go = gh_mgr.get_object (h);
5736
5737 if (go.valid_object ())
5738 gh_mgr.free (h, from_root);
5739 }
5740
5741 // FIXME: is it necessary to check whether the axes object is
5742 // being deleted now? I think this function is only called when an
5743 // individual child object is delete and not when the parent axes
5744 // object is deleted.
5745
5746 if (! is_beingdeleted ())
5747 {
5748 hp = gh_mgr.make_graphics_handle ("text", m___myhandle__, false, false);
5749
5750 xset (hp.handle_value (), "handlevisibility", "off");
5751
5752 adopt (hp.handle_value ());
5753 }
5754}
5755
5756void
5757axes::properties::remove_child (const graphics_handle& h, bool from_root)
5758{
5759 gh_manager& gh_mgr
5760 = octave::__get_gh_manager__ ("axes::properties::remove_child");
5761
5762 graphics_object go = gh_mgr.get_object (h);
5763
5764 if (m_xlabel.handle_value ().ok () && h == m_xlabel.handle_value ())
5765 {
5766 delete_text_child (m_xlabel, from_root);
5767 update_xlabel_position ();
5768 }
5769 else if (m_ylabel.handle_value ().ok () && h == m_ylabel.handle_value ())
5770 {
5771 delete_text_child (m_ylabel, from_root);
5772 update_ylabel_position ();
5773 }
5774 else if (m_zlabel.handle_value ().ok () && h == m_zlabel.handle_value ())
5775 {
5776 delete_text_child (m_zlabel, from_root);
5777 update_zlabel_position ();
5778 }
5779 else if (m_title.handle_value ().ok () && h == m_title.handle_value ())
5780 {
5781 delete_text_child (m_title, from_root);
5782 update_title_position ();
5783 }
5784 else if (get_num_lights () > 0 && go.isa ("light")
5785 && go.get_properties ().is_visible ())
5786 decrease_num_lights ();
5787
5788 if (go.valid_object ())
5789 base_properties::remove_child (h, from_root);
5790
5791}
5792
5793void
5795{
5796 gh_manager& gh_mgr = octave::__get_gh_manager__ ("axes::properties::adopt");
5797
5798 graphics_object go (gh_mgr.get_object (h));
5799
5800 if (go.isa ("light") && go.get_properties ().is_visible ())
5801 increase_num_lights ();
5802
5804
5805 // FIXME: For performance reasons, we would like to call
5806 // update_axis_limits ("xlim", h);
5807 // which updates the limits based ONLY on the new data from h.
5808 // But this isn't working properly at the moment, so we
5809 // call the other form which invokes a full tree traversal of all
5810 // of the axes children.
5811 if (xlimmode_is ("auto"))
5812 update_axis_limits ("xlim");
5813
5814 if (ylimmode_is ("auto"))
5815 update_axis_limits ("ylim");
5816
5817 if (zlimmode_is ("auto"))
5818 update_axis_limits ("zlim");
5819
5820 if (climmode_is ("auto"))
5821 update_axis_limits ("clim");
5822
5823 if (climmode_is ("auto"))
5824 update_axis_limits ("alim");
5825}
5826
5827inline Matrix
5829{
5830 Matrix m (4, 4, 0.0);
5831
5832 for (int i = 0; i < 4; i++)
5833 m(i, i) = 1;
5834
5835 return m;
5836}
5837
5838inline ColumnVector
5840{
5841 ColumnVector v (4, 0.0);
5842
5843 v(3) = 1;
5844
5845 return v;
5846}
5847
5848inline ColumnVector
5849xform_vector (double x, double y, double z)
5850{
5851 ColumnVector v (4, 1.0);
5852
5853 v(0) = x;
5854 v(1) = y;
5855 v(2) = z;
5856
5857 return v;
5858}
5859
5860inline ColumnVector
5861transform (const Matrix& m, double x, double y, double z)
5862{
5863 return (m * xform_vector (x, y, z));
5864}
5865
5866inline Matrix
5867xform_scale (double x, double y, double z)
5868{
5869 Matrix m (4, 4, 0.0);
5870
5871 m(0, 0) = x;
5872 m(1, 1) = y;
5873 m(2, 2) = z;
5874 m(3, 3) = 1;
5875
5876 return m;
5877}
5878
5879inline Matrix
5880xform_translate (double x, double y, double z)
5881{
5882 Matrix m = xform_matrix ();
5883
5884 m(0, 3) = x;
5885 m(1, 3) = y;
5886 m(2, 3) = z;
5887 m(3, 3) = 1;
5888
5889 return m;
5890}
5891
5892inline void
5893scale (Matrix& m, double x, double y, double z)
5894{
5895 m = m * xform_scale (x, y, z);
5896}
5897
5898inline void
5899translate (Matrix& m, double x, double y, double z)
5900{
5901 m = m * xform_translate (x, y, z);
5902}
5903
5904inline void
5906{
5907 v = m * v;
5908}
5909
5910inline void
5911scale (ColumnVector& v, double x, double y, double z)
5912{
5913 v(0) *= x;
5914 v(1) *= y;
5915 v(2) *= z;
5916}
5917
5918inline void
5919translate (ColumnVector& v, double x, double y, double z)
5920{
5921 v(0) += x;
5922 v(1) += y;
5923 v(2) += z;
5924}
5925
5926inline void
5928{
5929 double fact = 1.0 / sqrt (v(0)*v(0)+v(1)*v(1)+v(2)*v(2));
5930 scale (v, fact, fact, fact);
5931}
5932
5933inline double
5934dot (const ColumnVector& v1, const ColumnVector& v2)
5935{
5936 return (v1(0)*v2(0)+v1(1)*v2(1)+v1(2)*v2(2));
5937}
5938
5939inline double
5941{
5942 return sqrt (dot (v, v));
5943}
5944
5945inline ColumnVector
5947{
5949
5950 r(0) = v1(1)*v2(2) - v1(2)*v2(1);
5951 r(1) = v1(2)*v2(0) - v1(0)*v2(2);
5952 r(2) = v1(0)*v2(1) - v1(1)*v2(0);
5953
5954 return r;
5955}
5956
5957inline Matrix
5959{
5960 static double data[32] =
5961 {
5962 0, 0, 0, 1,
5963 1, 0, 0, 1,
5964 0, 1, 0, 1,
5965 0, 0, 1, 1,
5966 1, 1, 0, 1,
5967 1, 0, 1, 1,
5968 0, 1, 1, 1,
5969 1, 1, 1, 1
5970 };
5971 Matrix m (4, 8);
5972
5973 memcpy (m.fortran_vec (), data, sizeof (double)*32);
5974
5975 return m;
5976}
5977
5978inline ColumnVector
5980{
5981 ColumnVector retval (4, 1.0);
5982
5983 memcpy (retval.fortran_vec (), m.data (), sizeof (double)*3);
5984
5985 return retval;
5986}
5987
5988inline RowVector
5990{
5991 return v.extract_n (0, 3).transpose ();
5992}
5993
5994void
5995axes::properties::update_camera (void)
5996{
5997 double xd = (xdir_is ("normal") ? 1 : -1);
5998 double yd = (ydir_is ("normal") ? 1 : -1);
5999 double zd = (zdir_is ("normal") ? 1 : -1);
6000
6001 Matrix xlimits = m_sx.scale (get_xlim ().matrix_value ());
6002 Matrix ylimits = m_sy.scale (get_ylim ().matrix_value ());
6003 Matrix zlimits = m_sz.scale (get_zlim ().matrix_value ());
6004
6005 double xo = xlimits(xd > 0 ? 0 : 1);
6006 double yo = ylimits(yd > 0 ? 0 : 1);
6007 double zo = zlimits(zd > 0 ? 0 : 1);
6008
6009 Matrix pb = get_plotboxaspectratio ().matrix_value ();
6010
6011 bool autocam = (camerapositionmode_is ("auto")
6012 && cameratargetmode_is ("auto")
6013 && cameraupvectormode_is ("auto")
6014 && cameraviewanglemode_is ("auto"));
6015 bool dowarp = (autocam && dataaspectratiomode_is ("auto")
6016 && plotboxaspectratiomode_is ("auto"));
6017
6018 ColumnVector c_eye (xform_vector ());
6019 ColumnVector c_center (xform_vector ());
6020 ColumnVector c_upv (xform_vector ());
6021
6022 if (cameratargetmode_is ("auto"))
6023 {
6024 c_center(0) = (xlimits(0) + xlimits(1)) / 2;
6025 c_center(1) = (ylimits(0) + ylimits(1)) / 2;
6026 c_center(2) = (zlimits(0) + zlimits(1)) / 2;
6027
6028 m_cameratarget = xform2cam (c_center);
6029 }
6030 else
6031 c_center = cam2xform (get_cameratarget ().matrix_value ());
6032
6033 if (camerapositionmode_is ("auto"))
6034 {
6035 Matrix tview = get_view ().matrix_value ();
6036 double az = tview(0);
6037 double el = tview(1);
6038 double d = 5 * sqrt (pb(0)*pb(0) + pb(1)*pb(1) + pb(2)*pb(2));
6039
6040 if (el == 90 || el == -90)
6041 c_eye(2) = d*octave::math::signum (el);
6042 else
6043 {
6044 az *= M_PI/180.0;
6045 el *= M_PI/180.0;
6046 c_eye(0) = d * cos (el) * sin (az);
6047 c_eye(1) = -d* cos (el) * cos (az);
6048 c_eye(2) = d * sin (el);
6049 }
6050 c_eye(0) = c_eye(0)*(xlimits(1)-xlimits(0))/(xd*pb(0))+c_center(0);
6051 c_eye(1) = c_eye(1)*(ylimits(1)-ylimits(0))/(yd*pb(1))+c_center(1);
6052 c_eye(2) = c_eye(2)*(zlimits(1)-zlimits(0))/(zd*pb(2))+c_center(2);
6053
6054 m_cameraposition = xform2cam (c_eye);
6055 }
6056 else
6057 c_eye = cam2xform (get_cameraposition ().matrix_value ());
6058
6059 if (cameraupvectormode_is ("auto"))
6060 {
6061 Matrix tview = get_view ().matrix_value ();
6062 double az = tview(0);
6063 double el = tview(1);
6064
6065 if (el == 90 || el == -90)
6066 {
6067 c_upv(0) = -octave::math::signum (el)
6068 * sin (az*M_PI/180.0)*(xlimits(1)-xlimits(0))/pb(0);
6069 c_upv(1) = octave::math::signum (el)
6070 * cos (az*M_PI/180.0)*(ylimits(1)-ylimits(0))/pb(1);
6071 }
6072 else
6073 c_upv(2) = 1;
6074
6075 m_cameraupvector = xform2cam (c_upv);
6076 }
6077 else
6078 c_upv = cam2xform (get_cameraupvector ().matrix_value ());
6079
6080 Matrix x_view = xform_matrix ();
6081 Matrix x_projection = xform_matrix ();
6082 Matrix x_viewport = xform_matrix ();
6083 Matrix x_normrender;
6084 Matrix x_pre = xform_matrix ();
6085
6086 m_x_render = xform_matrix ();
6087 m_x_render_inv = xform_matrix ();
6088
6089 scale (x_pre, pb(0), pb(1), pb(2));
6090 translate (x_pre, -0.5, -0.5, -0.5);
6091 scale (x_pre, xd/(xlimits(1)-xlimits(0)), yd/(ylimits(1)-ylimits(0)),
6092 zd/(zlimits(1)-zlimits(0)));
6093 translate (x_pre, -xo, -yo, -zo);
6094
6095 xform (c_eye, x_pre);
6096 xform (c_center, x_pre);
6097 scale (c_upv, pb(0)/(xlimits(1)-xlimits(0)), pb(1)/(ylimits(1)-ylimits(0)),
6098 pb(2)/(zlimits(1)-zlimits(0)));
6099 translate (c_center, -c_eye(0), -c_eye(1), -c_eye(2));
6100
6101 ColumnVector F (c_center), f (F), UP (c_upv);
6102 normalize (f);
6103 normalize (UP);
6104
6105 if (std::abs (dot (f, UP)) > 1e-15)
6106 {
6107 double fa = 1 / sqrt (1 - f(2)*f(2));
6108 scale (UP, fa, fa, fa);
6109 }
6110
6111 ColumnVector s = cross (f, UP);
6112 ColumnVector u = cross (s, f);
6113
6114 scale (x_view, 1, 1, -1);
6115 Matrix l = xform_matrix ();
6116 l(0, 0) = s(0); l(0, 1) = s(1); l(0, 2) = s(2);
6117 l(1, 0) = u(0); l(1, 1) = u(1); l(1, 2) = u(2);
6118 l(2, 0) = -f(0); l(2, 1) = -f(1); l(2, 2) = -f(2);
6119 x_view = x_view * l;
6120 translate (x_view, -c_eye(0), -c_eye(1), -c_eye(2));
6121 scale (x_view, pb(0), pb(1), pb(2));
6122 translate (x_view, -0.5, -0.5, -0.5);
6123
6124 Matrix x_cube = x_view * unit_cube ();
6125 ColumnVector cmin = x_cube.row_min ();
6126 ColumnVector cmax = x_cube.row_max ();
6127 double xM = cmax(0) - cmin(0);
6128 double yM = cmax(1) - cmin(1);
6129
6130 Matrix bb = get_boundingbox (true);
6131
6132 double v_angle;
6133
6134 if (cameraviewanglemode_is ("auto"))
6135 {
6136 double af;
6137
6138 // FIXME: was this really needed? When compared to Matlab, it
6139 // does not seem to be required. Need investigation with concrete
6140 // graphics toolkit to see results visually.
6141 if (false && dowarp)
6142 af = (1.0 / (xM > yM ? xM : yM));
6143 else
6144 {
6145 if ((bb(2)/bb(3)) > (xM/yM))
6146 af = 1.0 / yM;
6147 else
6148 af = 1.0 / xM;
6149 }
6150 v_angle = 2 * (180.0 / M_PI) * atan (1 / (2 * af * norm (F)));
6151
6152 m_cameraviewangle = v_angle;
6153 }
6154 else
6155 v_angle = get_cameraviewangle ();
6156
6157 double pf = 1 / (2 * tan ((v_angle / 2) * M_PI / 180.0) * norm (F));
6158 scale (x_projection, pf, pf, 1);
6159
6160 if (dowarp)
6161 {
6162 xM *= pf;
6163 yM *= pf;
6164 translate (x_viewport, bb(0)+bb(2)/2, bb(1)+bb(3)/2, 0);
6165 scale (x_viewport, bb(2)/xM, -bb(3)/yM, 1);
6166 }
6167 else
6168 {
6169 double pix = 1;
6170 if (autocam)
6171 {
6172 if ((bb(2)/bb(3)) > (xM/yM))
6173 pix = bb(3);
6174 else
6175 pix = bb(2);
6176 }
6177 else
6178 pix = (bb(2) < bb(3) ? bb(2) : bb(3));
6179 translate (x_viewport, bb(0)+bb(2)/2, bb(1)+bb(3)/2, 0);
6180 scale (x_viewport, pix, -pix, 1);
6181 }
6182
6183 x_normrender = x_viewport * x_projection * x_view;
6184
6185 x_cube = x_normrender * unit_cube ();
6186 cmin = x_cube.row_min ();
6187 cmax = x_cube.row_max ();
6188 m_x_zlim.resize (1, 2);
6189 m_x_zlim(0) = cmin(2);
6190 m_x_zlim(1) = cmax(2);
6191
6192 m_x_render = x_normrender;
6193 scale (m_x_render, xd/(xlimits(1)-xlimits(0)), yd/(ylimits(1)-ylimits(0)),
6194 zd/(zlimits(1)-zlimits(0)));
6195 translate (m_x_render, -xo, -yo, -zo);
6196
6197 m_x_render_inv = m_x_render.inverse ();
6198
6199 // Note: these matrices are a slight modified version of the regular matrices,
6200 // more suited for OpenGL rendering (m_x_gl_mat1 => light => m_x_gl_mat2)
6201 m_x_gl_mat1 = x_view;
6202 scale (m_x_gl_mat1, xd/(xlimits(1)-xlimits(0)), yd/(ylimits(1)-ylimits(0)),
6203 zd/(zlimits(1)-zlimits(0)));
6204 translate (m_x_gl_mat1, -xo, -yo, -zo);
6205 m_x_gl_mat2 = x_viewport * x_projection;
6206}
6207
6208static bool updating_axes_layout = false;
6209
6210void
6211axes::properties::update_axes_layout (void)
6212{
6214 return;
6215
6216 graphics_xform xform = get_transform ();
6217
6218 double xd = (xdir_is ("normal") ? 1 : -1);
6219 double yd = (ydir_is ("normal") ? 1 : -1);
6220 double zd = (zdir_is ("normal") ? 1 : -1);
6221
6222 const Matrix xlims = xform.xscale (get_xlim ().matrix_value ());
6223 const Matrix ylims = xform.yscale (get_ylim ().matrix_value ());
6224 const Matrix zlims = xform.zscale (get_zlim ().matrix_value ());
6225
6226 double x_min, x_max, y_min, y_max, z_min, z_max;
6227 x_min = xlims(0), x_max = xlims(1);
6228 y_min = ylims(0), y_max = ylims(1);
6229 z_min = zlims(0), z_max = zlims(1);
6230
6231 ColumnVector p1, p2, dir (3);
6232
6233 m_xstate = m_ystate = m_zstate = AXE_ANY_DIR;
6234
6235 p1 = xform.transform (x_min, (y_min+y_max)/2, (z_min+z_max)/2, false);
6236 p2 = xform.transform (x_max, (y_min+y_max)/2, (z_min+z_max)/2, false);
6237 dir(0) = octave::math::round (p2(0) - p1(0));
6238 dir(1) = octave::math::round (p2(1) - p1(1));
6239 dir(2) = (p2(2) - p1(2));
6240 if (dir(0) == 0 && dir(1) == 0)
6241 m_xstate = AXE_DEPTH_DIR;
6242 else if (dir(2) == 0)
6243 {
6244 if (dir(0) == 0)
6245 m_xstate = AXE_VERT_DIR;
6246 else if (dir(1) == 0)
6247 m_xstate = AXE_HORZ_DIR;
6248 }
6249
6250 if (dir(2) == 0)
6251 {
6252 if (dir(1) == 0)
6253 m_xPlane = (dir(0) > 0 ? x_max : x_min);
6254 else
6255 m_xPlane = (dir(1) < 0 ? x_max : x_min);
6256 }
6257 else
6258 m_xPlane = (dir(2) < 0 ? x_min : x_max);
6259
6260 m_xPlaneN = (m_xPlane == x_min ? x_max : x_min);
6261 m_fx = (x_max - x_min) / sqrt (dir(0)*dir(0) + dir(1)*dir(1));
6262
6263 p1 = xform.transform ((x_min + x_max)/2, y_min, (z_min + z_max)/2, false);
6264 p2 = xform.transform ((x_min + x_max)/2, y_max, (z_min + z_max)/2, false);
6265 dir(0) = octave::math::round (p2(0) - p1(0));
6266 dir(1) = octave::math::round (p2(1) - p1(1));
6267 dir(2) = (p2(2) - p1(2));
6268 if (dir(0) == 0 && dir(1) == 0)
6269 m_ystate = AXE_DEPTH_DIR;
6270 else if (dir(2) == 0)
6271 {
6272 if (dir(0) == 0)
6273 m_ystate = AXE_VERT_DIR;
6274 else if (dir(1) == 0)
6275 m_ystate = AXE_HORZ_DIR;
6276 }
6277
6278 if (dir(2) == 0)
6279 {
6280 if (dir(1) == 0)
6281 m_yPlane = (dir(0) > 0 ? y_max : y_min);
6282 else
6283 m_yPlane = (dir(1) < 0 ? y_max : y_min);
6284 }
6285 else
6286 m_yPlane = (dir(2) < 0 ? y_min : y_max);
6287
6288 m_yPlaneN = (m_yPlane == y_min ? y_max : y_min);
6289 m_fy = (y_max - y_min) / sqrt (dir(0)*dir(0) + dir(1)*dir(1));
6290
6291 p1 = xform.transform ((x_min + x_max)/2, (y_min + y_max)/2, z_min, false);
6292 p2 = xform.transform ((x_min + x_max)/2, (y_min + y_max)/2, z_max, false);
6293 dir(0) = octave::math::round (p2(0) - p1(0));
6294 dir(1) = octave::math::round (p2(1) - p1(1));
6295 dir(2) = (p2(2) - p1(2));
6296 if (dir(0) == 0 && dir(1) == 0)
6297 m_zstate = AXE_DEPTH_DIR;
6298 else if (dir(2) == 0)
6299 {
6300 if (dir(0) == 0)
6301 m_zstate = AXE_VERT_DIR;
6302 else if (dir(1) == 0)
6303 m_zstate = AXE_HORZ_DIR;
6304 }
6305
6306 if (dir(2) == 0)
6307 {
6308 if (dir(1) == 0)
6309 m_zPlane = (dir(0) > 0 ? z_min : z_max);
6310 else
6311 m_zPlane = (dir(1) < 0 ? z_min : z_max);
6312 }
6313 else
6314 m_zPlane = (dir(2) < 0 ? z_min : z_max);
6315
6316 m_zPlaneN = (m_zPlane == z_min ? z_max : z_min);
6317 m_fz = (z_max - z_min) / sqrt (dir(0)*dir(0) + dir(1)*dir(1));
6318
6320
6321 m_xySym = (xd*yd*(m_xPlane-m_xPlaneN)*(m_yPlane-m_yPlaneN) > 0);
6322 m_zSign = (zd*(m_zPlane-m_zPlaneN) <= 0);
6323 m_xyzSym = (m_zSign ? m_xySym : ! m_xySym);
6324 m_xpTick = (m_zSign ? m_xPlaneN : m_xPlane);
6325 m_ypTick = (m_zSign ? m_yPlaneN : m_yPlane);
6326 m_zpTick = (m_zSign ? m_zPlane : m_zPlaneN);
6327 m_xpTickN = (m_zSign ? m_xPlane : m_xPlaneN);
6328 m_ypTickN = (m_zSign ? m_yPlane : m_yPlaneN);
6329 m_zpTickN = (m_zSign ? m_zPlaneN : m_zPlane);
6330
6331 // 2D mode
6332 m_x2Dtop = false;
6333 m_y2Dright = false;
6334 m_layer2Dtop = false;
6335 if (m_xstate == AXE_HORZ_DIR && m_ystate == AXE_VERT_DIR)
6336 {
6337 Matrix ylimits = get_ylim ().matrix_value ();
6338 if (xaxislocation_is ("top")
6339 || (yscale_is ("log") && xaxislocation_is ("origin")
6340 && (ylimits(1) < 0.)))
6341 {
6342 std::swap (m_yPlane, m_yPlaneN);
6343 m_x2Dtop = true;
6344 }
6345 m_ypTick = m_yPlaneN;
6346 m_ypTickN = m_yPlane;
6347 Matrix xlimits = get_xlim ().matrix_value ();
6348 if (yaxislocation_is ("right")
6349 || (xscale_is ("log") && yaxislocation_is ("origin")
6350 && (xlimits(1) < 0.)))
6351 {
6352 std::swap (m_xPlane, m_xPlaneN);
6353 m_y2Dright = true;
6354 }
6355 m_xpTick = m_xPlaneN;
6356 m_xpTickN = m_xPlane;
6357 if (layer_is ("top"))
6358 {
6359 m_zpTick = m_zPlaneN;
6360 m_layer2Dtop = true;
6361 }
6362 else
6363 m_zpTick = m_zPlane;
6364 }
6365
6366 Matrix viewmat = get_view ().matrix_value ();
6367 m_nearhoriz = std::abs (viewmat(1)) <= 5;
6368 m_is2D = viewmat(1) == 90;
6369
6370 update_ticklength ();
6371}
6372
6373void
6374axes::properties::update_ticklength (void)
6375{
6376 bool mode2D = (((m_xstate > AXE_DEPTH_DIR ? 1 : 0) +
6377 (m_ystate > AXE_DEPTH_DIR ? 1 : 0) +
6378 (m_zstate > AXE_DEPTH_DIR ? 1 : 0)) == 2);
6379
6380 if (tickdirmode_is ("auto"))
6381 m_tickdir.set (mode2D ? "in" : "out", true);
6382
6383 double ticksign = (tickdir_is ("in") ? -1 : 1);
6384
6385 Matrix bbox = get_boundingbox (true);
6386 Matrix ticklen = get_ticklength ().matrix_value ();
6387 ticklen(0) *= std::max (bbox(2), bbox(3));
6388 // FIXME: This algorithm is not Matlab-compatible. See bug #55483.
6389 // Scale the results of Octave's algorithm for better visuals.
6390 ticklen(1) *= (0.76 * std::max (bbox(2), bbox(3)));
6391
6392 m_xticklen = ticksign * (mode2D ? ticklen(0) : ticklen(1));
6393 m_yticklen = ticksign * (mode2D ? ticklen(0) : ticklen(1));
6394 m_zticklen = ticksign * (mode2D ? ticklen(0) : ticklen(1));
6395
6396 double offset = get___fontsize_points__ () / 2;
6397
6398 m_xtickoffset = (mode2D ? std::max (0., m_xticklen) : std::abs (m_xticklen)) +
6399 (m_xstate == AXE_HORZ_DIR ? offset*1.5 : offset);
6400 m_ytickoffset = (mode2D ? std::max (0., m_yticklen) : std::abs (m_yticklen)) +
6401 (m_ystate == AXE_HORZ_DIR ? offset*1.5 : offset);
6402 m_ztickoffset = (mode2D ? std::max (0., m_zticklen) : std::abs (m_zticklen)) +
6403 (m_zstate == AXE_HORZ_DIR ? offset*1.5 : offset);
6404
6405 update_xlabel_position ();
6406 update_ylabel_position ();
6407 update_zlabel_position ();
6408 update_title_position ();
6409}
6410
6411/*
6412## FIXME: A demo can't be called in a C++ file. This should be made a test
6413## or moved to a .m file where it can be called.
6414%!demo
6415%! clf;
6416%! subplot (2,1,1);
6417%! plot (rand (3));
6418%! xlabel xlabel;
6419%! ylabel ylabel;
6420%! title title;
6421%! subplot (2,1,2);
6422%! plot (rand (3));
6423%! set (gca, "ticklength", get (gca, "ticklength") * 2, "tickdir", "out");
6424%! xlabel xlabel;
6425%! ylabel ylabel;
6426%! title title;
6427*/
6428
6429static ColumnVector
6431 const text::properties& props,
6432 const graphics_xform& xform,
6433 const Matrix& bbox)
6434{
6435 ColumnVector retval;
6436
6437 std::string to_units = props.get_units ();
6438
6439 if (to_units != "data")
6440 {
6441 ColumnVector v = xform.transform (p(0), p(1), p(2));
6442
6443 retval.resize (3);
6444
6445 retval(0) = v(0) - bbox(0) + 1;
6446 retval(1) = bbox(1) + bbox(3) - v(1) + 1;
6447 retval(2) = 0;
6448
6449 retval = convert_position (retval, "pixels", to_units,
6450 bbox.extract_n (0, 2, 1, 2));
6451 }
6452 else
6453 retval = p;
6454
6455 return retval;
6456}
6457
6458static bool updating_xlabel_position = false;
6459
6460void
6461axes::properties::update_xlabel_position (void)
6462{
6464 return;
6465
6466 gh_manager& gh_mgr
6467 = octave::__get_gh_manager__ ("axes::properties::update_xlabel_position");
6468
6469 graphics_object go = gh_mgr.get_object (get_xlabel ());
6470
6471 if (! go.valid_object ())
6472 return;
6473
6474 text::properties& xlabel_props
6475 = reinterpret_cast<text::properties&> (go.get_properties ());
6476
6477 bool isempty = xlabel_props.get_string ().isempty ();
6478
6480 restore_var (updating_xlabel_position, true);
6481
6482 if (! isempty)
6483 {
6484 if (xlabel_props.horizontalalignmentmode_is ("auto"))
6485 {
6486 xlabel_props.set_horizontalalignment
6487 (m_xstate > AXE_DEPTH_DIR ? "center" : (m_xyzSym ? "left" : "right"));
6488
6489 xlabel_props.set_horizontalalignmentmode ("auto");
6490 }
6491
6492 if (xlabel_props.verticalalignmentmode_is ("auto"))
6493 {
6494 xlabel_props.set_verticalalignment
6495 (m_xstate == AXE_VERT_DIR || m_x2Dtop ? "bottom" : "top");
6496
6497 xlabel_props.set_verticalalignmentmode ("auto");
6498 }
6499 }
6500
6501 if (xlabel_props.positionmode_is ("auto")
6502 || xlabel_props.rotationmode_is ("auto"))
6503 {
6504 graphics_xform xform = get_transform ();
6505
6506 Matrix ext (1, 2, 0.0);
6507 ext = get_ticklabel_extents (get_xtick ().matrix_value (),
6508 get_xticklabel ().string_vector_value (),
6509 get_xlim ().matrix_value ());
6510
6511 double margin = 5;
6512 double wmax = ext(0) + margin;
6513 double hmax = ext(1) + margin;
6514 double angle = 0.0;
6515 ColumnVector p
6516 = graphics_xform::xform_vector ((m_xpTickN + m_xpTick)/2, m_ypTick, m_zpTick);
6517
6518 bool tick_along_z = m_nearhoriz || octave::math::isinf (m_fy);
6519 if (tick_along_z)
6520 p(2) += (octave::math::signum (m_zpTick - m_zpTickN) * m_fz * m_xtickoffset);
6521 else
6522 p(1) += (octave::math::signum (m_ypTick - m_ypTickN) * m_fy * m_xtickoffset);
6523
6524 p = xform.transform (p(0), p(1), p(2), false);
6525
6526 switch (m_xstate)
6527 {
6528 case AXE_ANY_DIR:
6529 p(0) += (m_xyzSym ? wmax : -wmax);
6530 p(1) += hmax;
6531 break;
6532
6533 case AXE_VERT_DIR:
6534 p(0) -= wmax;
6535 angle = 90;
6536 break;
6537
6538 case AXE_HORZ_DIR:
6539 p(1) += (m_x2Dtop ? -hmax : hmax);
6540 break;
6541 }
6542
6543 if (xlabel_props.positionmode_is ("auto"))
6544 {
6545 p = xform.untransform (p(0), p(1), p(2), true);
6546
6547 p = convert_label_position (p, xlabel_props, xform,
6548 get_extent (false));
6549
6550 xlabel_props.set_position (p.extract_n (0, 3).transpose ());
6551 xlabel_props.set_positionmode ("auto");
6552 }
6553
6554 if (! isempty && xlabel_props.rotationmode_is ("auto"))
6555 {
6556 xlabel_props.set_rotation (angle);
6557 xlabel_props.set_rotationmode ("auto");
6558 }
6559 }
6560}
6561
6562static bool updating_ylabel_position = false;
6563
6564void
6565axes::properties::update_ylabel_position (void)
6566{
6568 return;
6569
6570 gh_manager& gh_mgr
6571 = octave::__get_gh_manager__ ("axes::properties::update_ylabel_position");
6572
6573 graphics_object go = gh_mgr.get_object (get_ylabel ());
6574
6575 if (! go.valid_object ())
6576 return;
6577
6578 text::properties& ylabel_props
6579 = reinterpret_cast<text::properties&> (go.get_properties ());
6580
6581 bool isempty = ylabel_props.get_string ().isempty ();
6582
6584 restore_var (updating_ylabel_position, true);
6585
6586 if (! isempty)
6587 {
6588 if (ylabel_props.horizontalalignmentmode_is ("auto"))
6589 {
6590 ylabel_props.set_horizontalalignment
6591 (m_ystate > AXE_DEPTH_DIR ? "center" : (! m_xyzSym ? "left" : "right"));
6592
6593 ylabel_props.set_horizontalalignmentmode ("auto");
6594 }
6595
6596 if (ylabel_props.verticalalignmentmode_is ("auto"))
6597 {
6598 ylabel_props.set_verticalalignment
6599 (m_ystate == AXE_VERT_DIR && ! m_y2Dright ? "bottom" : "top");
6600
6601 ylabel_props.set_verticalalignmentmode ("auto");
6602 }
6603 }
6604
6605 if (ylabel_props.positionmode_is ("auto")
6606 || ylabel_props.rotationmode_is ("auto"))
6607 {
6608 graphics_xform xform = get_transform ();
6609
6610 Matrix ext (1, 2, 0.0);
6611
6612 ext = get_ticklabel_extents (get_ytick ().matrix_value (),
6613 get_yticklabel ().string_vector_value (),
6614 get_ylim ().matrix_value ());
6615 double margin = 5;
6616 double wmax = ext(0) + margin;
6617 double hmax = ext(1) + margin;
6618 double angle = 0.0;
6619 ColumnVector p
6620 = graphics_xform::xform_vector (m_xpTick, (m_ypTickN + m_ypTick)/2, m_zpTick);
6621
6622 bool tick_along_z = m_nearhoriz || octave::math::isinf (m_fx);
6623 if (tick_along_z)
6624 p(2) += (octave::math::signum (m_zpTick - m_zpTickN) * m_fz * m_ytickoffset);
6625 else
6626 p(0) += (octave::math::signum (m_xpTick - m_xpTickN) * m_fx * m_ytickoffset);
6627
6628 p = xform.transform (p(0), p(1), p(2), false);
6629
6630 switch (m_ystate)
6631 {
6632 case AXE_ANY_DIR:
6633 p(0) += (! m_xyzSym ? wmax : -wmax);
6634 p(1) += hmax;
6635 break;
6636
6637 case AXE_VERT_DIR:
6638 p(0) += (m_y2Dright ? wmax : -wmax);
6639 angle = 90;
6640 break;
6641
6642 case AXE_HORZ_DIR:
6643 p(1) += hmax;
6644 break;
6645 }
6646
6647 if (ylabel_props.positionmode_is ("auto"))
6648 {
6649 p = xform.untransform (p(0), p(1), p(2), true);
6650
6651 p = convert_label_position (p, ylabel_props, xform,
6652 get_extent (false));
6653
6654 ylabel_props.set_position (p.extract_n (0, 3).transpose ());
6655 ylabel_props.set_positionmode ("auto");
6656 }
6657
6658 if (! isempty && ylabel_props.rotationmode_is ("auto"))
6659 {
6660 ylabel_props.set_rotation (angle);
6661 ylabel_props.set_rotationmode ("auto");
6662 }
6663 }
6664}
6665
6666static bool updating_zlabel_position = false;
6667
6668void
6669axes::properties::update_zlabel_position (void)
6670{
6672 return;
6673
6674 gh_manager& gh_mgr
6675 = octave::__get_gh_manager__ ("axes::properties::update_zlabel_position");
6676
6677 graphics_object go = gh_mgr.get_object (get_zlabel ());
6678
6679 if (! go.valid_object ())
6680 return;
6681
6682 text::properties& zlabel_props
6683 = reinterpret_cast<text::properties&> (go.get_properties ());
6684
6685 bool camAuto = cameraupvectormode_is ("auto");
6686 bool isempty = zlabel_props.get_string ().isempty ();
6687
6689 restore_updating_zlabel_position (updating_zlabel_position, true);
6690
6691 if (! isempty)
6692 {
6693 if (zlabel_props.horizontalalignmentmode_is ("auto"))
6694 {
6695 zlabel_props.set_horizontalalignment
6696 ((m_zstate > AXE_DEPTH_DIR || camAuto) ? "center" : "right");
6697
6698 zlabel_props.set_horizontalalignmentmode ("auto");
6699 }
6700
6701 if (zlabel_props.verticalalignmentmode_is ("auto"))
6702 {
6703 zlabel_props.set_verticalalignment
6704 (m_zstate == AXE_VERT_DIR
6705 ? "bottom" : ((m_zSign || camAuto) ? "bottom" : "top"));
6706
6707 zlabel_props.set_verticalalignmentmode ("auto");
6708 }
6709 }
6710
6711 if (zlabel_props.positionmode_is ("auto")
6712 || zlabel_props.rotationmode_is ("auto"))
6713 {
6714 graphics_xform xform = get_transform ();
6715
6716 Matrix ext (1, 2, 0.0);
6717 ext = get_ticklabel_extents (get_ztick ().matrix_value (),
6718 get_zticklabel ().string_vector_value (),
6719 get_zlim ().matrix_value ());
6720
6721 double margin = 5;
6722 double wmax = ext(0) + margin;
6723 double hmax = ext(1) + margin;
6724 double angle = 0.0;
6725 ColumnVector p;
6726
6727 if (m_xySym)
6728 {
6729 p = graphics_xform::xform_vector (m_xPlaneN, m_yPlane,
6730 (m_zpTickN + m_zpTick)/2);
6731 if (octave::math::isinf (m_fy))
6732 p(0) += octave::math::signum (m_xPlaneN - m_xPlane) * m_fx * m_ztickoffset;
6733 else
6734 p(1) += octave::math::signum (m_yPlane - m_yPlaneN) * m_fy * m_ztickoffset;
6735 }
6736 else
6737 {
6738 p = graphics_xform::xform_vector (m_xPlane, m_yPlaneN,
6739 (m_zpTickN + m_zpTick)/2);
6740 if (octave::math::isinf (m_fx))
6741 p(1) += octave::math::signum (m_yPlaneN - m_yPlane) * m_fy * m_ztickoffset;
6742 else
6743 p(0) += octave::math::signum (m_xPlane - m_xPlaneN) * m_fx * m_ztickoffset;
6744 }
6745
6746 p = xform.transform (p(0), p(1), p(2), false);
6747
6748 switch (m_zstate)
6749 {
6750 case AXE_ANY_DIR:
6751 if (camAuto)
6752 {
6753 p(0) -= wmax;
6754 angle = 90;
6755 }
6756
6757 // FIXME: what's the correct offset?
6758 //
6759 // p[0] += (! m_xySym ? wmax : -wmax);
6760 // p[1] += (m_zSign ? hmax : -hmax);
6761
6762 break;
6763
6764 case AXE_VERT_DIR:
6765 p(0) -= wmax;
6766 angle = 90;
6767 break;
6768
6769 case AXE_HORZ_DIR:
6770 p(1) += hmax;
6771 break;
6772 }
6773
6774 if (zlabel_props.positionmode_is ("auto"))
6775 {
6776 p = xform.untransform (p(0), p(1), p(2), true);
6777
6778 p = convert_label_position (p, zlabel_props, xform,
6779 get_extent (false));
6780
6781 zlabel_props.set_position (p.extract_n (0, 3).transpose ());
6782 zlabel_props.set_positionmode ("auto");
6783 }
6784
6785 if (! isempty && zlabel_props.rotationmode_is ("auto"))
6786 {
6787 zlabel_props.set_rotation (angle);
6788 zlabel_props.set_rotationmode ("auto");
6789 }
6790 }
6791}
6792
6793static bool updating_title_position = false;
6794
6795void
6796axes::properties::update_title_position (void)
6797{
6799 return;
6800
6801 gh_manager& gh_mgr
6802 = octave::__get_gh_manager__ ("axes::properties::update_title_position");
6803
6804 graphics_object go = gh_mgr.get_object (get_title ());
6805
6806 if (! go.valid_object ())
6807 return;
6808
6809 text::properties& title_props
6810 = reinterpret_cast<text::properties&> (go.get_properties ());
6811
6813
6814 if (title_props.positionmode_is ("auto"))
6815 {
6816 graphics_xform xform = get_transform ();
6817
6818 // FIXME: bbox should be stored in axes::properties
6819 Matrix bbox = get_extent (false);
6820
6821 ColumnVector p
6822 = graphics_xform::xform_vector (bbox(0) + bbox(2)/2, bbox(1) - 10,
6823 (m_x_zlim(0) + m_x_zlim(1))/2);
6824
6825 if (m_x2Dtop)
6826 {
6827 Matrix ext (1, 2, 0.0);
6828 ext = get_ticklabel_extents (get_xtick ().matrix_value (),
6829 get_xticklabel ().string_vector_value (),
6830 get_xlim ().matrix_value ());
6831 p(1) -= ext(1);
6832 }
6833
6834 p = xform.untransform (p(0), p(1), p(2), true);
6835
6836 p = convert_label_position (p, title_props, xform, bbox);
6837
6838 title_props.set_position (p.extract_n (0, 3).transpose ());
6839 title_props.set_positionmode ("auto");
6840 }
6841}
6842
6843void
6844axes::properties::update_autopos (const std::string& elem_type)
6845{
6846 if (elem_type == "xlabel")
6847 update_xlabel_position ();
6848 else if (elem_type == "ylabel")
6849 update_ylabel_position ();
6850 else if (elem_type == "zlabel")
6851 update_zlabel_position ();
6852 else if (elem_type == "title")
6853 update_title_position ();
6854 else if (elem_type == "sync")
6855 sync_positions ();
6856}
6857
6858static void
6859normalized_aspectratios (Matrix& aspectratios, const Matrix& scalefactors,
6860 double xlength, double ylength, double zlength)
6861{
6862 double xval = xlength / scalefactors(0);
6863 double yval = ylength / scalefactors(1);
6864 double zval = zlength / scalefactors(2);
6865
6866 double minval = octave::math::min (octave::math::min (xval, yval), zval);
6867
6868 aspectratios(0) = xval / minval;
6869 aspectratios(1) = yval / minval;
6870 aspectratios(2) = zval / minval;
6871}
6872
6873static void
6874max_axes_scale (double& s, Matrix& limits, const Matrix& kids,
6875 double pbfactor, double dafactor, char limit_type, bool tight)
6876{
6877 if (tight)
6878 {
6879 double minval = octave::numeric_limits<double>::Inf ();
6880 double maxval = -octave::numeric_limits<double>::Inf ();
6881 double min_pos = octave::numeric_limits<double>::Inf ();
6882 double max_neg = -octave::numeric_limits<double>::Inf ();
6883 get_children_limits (minval, maxval, min_pos, max_neg, kids, limit_type);
6884 if (octave::math::isfinite (minval) && octave::math::isfinite (maxval))
6885 {
6886 limits(0) = minval;
6887 limits(1) = maxval;
6888 s = octave::math::max (s, (maxval - minval) / (pbfactor * dafactor));
6889 }
6890 }
6891 else
6892 s = octave::math::max (s, (limits(1) - limits(0)) / (pbfactor * dafactor));
6893}
6894
6895static std::set<double> updating_aspectratios;
6896
6897void
6898axes::properties::update_aspectratios (void)
6899{
6900 if (updating_aspectratios.find (get___myhandle__ ().value ())
6901 != updating_aspectratios.end ())
6902 return;
6903
6904 Matrix xlimits = get_xlim ().matrix_value ();
6905 Matrix ylimits = get_ylim ().matrix_value ();
6906 Matrix zlimits = get_zlim ().matrix_value ();
6907
6908 double dx = (xlimits(1) - xlimits(0));
6909 double dy = (ylimits(1) - ylimits(0));
6910 double dz = (zlimits(1) - zlimits(0));
6911
6912 Matrix da = get_dataaspectratio ().matrix_value ();
6913 Matrix pba = get_plotboxaspectratio ().matrix_value ();
6914
6915 if (dataaspectratiomode_is ("auto"))
6916 {
6917 if (plotboxaspectratiomode_is ("auto"))
6918 {
6919 pba = Matrix (1, 3, 1.0);
6920 m_plotboxaspectratio.set (pba, false);
6921 }
6922
6923 normalized_aspectratios (da, pba, dx, dy, dz);
6924 m_dataaspectratio.set (da, false);
6925 }
6926 else if (plotboxaspectratiomode_is ("auto"))
6927 {
6928 normalized_aspectratios (pba, da, dx, dy, dz);
6929 m_plotboxaspectratio.set (pba, false);
6930 }
6931 else
6932 {
6934 bool modified_limits = false;
6935 Matrix kids;
6936
6937 if (xlimmode_is ("auto") && ylimmode_is ("auto") && zlimmode_is ("auto"))
6938 {
6939 modified_limits = true;
6940 kids = get_children ();
6941 max_axes_scale (s, xlimits, kids, pba(0), da(0), 'x', true);
6942 max_axes_scale (s, ylimits, kids, pba(1), da(1), 'y', true);
6943 max_axes_scale (s, zlimits, kids, pba(2), da(2), 'z', true);
6944 }
6945 else if (xlimmode_is ("auto") && ylimmode_is ("auto"))
6946 {
6947 modified_limits = true;
6948 max_axes_scale (s, zlimits, kids, pba(2), da(2), 'z', false);
6949 }
6950 else if (ylimmode_is ("auto") && zlimmode_is ("auto"))
6951 {
6952 modified_limits = true;
6953 max_axes_scale (s, xlimits, kids, pba(0), da(0), 'x', false);
6954 }
6955 else if (zlimmode_is ("auto") && xlimmode_is ("auto"))
6956 {
6957 modified_limits = true;
6958 max_axes_scale (s, ylimits, kids, pba(1), da(1), 'y', false);
6959 }
6960
6961 if (modified_limits)
6962 {
6964 restore_var (updating_aspectratios);
6965
6966 updating_aspectratios.insert (get___myhandle__ ().value ());
6967
6968 dx = pba(0) * da(0);
6969 dy = pba(1) * da(1);
6970 dz = pba(2) * da(2);
6971 if (octave::math::isinf (s))
6972 s = 1 / octave::math::min (octave::math::min (dx, dy), dz);
6973
6974 if (xlimmode_is ("auto"))
6975 {
6976 dx = s * dx;
6977 xlimits(0) = 0.5 * (xlimits(0) + xlimits(1) - dx);
6978 xlimits(1) = xlimits(0) + dx;
6979 set_xlim (xlimits);
6980 set_xlimmode ("auto");
6981 }
6982
6983 if (ylimmode_is ("auto"))
6984 {
6985 dy = s * dy;
6986 ylimits(0) = 0.5 * (ylimits(0) + ylimits(1) - dy);
6987 ylimits(1) = ylimits(0) + dy;
6988 set_ylim (ylimits);
6989 set_ylimmode ("auto");
6990 }
6991
6992 if (zlimmode_is ("auto"))
6993 {
6994 dz = s * dz;
6995 zlimits(0) = 0.5 * (zlimits(0) + zlimits(1) - dz);
6996 zlimits(1) = zlimits(0) + dz;
6997 set_zlim (zlimits);
6998 set_zlimmode ("auto");
6999 }
7000 }
7001 else
7002 {
7003 normalized_aspectratios (pba, da, dx, dy, dz);
7004 m_plotboxaspectratio.set (pba, false);
7005 }
7006 }
7007}
7008
7009void
7010axes::properties::update_label_color (handle_property label,
7011 color_property col)
7012{
7013 gh_manager& gh_mgr
7014 = octave::__get_gh_manager__ ("axes::properties::update_label_color");
7015
7016 gh_mgr.get_object (label.handle_value ()).set ("color", col.get ());
7017}
7018
7019void
7020axes::properties::update_font (std::string prop)
7021{
7022 gh_manager& gh_mgr
7023 = octave::__get_gh_manager__ ("axes::properties::update_font");
7024
7025 if (! prop.empty ())
7026 {
7027 octave_value val = get (prop);
7028 octave_value tval = val;
7029 if (prop == "fontsize")
7030 {
7031 tval = octave_value (val.double_value () *
7032 get_titlefontsizemultiplier ());
7033 val = octave_value (val.double_value () *
7034 get_labelfontsizemultiplier ());
7035 }
7036 else if (prop == "fontweight")
7037 tval = get ("titlefontweight");
7038
7039 gh_mgr.get_object (get_xlabel ()).set (prop, val);
7040 gh_mgr.get_object (get_ylabel ()).set (prop, val);
7041 gh_mgr.get_object (get_zlabel ()).set (prop, val);
7042 gh_mgr.get_object (get_title ()).set (prop, tval);
7043 }
7044
7045 double dpr = device_pixel_ratio (get___myhandle__ ());
7046
7047 octave::autolock guard (gh_mgr.graphics_lock ());
7048
7049 m_txt_renderer.set_font (get ("fontname").string_value (),
7050 get ("fontweight").string_value (),
7051 get ("fontangle").string_value (),
7052 get ("__fontsize_points__").double_value () * dpr);
7053}
7054
7055// The INTERNAL flag defines whether position or outerposition is used.
7056
7057Matrix
7058axes::properties::get_boundingbox (bool internal,
7059 const Matrix& parent_pix_size) const
7060{
7061 Matrix pos = (internal ? get_position ().matrix_value ()
7062 : get_outerposition ().matrix_value ());
7063 Matrix parent_size (parent_pix_size);
7064
7065 if (parent_size.isempty ())
7066 {
7067 gh_manager& gh_mgr
7068 = octave::__get_gh_manager__ ("axes::properties::get_boundingbox");
7069
7070 graphics_object go = gh_mgr.get_object (get_parent ());
7071
7072 if (go.valid_object ())
7073 parent_size
7074 = go.get_properties ().get_boundingbox (true).extract_n (0, 2, 1, 2);
7075 else
7076 parent_size = default_figure_position ();
7077 }
7078
7079 pos = convert_position (pos, get_units (), "pixels", parent_size);
7080
7081 pos(0)--;
7082 pos(1)--;
7083 pos(1) = parent_size(1) - pos(1) - pos(3);
7084
7085 return pos;
7086}
7087
7088Matrix
7089axes::properties::get_extent (bool with_text, bool only_text_height) const
7090{
7091 graphics_xform xform = get_transform ();
7092
7093 Matrix ext (1, 4, 0.0);
7094 ext(0) = ext(1) = octave::numeric_limits<double>::Inf ();
7095 ext(2) = ext(3) = -octave::numeric_limits<double>::Inf ();
7096 for (int i = 0; i <= 1; i++)
7097 for (int j = 0; j <= 1; j++)
7098 for (int k = 0; k <= 1; k++)
7099 {
7100 ColumnVector p = xform.transform (i ? m_xPlaneN : m_xPlane,
7101 j ? m_yPlaneN : m_yPlane,
7102 k ? m_zPlaneN : m_zPlane, false);
7103 ext(0) = std::min (ext(0), p(0));
7104 ext(1) = std::min (ext(1), p(1));
7105 ext(2) = std::max (ext(2), p(0));
7106 ext(3) = std::max (ext(3), p(1));
7107 }
7108
7109 if (with_text)
7110 {
7111 for (int i = 0; i < 4; i++)
7112 {
7113 graphics_handle htext;
7114 if (i == 0)
7115 htext = get_title ();
7116 else if (i == 1)
7117 htext = get_xlabel ();
7118 else if (i == 2)
7119 htext = get_ylabel ();
7120 else if (i == 3)
7121 htext = get_zlabel ();
7122
7123 gh_manager& gh_mgr
7124 = octave::__get_gh_manager__ ("axes::properties::get_extent");
7125
7126 text::properties& text_props
7127 = reinterpret_cast<text::properties&>
7128 (gh_mgr.get_object (htext).get_properties ());
7129
7130 Matrix text_pos = text_props.get_data_position ();
7131 text_pos = xform.transform (text_pos(0), text_pos(1), text_pos(2));
7132 if (text_props.get_string ().isempty ())
7133 {
7134 ext(0) = std::min (ext(0), text_pos(0));
7135 ext(1) = std::min (ext(1), text_pos(1));
7136 ext(2) = std::max (ext(2), text_pos(0));
7137 ext(3) = std::max (ext(3), text_pos(1));
7138 }
7139 else
7140 {
7141 Matrix text_ext = text_props.get_extent_matrix (true);
7142
7143 // The text extent is returned in device pixels. Unscale and
7144 // work with logical pixels
7145 double dpr = device_pixel_ratio (get___myhandle__ ());
7146 if (dpr != 1.0)
7147 for (int j = 0; j < 4; j++)
7148 text_ext(j) /= dpr;
7149
7150 bool ignore_horizontal = false;
7151 bool ignore_vertical = false;
7152 if (only_text_height)
7153 {
7154 double text_rotation = text_props.get_rotation ();
7155 if (text_rotation == 0. || text_rotation == 180.)
7156 ignore_horizontal = true;
7157 else if (text_rotation == 90. || text_rotation == 270.)
7158 ignore_vertical = true;
7159 }
7160
7161 if (! ignore_horizontal)
7162 {
7163 ext(0) = std::min (ext(0), text_pos(0)+text_ext(0));
7164 ext(2) = std::max (ext(2),
7165 text_pos(0)+text_ext(0)+text_ext(2));
7166 }
7167
7168 if (! ignore_vertical)
7169 {
7170 ext(1) = std::min (ext(1),
7171 text_pos(1)-text_ext(1)-text_ext(3));
7172 ext(3) = std::max (ext(3), text_pos(1)-text_ext(1));
7173 }
7174 }
7175 }
7176 }
7177
7178 ext(2) = ext(2) - ext(0);
7179 ext(3) = ext(3) - ext(1);
7180
7181 return ext;
7182}
7183
7184static octave_value
7186{
7187 octave_value retval = val;
7188
7189 if (val.iscellstr ())
7190 {
7191 // Always return a column vector for Matlab compatibility
7192 if (val.columns () > 1)
7193 retval = val.reshape (dim_vector (val.numel (), 1));
7194 }
7195 else
7196 {
7197 string_vector sv;
7198 if (val.isnumeric ())
7199 {
7200 NDArray data = val.array_value ();
7201 std::ostringstream oss;
7202 oss.precision (5);
7203 for (octave_idx_type i = 0; i < val.numel (); i++)
7204 {
7205 oss.str ("");
7206 oss << data(i);
7207 sv.append (oss.str ());
7208 }
7209 }
7210 else if (val.is_string () && val.rows () == 1)
7211 {
7212 std::string valstr = val.string_value ();
7213 std::istringstream iss (valstr);
7214 std::string tmpstr;
7215
7216 // Split string with delimiter '|'
7217 while (std::getline (iss, tmpstr, '|'))
7218 sv.append (tmpstr);
7219
7220 // If string ends with '|' Matlab appends a null string
7221 if (*valstr.rbegin () == '|')
7222 sv.append (std::string (""));
7223 }
7224 else
7225 return retval;
7226
7227 charMatrix chmat (sv, ' ');
7228
7229 retval = octave_value (chmat);
7230 }
7231
7232 return retval;
7233}
7234
7235void
7236axes::properties::set_xticklabel (const octave_value& val)
7237{
7238 if (m_xticklabel.set (convert_ticklabel_string (val), false))
7239 {
7240 set_xticklabelmode ("manual");
7241 m_xticklabel.run_listeners (GCB_POSTSET);
7242 mark_modified ();
7243 }
7244 else
7245 set_xticklabelmode ("manual");
7246
7247 sync_positions ();
7248}
7249
7250void
7251axes::properties::set_yticklabel (const octave_value& val)
7252{
7253 if (m_yticklabel.set (convert_ticklabel_string (val), false))
7254 {
7255 set_yticklabelmode ("manual");
7256 m_yticklabel.run_listeners (GCB_POSTSET);
7257 mark_modified ();
7258 }
7259 else
7260 set_yticklabelmode ("manual");
7261
7262 sync_positions ();
7263}
7264
7265void
7266axes::properties::set_zticklabel (const octave_value& val)
7267{
7268 if (m_zticklabel.set (convert_ticklabel_string (val), false))
7269 {
7270 set_zticklabelmode ("manual");
7271 m_zticklabel.run_listeners (GCB_POSTSET);
7272 mark_modified ();
7273 }
7274 else
7275 set_zticklabelmode ("manual");
7276
7277 sync_positions ();
7278}
7279
7280// Almost identical to convert_ticklabel_string but it only accepts
7281// cellstr or string, not numeric input.
7282static octave_value
7284{
7285 octave_value retval = val;
7286
7287 if (val.iscellstr ())
7288 {
7289 // Always return a column vector for Matlab Compatibility
7290 if (val.columns () > 1)
7291 retval = val.reshape (dim_vector (val.numel (), 1));
7292 }
7293 else
7294 {
7295 string_vector sv;
7296 if (val.is_string () && val.rows () == 1)
7297 {
7298 std::string valstr = val.string_value ();
7299 std::istringstream iss (valstr);
7300 std::string tmpstr;
7301
7302 // Split string with delimiter '|'
7303 while (std::getline (iss, tmpstr, '|'))
7304 sv.append (tmpstr);
7305
7306 // If string ends with '|' Matlab appends a null string
7307 if (*valstr.rbegin () == '|')
7308 sv.append (std::string (""));
7309 }
7310 else
7311 return retval;
7312
7313 charMatrix chmat (sv, ' ');
7314
7315 retval = octave_value (chmat);
7316 }
7317
7318 return retval;
7319}
7320
7321void
7322axes::properties::set_linestyleorder (const octave_value& val)
7323{
7324 m_linestyleorder.set (convert_linestyleorder_string (val), false);
7325}
7326
7327void
7328axes::properties::set_units (const octave_value& val)
7329{
7330 caseless_str old_units = get_units ();
7331
7332 if (m_units.set (val, true))
7333 {
7334 update_units (old_units);
7335 mark_modified ();
7336 }
7337}
7338
7339void
7340axes::properties::update_units (const caseless_str& old_units)
7341{
7342 gh_manager& gh_mgr
7343 = octave::__get_gh_manager__ ("axes::properties::update_units");
7344
7345 graphics_object parent_go = gh_mgr.get_object (get_parent ());
7346
7347 Matrix parent_bb
7348 = parent_go.get_properties ().get_boundingbox (true).extract_n (0, 2, 1, 2);
7349
7350 caseless_str new_units = get_units ();
7351 m_position.set (octave_value
7352 (convert_position (get_position ().matrix_value (),
7353 old_units, new_units, parent_bb)),
7354 false);
7355 m_outerposition.set (octave_value
7356 (convert_position (get_outerposition ().matrix_value (),
7357 old_units, new_units, parent_bb)),
7358 false);
7359 m_tightinset.set (octave_value
7360 (convert_position (get_tightinset ().matrix_value (),
7361 old_units, new_units, parent_bb)),
7362 false);
7363 m_looseinset.set (octave_value
7364 (convert_position (get_looseinset ().matrix_value (),
7365 old_units, new_units, parent_bb)),
7366 false);
7367}
7368
7369void
7370axes::properties::set_fontunits (const octave_value& val)
7371{
7372 caseless_str old_fontunits = get_fontunits ();
7373
7374 if (m_fontunits.set (val, true))
7375 {
7376 update_fontunits (old_fontunits);
7377 mark_modified ();
7378 }
7379}
7380
7381void
7382axes::properties::update_fontunits (const caseless_str& old_units)
7383{
7384 caseless_str new_units = get_fontunits ();
7385 double parent_height = get_boundingbox (true).elem (3);
7386 double fontsz = get_fontsize ();
7387
7388 fontsz = convert_font_size (fontsz, old_units, new_units, parent_height);
7389
7390 set_fontsize (octave_value (fontsz));
7391}
7392
7393double
7394axes::properties::get___fontsize_points__ (double box_pix_height) const
7395{
7396 double fontsz = get_fontsize ();
7397 double parent_height = box_pix_height;
7398
7399 if (fontunits_is ("normalized") && parent_height <= 0)
7400 parent_height = get_boundingbox (true).elem (3);
7401
7402 return convert_font_size (fontsz, get_fontunits (), "points", parent_height);
7403}
7404
7406graphics_xform::xform_vector (double x, double y, double z)
7407{
7408 return octave::xform_vector (x, y, z);
7409}
7410
7411Matrix
7412graphics_xform::xform_eye (void)
7413{
7414 return octave::xform_matrix ();
7415}
7416
7418graphics_xform::transform (double x, double y, double z, bool use_scale) const
7419{
7420 if (use_scale)
7421 {
7422 x = m_sx.scale (x);
7423 y = m_sy.scale (y);
7424 z = m_sz.scale (z);
7425 }
7426
7427 return octave::transform (m_xform, x, y, z);
7428}
7429
7431graphics_xform::untransform (double x, double y, double z,
7432 bool use_scale) const
7433{
7434 ColumnVector v = octave::transform (m_xform_inv, x, y, z);
7435
7436 if (use_scale)
7437 {
7438 v(0) = m_sx.unscale (v(0));
7439 v(1) = m_sy.unscale (v(1));
7440 v(2) = m_sz.unscale (v(2));
7441 }
7442
7443 return v;
7444}
7445
7447axes::get_default (const caseless_str& pname) const
7448{
7449 octave_value retval = m_default_properties.lookup (pname);
7450
7451 if (retval.is_undefined ())
7452 {
7453 graphics_handle parent_h = get_parent ();
7454
7455 gh_manager& gh_mgr
7456 = octave::__get_gh_manager__ ("axes::properties::get_default");
7457
7458 graphics_object parent_go = gh_mgr.get_object (parent_h);
7459
7460 retval = parent_go.get_default (pname);
7461 }
7462
7463 return retval;
7464}
7465
7466// FIXME: remove.
7467// FIXME: maybe this should go into array_property class?
7468/*
7469static void
7470check_limit_vals (double& min_val, double& max_val,
7471 double& min_pos, double& max_neg,
7472 const array_property& data)
7473{
7474 double val = data.min_val ();
7475 if (octave::math::isfinite (val) && val < min_val)
7476 min_val = val;
7477 val = data.max_val ();
7478 if (octave::math::isfinite (val) && val > max_val)
7479 max_val = val;
7480 val = data.min_pos ();
7481 if (octave::math::isfinite (val) && val > 0 && val < min_pos)
7482 min_pos = val;
7483 val = data.max_neg ();
7484 if (octave::math::isfinite (val) && val < 0 && val > max_neg)
7485 max_neg = val;
7486}
7487*/
7488
7489static void
7490check_limit_vals (double& min_val, double& max_val,
7491 double& min_pos, double& max_neg,
7492 const octave_value& data)
7493{
7494 Matrix m;
7495
7496 if (data.is_matrix_type ())
7497 m = data.matrix_value ();
7498
7499 if (m.numel () != 4)
7500 {
7501 m = Matrix (1, 4, 0.0);
7504 }
7505
7506 double val;
7507
7508 val = m(0);
7509 if (octave::math::isfinite (val) && val < min_val)
7510 min_val = val;
7511
7512 val = m(1);
7513 if (octave::math::isfinite (val) && val > max_val)
7514 max_val = val;
7515
7516 val = m(2);
7517 if (octave::math::isfinite (val) && val > 0 && val < min_pos)
7518 min_pos = val;
7519
7520 val = m(3);
7521 if (octave::math::isfinite (val) && val < 0 && val > max_neg)
7522 max_neg = val;
7523}
7524
7525// magform(x) Returns (a, b),
7526// where x = a * 10^b, abs (a) >= 1., and b is integer.
7527
7528static void
7529magform (double x, double& a, int& b)
7530{
7531 if (x == 0)
7532 {
7533 a = 0;
7534 b = 0;
7535 }
7536 else
7537 {
7538 b = static_cast<int> (std::floor (std::log10 (std::abs (x))));
7539 a = x / std::pow (10.0, b);
7540 }
7541}
7542
7543void
7544axes::properties::update_outerposition (void)
7545{
7546 set_positionconstraint ("outerposition");
7547 caseless_str old_units = get_units ();
7548 set_units ("normalized");
7549
7550 Matrix outerbox = m_outerposition.get ().matrix_value ();
7551
7552 double outer_left = outerbox(0);
7553 double outer_bottom = outerbox(1);
7554 double outer_width = outerbox(2);
7555 double outer_height = outerbox(3);
7556
7557 double outer_right = outer_width + outer_left;
7558 double outer_top = outer_height + outer_bottom;
7559
7560 Matrix linset = m_looseinset.get ().matrix_value ();
7561 Matrix tinset = m_tightinset.get ().matrix_value ();
7562
7563 double left_margin = std::max (linset(0), tinset(0));
7564 double bottom_margin = std::max (linset(1), tinset(1));
7565 double right_margin = std::max (linset(2), tinset(2));
7566 double top_margin = std::max (linset(3), tinset(3));
7567
7568 double inner_left = outer_left;
7569 double inner_right = outer_right;
7570
7571 if ((left_margin + right_margin) < outer_width)
7572 {
7573 inner_left += left_margin;
7574 inner_right -= right_margin;
7575 }
7576
7577 double inner_bottom = outer_bottom;
7578 double inner_top = outer_top;
7579
7580 if ((bottom_margin + top_margin) < outer_height)
7581 {
7582 inner_bottom += bottom_margin;
7583 inner_top -= top_margin;
7584 }
7585
7586 double inner_width = inner_right - inner_left;
7587 double inner_height = inner_top - inner_bottom;
7588
7589 Matrix innerbox (1, 4);
7590
7591 innerbox(0) = inner_left;
7592 innerbox(1) = inner_bottom;
7593 innerbox(2) = inner_width;
7594 innerbox(3) = inner_height;
7595
7596 m_position = innerbox;
7597
7598 set_units (old_units);
7599 update_transform ();
7600}
7601
7602void
7603axes::properties::update_position (void)
7604{
7605 set_positionconstraint ("innerposition");
7606 caseless_str old_units = get_units ();
7607 set_units ("normalized");
7608
7609 Matrix innerbox = m_position.get ().matrix_value ();
7610
7611 double inner_left = innerbox(0);
7612 double inner_bottom = innerbox(1);
7613 double inner_width = innerbox(2);
7614 double inner_height = innerbox(3);
7615
7616 double inner_right = inner_width + inner_left;
7617 double inner_top = inner_height + inner_bottom;
7618
7619 Matrix linset = m_looseinset.get ().matrix_value ();
7620 Matrix tinset = m_tightinset.get ().matrix_value ();
7621
7622 double left_margin = std::max (linset(0), tinset(0));
7623 double bottom_margin = std::max (linset(1), tinset(1));
7624 double right_margin = std::max (linset(2), tinset(2));
7625 double top_margin = std::max (linset(3), tinset(3));
7626
7627 // FIXME: do we need to place limits on any of these?
7628
7629 double outer_left = inner_left - left_margin;
7630 double outer_bottom = inner_bottom - bottom_margin;
7631 double outer_right = inner_right + right_margin;
7632 double outer_top = inner_top + top_margin;
7633
7634 double outer_width = outer_right - outer_left;
7635 double outer_height = outer_top - outer_bottom;
7636
7637 Matrix outerbox (1, 4);
7638
7639 outerbox(0) = outer_left;
7640 outerbox(1) = outer_bottom;
7641 outerbox(2) = outer_width;
7642 outerbox(3) = outer_height;
7643
7644 m_outerposition = outerbox;
7645
7646 set_units (old_units);
7647 update_transform ();
7648}
7649
7650void
7651axes::properties::update_looseinset (void)
7652{
7653 caseless_str old_units = get_units ();
7654 set_units ("normalized");
7655
7656 Matrix linset = m_looseinset.get ().matrix_value ();
7657 Matrix tinset = m_tightinset.get ().matrix_value ();
7658
7659 double left_margin = std::max (linset(0), tinset(0));
7660 double bottom_margin = std::max (linset(1), tinset(1));
7661 double right_margin = std::max (linset(2), tinset(2));
7662 double top_margin = std::max (linset(3), tinset(3));
7663
7664 if (m_positionconstraint.is ("innerposition"))
7665 {
7666 Matrix innerbox = m_position.get ().matrix_value ();
7667
7668 double inner_left = innerbox(0);
7669 double inner_bottom = innerbox(1);
7670 double inner_width = innerbox(2);
7671 double inner_height = innerbox(3);
7672
7673 double inner_right = inner_width + inner_left;
7674 double inner_top = inner_height + inner_bottom;
7675
7676 // FIXME: do we need to place limits on any of these?
7677
7678 double outer_left = inner_left - left_margin;
7679 double outer_bottom = inner_bottom - bottom_margin;
7680 double outer_right = inner_right + right_margin;
7681 double outer_top = inner_top + top_margin;
7682
7683 double outer_width = outer_right - outer_left;
7684 double outer_height = outer_top - outer_bottom;
7685
7686 Matrix outerbox (1, 4);
7687
7688 outerbox(0) = outer_left;
7689 outerbox(1) = outer_bottom;
7690 outerbox(2) = outer_width;
7691 outerbox(3) = outer_height;
7692
7693 m_outerposition = outerbox;
7694 }
7695 else
7696 {
7697 Matrix outerbox = m_outerposition.get ().matrix_value ();
7698
7699 double outer_left = outerbox(0);
7700 double outer_bottom = outerbox(1);
7701 double outer_width = outerbox(2);
7702 double outer_height = outerbox(3);
7703
7704 double outer_right = outer_width + outer_left;
7705 double outer_top = outer_height + outer_bottom;
7706
7707 double inner_left = outer_left;
7708 double inner_right = outer_right;
7709
7710 if ((left_margin + right_margin) < outer_width)
7711 {
7712 inner_left += left_margin;
7713 inner_right -= right_margin;
7714 }
7715
7716 double inner_bottom = outer_bottom;
7717 double inner_top = outer_top;
7718
7719 if ((bottom_margin + top_margin) < outer_height)
7720 {
7721 inner_bottom += bottom_margin;
7722 inner_top -= top_margin;
7723 }
7724
7725 double inner_width = inner_right - inner_left;
7726 double inner_height = inner_top - inner_bottom;
7727
7728 Matrix innerbox (1, 4);
7729
7730 innerbox(0) = inner_left;
7731 innerbox(1) = inner_bottom;
7732 innerbox(2) = inner_width;
7733 innerbox(3) = inner_height;
7734
7735 m_position = innerbox;
7736 }
7737
7738 set_units (old_units);
7739 update_transform ();
7740}
7741
7742// A translation from Tom Holoryd's python code at
7743// http://kurage.nimh.nih.gov/tomh/tics.py
7744// FIXME: add log ticks
7745
7746double
7747axes::properties::calc_tick_sep (double lo, double hi)
7748{
7749 int ticint = 5;
7750
7751 // Reference: Lewart, C. R., "Algorithms SCALE1, SCALE2, and SCALE3 for
7752 // Determination of Scales on Computer Generated Plots", Communications of
7753 // the ACM, 10 (1973), 639-640.
7754 // Also cited as ACM Algorithm 463.
7755
7756 double a;
7757 int b, x;
7758
7759 magform ((hi - lo) / ticint, a, b);
7760
7761 static const double sqrt_2 = sqrt (2.0);
7762 static const double sqrt_10 = sqrt (10.0);
7763 static const double sqrt_50 = sqrt (50.0);
7764
7765 if (a < sqrt_2)
7766 x = 1;
7767 else if (a < sqrt_10)
7768 x = 2;
7769 else if (a < sqrt_50)
7770 x = 5;
7771 else
7772 x = 10;
7773
7774 return x * std::pow (10., b);
7775}
7776
7777// Attempt to make "nice" limits from the actual max and min of the data.
7778// For log plots, we will also use the smallest strictly positive value.
7779
7780Matrix
7781axes::properties::get_axis_limits (double xmin, double xmax,
7782 double min_pos, double max_neg,
7783 const bool logscale)
7784{
7785 Matrix retval;
7786
7787 double min_val = xmin;
7788 double max_val = xmax;
7789
7790 if (octave::math::isinf (min_val) && min_val > 0
7791 && octave::math::isinf (max_val) && max_val < 0)
7792 {
7793 retval = default_lim (logscale);
7794 return retval;
7795 }
7796 else if (! (octave::math::isinf (min_val) || octave::math::isinf (max_val)))
7797 {
7798 if (logscale)
7799 {
7800 if (octave::math::isinf (min_pos) && octave::math::isinf (max_neg))
7801 {
7802 // FIXME: max_neg is needed for "loglog ([0 -Inf])"
7803 // This is the *only* place where max_neg is needed.
7804 // Is there another way?
7805 retval = default_lim (logscale);
7806 return retval;
7807 }
7808 if (min_val <= 0)
7809 {
7810 if (max_val > 0)
7811 {
7812 warning_with_id ("Octave:negative-data-log-axis",
7813 "axis: omitting non-positive data in log plot");
7814 min_val = min_pos;
7815 }
7816 else if (max_val == 0)
7817 max_val = max_neg;
7818 }
7819 // FIXME: maybe this test should also be relative?
7820 if (std::abs (min_val - max_val)
7821 < sqrt (std::numeric_limits<double>::epsilon ()))
7822 {
7823 // Widen range when too small
7824 if (min_val >= 0)
7825 {
7826 min_val *= 0.9;
7827 max_val *= 1.1;
7828 }
7829 else
7830 {
7831 min_val *= 1.1;
7832 max_val *= 0.9;
7833 }
7834 }
7835 if (min_val > 0)
7836 {
7837 // Log plots with all positive data
7838 min_val = std::pow (10, std::floor (log10 (min_val)));
7839 max_val = std::pow (10, std::ceil (log10 (max_val)));
7840 }
7841 else
7842 {
7843 // Log plots with all negative data
7844 min_val = -std::pow (10, std::ceil (log10 (-min_val)));
7845 max_val = -std::pow (10, std::floor (log10 (-max_val)));
7846 }
7847 }
7848 else
7849 {
7850 if (min_val == 0 && max_val == 0)
7851 {
7852 min_val = -1;
7853 max_val = 1;
7854 }
7855 // FIXME: maybe this test should also be relative?
7856 else if (std::abs (min_val - max_val)
7857 < sqrt (std::numeric_limits<double>::epsilon ()))
7858 {
7859 min_val -= 0.1 * std::abs (min_val);
7860 max_val += 0.1 * std::abs (max_val);
7861 }
7862
7863 double tick_sep = calc_tick_sep (min_val, max_val);
7864 double min_tick = std::floor (min_val / tick_sep);
7865 double max_tick = std::ceil (max_val / tick_sep);
7866 // Prevent round-off from cropping ticks
7867 min_val = std::min (min_val, tick_sep * min_tick);
7868 max_val = std::max (max_val, tick_sep * max_tick);
7869 }
7870 }
7871
7872 retval.resize (1, 2);
7873
7874 retval(0) = min_val;
7875 retval(1) = max_val;
7876
7877 return retval;
7878}
7879
7880void
7881axes::properties::check_axis_limits (Matrix& limits, const Matrix kids,
7882 const bool logscale, char& update_type)
7883{
7884 double min_val = octave::numeric_limits<double>::Inf ();
7885 double max_val = -octave::numeric_limits<double>::Inf ();
7886 double min_pos = octave::numeric_limits<double>::Inf ();
7887 double max_neg = -octave::numeric_limits<double>::Inf ();
7888 double eps = std::numeric_limits<double>::epsilon ();
7889 bool do_update = false;
7890 bool have_children_limits = false;
7891
7892 // check whether we need to get children limits
7893 if (! octave::math::isfinite (limits(0))
7894 || ! octave::math::isfinite (limits(1)))
7895 {
7896 get_children_limits (min_val, max_val, min_pos, max_neg, kids,
7897 update_type);
7898 have_children_limits = true;
7899 }
7900 if (! octave::math::isfinite (limits(0)))
7901 {
7902 limits(0) = min_val;
7903 do_update = true;
7904 }
7905 if (! octave::math::isfinite (limits(1)))
7906 {
7907 limits(1) = max_val;
7908 do_update = true;
7909 }
7910 if (limits(0) == 0 && limits(1) == 0)
7911 {
7912 limits = default_lim (logscale);
7913 do_update = true;
7914 }
7915 // FIXME: maybe this test should also be relative?
7916 else if (! logscale && (std::abs (limits(0) - limits(1)) < sqrt (eps)))
7917 {
7918 limits(0) -= 0.1 * std::abs (limits(0));
7919 limits(1) += 0.1 * std::abs (limits(1));
7920 do_update = true;
7921 }
7922 else if (logscale
7923 && (std::abs (std::log10 (limits(0) / limits(1))) < sqrt (eps)))
7924 {
7925 limits(0) = (limits(0) < 0 ? 10.0 * limits(0) : 0.1 * limits(0));
7926 limits(1) = (limits(1) < 0 ? 0.1 * limits(1) : 10.0 * limits(1));
7927 do_update = true;
7928 }
7929
7930 if (logscale && limits(0)*limits(1) <= 0)
7931 {
7932 if (! have_children_limits)
7933 get_children_limits (min_val, max_val, min_pos, max_neg, kids,
7934 update_type);
7935
7936 if (limits(1) > 0)
7937 {
7938 warning_with_id ("Octave:axis-non-positive-log-limits",
7939 "Non-positive limit for logarithmic axis ignored\n");
7940 if (octave::math::isfinite (min_pos))
7941 limits(0) = min_pos;
7942 else
7943 limits(0) = 0.1 * limits(1);
7944 }
7945 else
7946 {
7947 warning_with_id ("Octave:axis-non-negative-log-limits",
7948 "Non-negative limit for logarithmic axis ignored\n");
7949 if (octave::math::isfinite (max_neg))
7950 limits(1) = max_neg;
7951 else
7952 limits(1) = 0.1 * limits(0);
7953 }
7954 // FIXME: maybe this test should also be relative?
7955 if (std::abs (limits(0) - limits(1)) < sqrt (eps))
7956 {
7957 // Widen range when too small
7958 if (limits(0) > 0)
7959 {
7960 limits(0) *= 0.9;
7961 limits(1) *= 1.1;
7962 }
7963 else
7964 {
7965 limits(0) *= 1.1;
7966 limits(1) *= 0.9;
7967 }
7968 }
7969 do_update = true;
7970 }
7971
7972 if (! do_update)
7973 update_type = 0;
7974
7975}
7976
7977/*
7978## Test validation of auto and manual axis limits
7979%!test
7980%! hf = figure ("visible", "off");
7981%! unwind_protect
7982%! hax = axes ("parent", hf);
7983%! plot (0, pi);
7984%! assert (get (hax, "xlim"), [-1, 1]);
7985%! assert (get (hax, "xlimmode"), "auto");
7986%! assert (get (hax, "ylim"), [2.8, 3.5], 2*eps);
7987%! assert (get (hax, "ylimmode"), "auto");
7988%! set (hax, "xlim", [1, 1], "ylim", [0, 0]);
7989%! assert (get (hax, "xlim"), [0.9, 1.1]);
7990%! assert (get (hax, "xlimmode"), "manual");
7991%! assert (get (hax, "ylim"), [0, 1]);
7992%! assert (get (hax, "ylimmode"), "manual");
7993%! set (hax, "xlim", [-Inf, Inf], "ylim", [-Inf, Inf]);
7994%! ## Matlab does not update the properties
7995%! assert (get (hax, "xlim"), [0, 1]);
7996%! assert (get (hax, "ylim"), [0.9, 1.1]*pi, 2*eps);
7997%! unwind_protect_cleanup
7998%! delete (hf);
7999%! end_unwind_protect
8000
8001%!test
8002%! hf = figure ("visible", "off");
8003%! unwind_protect
8004%! hax = axes ("parent", hf);
8005%! plot ([0, exp(1)], [-pi, 3]);
8006%! assert (get (hax, "xlim"), [0, 3]);
8007%! assert (get (hax, "xlimmode"), "auto");
8008%! assert (get (hax, "ylim"), [-4, 3]);
8009%! assert (get (hax, "ylimmode"), "auto");
8010%! set (hax, "xlim", [-Inf, Inf], "ylim", [-Inf, Inf]);
8011%! ## Matlab does not update the properties but uses tight limits on screen
8012%! assert (get (hax, "xlim"), [0, exp(1)]);
8013%! assert (get (hax, "xlimmode"), "manual");
8014%! assert (get (hax, "ylim"), [-pi, 3]);
8015%! assert (get (hax, "ylimmode"), "manual");
8016%! unwind_protect_cleanup
8017%! delete (hf);
8018%! end_unwind_protect
8019
8020%!test
8021%! hf = figure ("visible", "off");
8022%! unwind_protect
8023%! hax = axes ("parent", hf);
8024%! loglog (0, pi);
8025%! assert (get (hax, "xlim"), [0.1, 1.0]);
8026%! assert (get (hax, "xlimmode"), "auto");
8027%! assert (get (hax, "ylim"), [1, 10]);
8028%! assert (get (hax, "ylimmode"), "auto");
8029%! set (hax, "xlim", [1, 1], "ylim", [0, 0]);
8030%! assert (get (hax, "xlim"), [0.1, 10]);
8031%! assert (get (hax, "xlimmode"), "manual");
8032%! assert (get (hax, "ylim"), [0.1, 1.0]);
8033%! assert (get (hax, "ylimmode"), "manual");
8034%! set (hax, "xlim", [-Inf, Inf], "ylim", [-Inf, Inf]);
8035%! ## Matlab does not update the properties
8036%! assert (get (hax, "xlim"), [0.1, 1.0]);
8037%! assert (get (hax, "ylim"), [0.1, 10]*pi);
8038%! unwind_protect_cleanup
8039%! delete (hf);
8040%! end_unwind_protect
8041
8042%!test
8043%! hf = figure ("visible", "off");
8044%! unwind_protect
8045%! hax = axes ("parent", hf);
8046%! loglog ([0 -1], [0 1]);
8047%! assert (get (hax, "xlim"), [-10, -0.1]);
8048%! assert (get (hax, "xlimmode"), "auto");
8049%! assert (get (hax, "ylim"), [0.1, 10]);
8050%! assert (get (hax, "ylimmode"), "auto");
8051%! set (hax, "xlim", [-Inf, Inf], "ylim", [-Inf, Inf]);
8052%! ## Matlab does not update the properties
8053%! assert (get (hax, "xlim"), [-1.1, -0.9]);
8054%! assert (get (hax, "ylim"), [0.9, 1.1]);
8055%! unwind_protect_cleanup
8056%! delete (hf);
8057%! end_unwind_protect
8058
8059%!test
8060%! hf = figure ("visible", "off");
8061%! unwind_protect
8062%! hax = axes ("parent", hf);
8063%! loglog ([1 -1], [1 pi]);
8064%! assert (get (hax, "xlim"), [0.1, 10]);
8065%! assert (get (hax, "xlimmode"), "auto");
8066%! assert (get (hax, "ylim"), [1, 10]);
8067%! assert (get (hax, "ylimmode"), "auto");
8068%! set (hax, "xlim", [-Inf, Inf], "ylim", [-Inf, Inf]);
8069%! ## Matlab does not update the properties but uses tight limits on screen
8070%! assert (get (hax, "xlim"), [0.9, 1.1]);
8071%! assert (get (hax, "ylim"), [1, pi]);
8072%! unwind_protect_cleanup
8073%! delete (hf);
8074%! end_unwind_protect
8075*/
8076
8077void
8078axes::properties::calc_ticks_and_lims (array_property& lims,
8079 array_property& ticks,
8080 array_property& mticks,
8081 bool limmode_is_auto,
8082 bool tickmode_is_auto,
8083 bool is_logscale)
8084{
8085 if (lims.get ().isempty ())
8086 return;
8087
8088 double lo = (lims.get ().matrix_value ())(0);
8089 double hi = (lims.get ().matrix_value ())(1);
8090 double lo_lim = lo;
8091 double hi_lim = hi;
8092 bool is_negative = lo < 0 && hi < 0;
8093
8094 // FIXME: should this be checked for somewhere else? (i.e., set{x,y,z}lim)
8095 if (hi < lo)
8096 std::swap (hi, lo);
8097
8098 if (is_logscale)
8099 {
8100 if (is_negative)
8101 {
8102 double tmp = hi;
8103 hi = std::log10 (-lo);
8104 lo = std::log10 (-tmp);
8105 }
8106 else
8107 {
8108 hi = std::log10 (hi);
8109 lo = std::log10 (lo);
8110 }
8111 }
8112
8113 Matrix tmp_ticks;
8114 if (tickmode_is_auto)
8115 {
8116 double tick_sep;
8117
8118 if (is_logscale)
8119 {
8120 if (! (octave::math::isinf (hi) || octave::math::isinf (lo)))
8121 tick_sep = 1; // Tick is every order of magnitude (bug #39449)
8122 else
8123 tick_sep = 0;
8124 }
8125 else
8126 tick_sep = calc_tick_sep (lo, hi);
8127
8128 double i1 = std::floor (lo / tick_sep);
8129 double i2 = std::ceil (hi / tick_sep);
8130
8131 if (limmode_is_auto)
8132 {
8133 // Adjust limits to include min and max ticks
8134 Matrix tmp_lims (1, 2);
8135 tmp_lims(0) = std::min (tick_sep * i1, lo);
8136 tmp_lims(1) = std::max (tick_sep * i2, hi);
8137
8138 if (is_logscale)
8139 {
8140 tmp_lims(0) = std::pow (10., tmp_lims(0));
8141 tmp_lims(1) = std::pow (10., tmp_lims(1));
8142 if (tmp_lims(0) <= 0)
8143 tmp_lims(0) = std::pow (10., lo);
8144 if (is_negative)
8145 {
8146 double tmp = tmp_lims(0);
8147 tmp_lims(0) = -tmp_lims(1);
8148 tmp_lims(1) = -tmp;
8149 }
8150 }
8151 lims = tmp_lims;
8152 }
8153 else
8154 {
8155 // adjust min and max ticks to be within limits
8156 if (i1*tick_sep < lo)
8157 i1++;
8158 if (i2*tick_sep > hi && i2 > i1)
8159 i2--;
8160 }
8161
8162 tmp_ticks = Matrix (1, i2-i1+1);
8163 for (int i = 0; i <= static_cast<int> (i2-i1); i++)
8164 {
8165 tmp_ticks(i) = tick_sep * (i+i1);
8166 if (is_logscale)
8167 tmp_ticks(i) = std::pow (10., tmp_ticks(i));
8168 }
8169 if (is_logscale && is_negative)
8170 {
8171 Matrix rev_ticks (1, i2-i1+1);
8172 rev_ticks = -tmp_ticks;
8173 for (int i = 0; i <= static_cast<int> (i2-i1); i++)
8174 tmp_ticks(i) = rev_ticks(i2-i1-i);
8175 }
8176
8177 ticks = tmp_ticks;
8178 }
8179 else
8180 tmp_ticks = ticks.get ().matrix_value ();
8181
8182 octave_idx_type n_ticks = tmp_ticks.numel ();
8183 if (n_ticks < 2)
8184 return;
8185
8186 // minor ticks between, above, and below min and max ticks
8187 int n = (is_logscale ? 8 : 4);
8188 double mult_below = (is_logscale ? tmp_ticks(1) / tmp_ticks(0) : 1);
8189 double mult_above = (is_logscale ? tmp_ticks(n_ticks-1) / tmp_ticks(n_ticks-2)
8190 : 1);
8191
8192 double d_below = (tmp_ticks(1) - tmp_ticks(0)) / mult_below / (n+1);
8193 int n_below = static_cast<int> (std::floor ((tmp_ticks(0)-lo_lim) / d_below));
8194 if (n_below < 0)
8195 n_below = 0;
8196 int n_between = n * (n_ticks - 1);
8197 double d_above = (tmp_ticks(n_ticks-1) - tmp_ticks(n_ticks-2)) * mult_above
8198 / (n+1);
8199 int n_above = static_cast<int> (std::floor ((hi_lim-tmp_ticks(n_ticks-1))
8200 / d_above));
8201 if (n_above < 0)
8202 n_above = 0;
8203
8204 Matrix tmp_mticks (1, n_below + n_between + n_above);
8205 for (int i = 0; i < n_below; i++)
8206 tmp_mticks(i) = tmp_ticks(0) - (n_below-i) * d_below;
8207 for (int i = 0; i < n_ticks-1; i++)
8208 {
8209 double d = (tmp_ticks(i+1) - tmp_ticks(i)) / (n + 1);
8210 for (int j = 0; j < n; j++)
8211 tmp_mticks(n_below+n*i+j) = tmp_ticks(i) + d * (j+1);
8212 }
8213 for (int i = 0; i < n_above; i++)
8214 tmp_mticks(n_below+n_between+i) = tmp_ticks(n_ticks-1) + (i + 1) * d_above;
8215
8216 mticks = tmp_mticks;
8217}
8218
8219/*
8220%!test <*45356>
8221%! hf = figure ("visible", "off");
8222%! unwind_protect
8223%! plot (1:10);
8224%! xlim ([4.75, 8.5]);
8225%! tics = get (gca, "xtick");
8226%! assert (tics, [5 6 7 8]);
8227%! unwind_protect_cleanup
8228%! close (hf);
8229%! end_unwind_protect
8230*/
8231
8232void
8233axes::properties::calc_ticklabels (const array_property& ticks,
8234 any_property& labels, bool logscale,
8235 const bool is_origin,
8236 const int other_axislocation,
8237 const array_property& axis_lims)
8238{
8239 Matrix values = ticks.get ().matrix_value ();
8240 Matrix lims = axis_lims.get ().matrix_value ();
8241 Cell c (values.dims ());
8242 std::ostringstream os;
8243
8244 // omit tick labels depending on location of other axis
8246 if (get_is2D () && is_origin)
8247 {
8248 if (other_axislocation == 0)
8249 {
8250 omit_ticks(0) = octave::math::max (octave::math::min (0., lims(1)),
8251 lims(0));
8252 }
8253 else if (other_axislocation == 1)
8254 omit_ticks(0) = lims(1);
8255 else if (other_axislocation == -1)
8256 omit_ticks(0) = lims(0);
8257 if (is_box ())
8258 {
8259 omit_ticks(1) = lims(0);
8260 omit_ticks(2) = lims(1);
8261 }
8262 }
8263
8264 if (logscale)
8265 {
8266 double significand;
8267 double exponent;
8268 double exp_max = 0.0;
8269 double exp_min = 0.0;
8270
8271 for (int i = 0; i < values.numel (); i++)
8272 {
8273 double exp = std::log10 (values(i));
8274 exp_min = std::min (exp_min, exp);
8275 exp_max = std::max (exp_max, exp);
8276 }
8277
8278 for (int i = 0; i < values.numel (); i++)
8279 {
8280 bool omit_tick = false;
8281 for (int i_omit = 0; i_omit < omit_ticks.numel (); i_omit++)
8282 if (values(i) == omit_ticks(i_omit))
8283 omit_tick = true;
8284 if (omit_tick)
8285 {
8286 c(i) = "";
8287 continue;
8288 }
8289
8290 if (values(i) < 0.0)
8291 exponent = std::floor (std::log10 (-values(i)));
8292 else
8293 exponent = std::floor (std::log10 (values(i)));
8294 significand = values(i) * std::pow (10., -exponent);
8295
8296 os.str ("");
8297 if ((std::abs (significand) - 1) >
8298 10*std::numeric_limits<double>::epsilon())
8299 os << significand << 'x';
8300 else if (significand < 0)
8301 os << '-';
8302
8303 os << "10^{";
8304
8305 if (exponent < 0.0)
8306 {
8307 os << '-';
8308 exponent = -exponent;
8309 }
8310 if (exponent < 10. && (exp_max > 9 || exp_min < -9))
8311 os << '0';
8312 os << exponent << '}';
8313
8314 if (m_ticklabelinterpreter.is ("latex"))
8315 c(i) = "$" + os.str () + "$";
8316 else
8317 c(i) = os.str ();
8318 }
8319 }
8320 else
8321 {
8322 for (int i = 0; i < values.numel (); i++)
8323 {
8324 bool omit_tick = false;
8325 for (int i_omit = 0; i_omit < omit_ticks.numel (); i_omit++)
8326 if (values(i) == omit_ticks(i_omit))
8327 omit_tick = true;
8328 if (omit_tick)
8329 c(i) = "";
8330 else
8331 {
8332 os.str ("");
8333 os << values(i);
8334 c(i) = os.str ();
8335 }
8336 }
8337 }
8338
8339 labels = c;
8340}
8341
8342Matrix
8343axes::properties::get_ticklabel_extents (const Matrix& ticks,
8344 const string_vector& ticklabels,
8345 const Matrix& limits)
8346{
8347 Matrix ext (1, 2, 0.0);
8348 double wmax, hmax;
8349 double dpr = device_pixel_ratio (get___myhandle__ ());
8350 wmax = hmax = 0.0;
8351 int n = std::min (ticklabels.numel (), ticks.numel ());
8352 for (int i = 0; i < n; i++)
8353 {
8354 double val = ticks(i);
8355 if (limits(0) <= val && val <= limits(1))
8356 {
8357 std::string label (ticklabels(i));
8358 label.erase (0, label.find_first_not_of (' '));
8359 label = label.substr (0, label.find_last_not_of (' ')+1);
8360
8361 if (m_txt_renderer.ok ())
8362 {
8363 gh_manager& gh_mgr
8364 = octave::__get_gh_manager__ ("axes::properties::get_ticklabel_extents");
8365
8366 octave::autolock guard (gh_mgr.graphics_lock ());
8367
8368 ext = m_txt_renderer.get_extent (label, 0.0,
8369 get_ticklabelinterpreter ());
8370
8371 wmax = std::max (wmax, ext(0) / dpr);
8372 hmax = std::max (hmax, ext(1) / dpr);
8373 }
8374 else
8375 {
8376 // FIXME: find a better approximation
8377 double fsize = get ("fontsize").double_value ();
8378 int len = label.length ();
8379
8380 wmax = std::max (wmax, 0.5*fsize*len);
8381 hmax = fsize;
8382 }
8383 }
8384 }
8385
8386 ext(0) = wmax;
8387 ext(1) = hmax;
8388 return ext;
8389}
8390
8391void
8392get_children_limits (double& min_val, double& max_val,
8393 double& min_pos, double& max_neg,
8394 const Matrix& kids, char limit_type)
8395{
8396 octave_idx_type n = kids.numel ();
8397
8398 gh_manager& gh_mgr = octave::__get_gh_manager__ ("get_children_limits");
8399
8400 switch (limit_type)
8401 {
8402 case 'x':
8403 for (octave_idx_type i = 0; i < n; i++)
8404 {
8405 graphics_object go = gh_mgr.get_object (kids(i));
8406
8407 if (go.is_xliminclude ())
8408 {
8409 octave_value lim = go.get_xlim ();
8410
8411 check_limit_vals (min_val, max_val, min_pos, max_neg, lim);
8412 }
8413 }
8414 break;
8415
8416 case 'y':
8417 for (octave_idx_type i = 0; i < n; i++)
8418 {
8419 graphics_object go = gh_mgr.get_object (kids(i));
8420
8421 if (go.is_yliminclude ())
8422 {
8423 octave_value lim = go.get_ylim ();
8424
8425 check_limit_vals (min_val, max_val, min_pos, max_neg, lim);
8426 }
8427 }
8428 break;
8429
8430 case 'z':
8431 for (octave_idx_type i = 0; i < n; i++)
8432 {
8433 graphics_object go = gh_mgr.get_object (kids(i));
8434
8435 if (go.is_zliminclude ())
8436 {
8437 octave_value lim = go.get_zlim ();
8438
8439 check_limit_vals (min_val, max_val, min_pos, max_neg, lim);
8440 }
8441 }
8442 break;
8443
8444 case 'c':
8445 for (octave_idx_type i = 0; i < n; i++)
8446 {
8447 graphics_object go = gh_mgr.get_object (kids(i));
8448
8449 if (go.is_climinclude ())
8450 {
8451 octave_value lim = go.get_clim ();
8452
8453 check_limit_vals (min_val, max_val, min_pos, max_neg, lim);
8454 }
8455 }
8456 break;
8457
8458 case 'a':
8459 for (octave_idx_type i = 0; i < n; i++)
8460 {
8461 graphics_object go = gh_mgr.get_object (kids(i));
8462
8463 if (go.is_aliminclude ())
8464 {
8465 octave_value lim = go.get_alim ();
8466
8467 check_limit_vals (min_val, max_val, min_pos, max_neg, lim);
8468 }
8469 }
8470 break;
8471
8472 default:
8473 break;
8474 }
8475}
8476
8477static std::set<double> updating_axis_limits;
8478
8479void
8480axes::update_axis_limits (const std::string& axis_type,
8481 const graphics_handle& h)
8482{
8483 if (updating_axis_limits.find (get_handle ().value ())
8484 != updating_axis_limits.end ())
8485 return;
8486
8487 Matrix kids = Matrix (1, 1, h.value ());
8488
8489 double min_val = octave::numeric_limits<double>::Inf ();
8490 double max_val = -octave::numeric_limits<double>::Inf ();
8491 double min_pos = octave::numeric_limits<double>::Inf ();
8492 double max_neg = -octave::numeric_limits<double>::Inf ();
8493
8494 char update_type = 0;
8495
8496 Matrix limits (1, 2);
8497 double val;
8498
8499#define FIX_LIMITS \
8500 val = limits(0); \
8501 if (octave::math::isfinite (val)) \
8502 min_val = val; \
8503 val = limits(1); \
8504 if (octave::math::isfinite (val)) \
8505 max_val = val;
8506
8507 if (axis_type == "xdata" || axis_type == "xscale"
8508 || axis_type == "xlimmode" || axis_type == "xliminclude"
8509 || axis_type == "xlim")
8510 {
8511 limits = m_properties.get_xlim ().matrix_value ();
8512 FIX_LIMITS;
8513
8514 update_type = 'x';
8515 if (m_properties.xlimmode_is ("auto"))
8516 {
8517 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'x');
8518
8519 limits = m_properties.get_axis_limits (min_val, max_val,
8520 min_pos, max_neg,
8521 m_properties.xscale_is ("log"));
8522 }
8523 else
8524 m_properties.check_axis_limits (limits, kids,
8525 m_properties.xscale_is ("log"),
8526 update_type);
8527 }
8528 else if (axis_type == "ydata" || axis_type == "yscale"
8529 || axis_type == "ylimmode" || axis_type == "yliminclude"
8530 || axis_type == "ylim")
8531 {
8532 limits = m_properties.get_ylim ().matrix_value ();
8533 FIX_LIMITS;
8534
8535 update_type = 'y';
8536 if (m_properties.ylimmode_is ("auto"))
8537 {
8538 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'y');
8539
8540 limits = m_properties.get_axis_limits (min_val, max_val,
8541 min_pos, max_neg,
8542 m_properties.yscale_is ("log"));
8543 }
8544 else
8545 m_properties.check_axis_limits (limits, kids,
8546 m_properties.yscale_is ("log"),
8547 update_type);
8548 }
8549 else if (axis_type == "zdata" || axis_type == "zscale"
8550 || axis_type == "zlimmode" || axis_type == "zliminclude"
8551 || axis_type == "zlim")
8552 {
8553 limits = m_properties.get_zlim ().matrix_value ();
8554 FIX_LIMITS;
8555
8556 update_type = 'z';
8557 if (m_properties.zlimmode_is ("auto"))
8558 {
8559 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'z');
8560
8561 m_properties.set_has3Dkids ((max_val - min_val) >
8562 std::numeric_limits<double>::epsilon ());
8563
8564 limits = m_properties.get_axis_limits (min_val, max_val,
8565 min_pos, max_neg,
8566 m_properties.zscale_is ("log"));
8567 }
8568 else
8569 {
8570 // FIXME: get_children_limits is only needed here in order to know
8571 // if there are 3D children. Is there a way to avoid this call?
8572 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'z');
8573
8574 m_properties.set_has3Dkids ((max_val - min_val) >
8575 std::numeric_limits<double>::epsilon ());
8576
8577 m_properties.check_axis_limits (limits, kids,
8578 m_properties.zscale_is ("log"),
8579 update_type);
8580 }
8581 }
8582 else if (axis_type == "cdata" || axis_type == "climmode"
8583 || axis_type == "cdatamapping" || axis_type == "climinclude"
8584 || axis_type == "clim")
8585 {
8586 if (m_properties.climmode_is ("auto"))
8587 {
8588 limits = m_properties.get_clim ().matrix_value ();
8589 FIX_LIMITS;
8590
8591 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'c');
8592
8593 if (min_val > max_val)
8594 {
8595 min_val = min_pos = 0;
8596 max_val = 1;
8597 }
8598 else if (min_val == max_val)
8599 {
8600 max_val = min_val + 1;
8601 min_val -= 1;
8602 }
8603
8604 limits(0) = min_val;
8605 limits(1) = max_val;
8606
8607 update_type = 'c';
8608 }
8609 }
8610 else if (axis_type == "alphadata" || axis_type == "alimmode"
8611 || axis_type == "alphadatamapping" || axis_type == "aliminclude"
8612 || axis_type == "alim")
8613 {
8614 if (m_properties.alimmode_is ("auto"))
8615 {
8616 limits = m_properties.get_alim ().matrix_value ();
8617 FIX_LIMITS;
8618
8619 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'a');
8620
8621 if (min_val > max_val)
8622 {
8623 min_val = min_pos = 0;
8624 max_val = 1;
8625 }
8626 else if (min_val == max_val)
8627 max_val = min_val + 1;
8628
8629 limits(0) = min_val;
8630 limits(1) = max_val;
8631
8632 update_type = 'a';
8633 }
8634 }
8635
8636#undef FIX_LIMITS
8637
8639 restore_var (updating_axis_limits);
8640
8641 updating_axis_limits.insert (get_handle ().value ());
8642 bool is_auto;
8643
8644 switch (update_type)
8645 {
8646 case 'x':
8647 is_auto = m_properties.xlimmode_is ("auto");
8648 m_properties.set_xlim (limits);
8649 if (is_auto)
8650 m_properties.set_xlimmode ("auto");
8651 m_properties.update_xlim ();
8652 break;
8653
8654 case 'y':
8655 is_auto = m_properties.ylimmode_is ("auto");
8656 m_properties.set_ylim (limits);
8657 if (is_auto)
8658 m_properties.set_ylimmode ("auto");
8659 m_properties.update_ylim ();
8660 break;
8661
8662 case 'z':
8663 is_auto = m_properties.zlimmode_is ("auto");
8664 m_properties.set_zlim (limits);
8665 if (is_auto)
8666 m_properties.set_zlimmode ("auto");
8667 m_properties.update_zlim ();
8668 break;
8669
8670 case 'c':
8671 m_properties.set_clim (limits);
8672 m_properties.set_climmode ("auto");
8673 break;
8674
8675 case 'a':
8676 m_properties.set_alim (limits);
8677 m_properties.set_alimmode ("auto");
8678 break;
8679
8680 default:
8681 break;
8682 }
8683
8684 m_properties.update_transform ();
8685}
8686
8687// FIXME: This function is called repeatedly while the axes are being set up.
8688// There is probably some way to make this more efficient.
8689
8690void
8691axes::update_axis_limits (const std::string& axis_type)
8692{
8693 if ((updating_axis_limits.find (get_handle ().value ())
8694 != updating_axis_limits.end ())
8695 || (updating_aspectratios.find (get_handle ().value ())
8696 != updating_aspectratios.end ()))
8697 return;
8698
8699 Matrix kids = m_properties.get_children ();
8700
8701 double min_val = octave::numeric_limits<double>::Inf ();
8702 double max_val = -octave::numeric_limits<double>::Inf ();
8703 double min_pos = octave::numeric_limits<double>::Inf ();
8704 double max_neg = -octave::numeric_limits<double>::Inf ();
8705
8706 char update_type = 0;
8707
8708 Matrix limits;
8709
8710 if (axis_type == "xdata" || axis_type == "xscale"
8711 || axis_type == "xlimmode" || axis_type == "xliminclude"
8712 || axis_type == "xlim")
8713 {
8714 update_type = 'x';
8715 if (m_properties.xlimmode_is ("auto"))
8716 {
8717 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'x');
8718
8719 limits = m_properties.get_axis_limits (min_val, max_val,
8720 min_pos, max_neg,
8721 m_properties.xscale_is ("log"));
8722 }
8723 else
8724 {
8725 limits = m_properties.get_xlim ().matrix_value ();
8726 m_properties.check_axis_limits (limits, kids,
8727 m_properties.xscale_is ("log"),
8728 update_type);
8729 if (axis_type == "xscale")
8730 update_type = 'x';
8731 }
8732 }
8733 else if (axis_type == "ydata" || axis_type == "yscale"
8734 || axis_type == "ylimmode" || axis_type == "yliminclude"
8735 || axis_type == "ylim")
8736 {
8737 update_type = 'y';
8738 if (m_properties.ylimmode_is ("auto"))
8739 {
8740 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'y');
8741
8742 limits = m_properties.get_axis_limits (min_val, max_val,
8743 min_pos, max_neg,
8744 m_properties.yscale_is ("log"));
8745 }
8746 else
8747 {
8748 limits = m_properties.get_ylim ().matrix_value ();
8749 m_properties.check_axis_limits (limits, kids,
8750 m_properties.yscale_is ("log"),
8751 update_type);
8752 if (axis_type == "yscale")
8753 update_type = 'y';
8754 }
8755 }
8756 else if (axis_type == "zdata" || axis_type == "zscale"
8757 || axis_type == "zlimmode" || axis_type == "zliminclude"
8758 || axis_type == "zlim")
8759 {
8760 update_type = 'z';
8761 if (m_properties.zlimmode_is ("auto"))
8762 {
8763 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'z');
8764
8765 m_properties.set_has3Dkids ((max_val - min_val) >
8766 std::numeric_limits<double>::epsilon ());
8767
8768 // FIXME: How to correctly handle (positive or negative) log scale?
8769 if ((! octave::math::isfinite (min_val)
8770 || ! octave::math::isfinite (max_val))
8771 && ! m_properties.zscale_is ("log"))
8772 min_val = max_val = 0.;
8773
8774 limits = m_properties.get_axis_limits (min_val, max_val,
8775 min_pos, max_neg,
8776 m_properties.zscale_is ("log"));
8777 }
8778 else
8779 {
8780 // FIXME: get_children_limits is only needed here in order to know
8781 // if there are 3D children. Is there a way to avoid this call?
8782 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'z');
8783
8784 m_properties.set_has3Dkids ((max_val - min_val) >
8785 std::numeric_limits<double>::epsilon ());
8786
8787 limits = m_properties.get_zlim ().matrix_value ();
8788 m_properties.check_axis_limits (limits, kids,
8789 m_properties.zscale_is ("log"),
8790 update_type);
8791 if (axis_type == "zscale")
8792 update_type = 'z';
8793 }
8794 }
8795 else if (axis_type == "cdata" || axis_type == "climmode"
8796 || axis_type == "cdatamapping" || axis_type == "climinclude"
8797 || axis_type == "clim")
8798 {
8799 if (m_properties.climmode_is ("auto"))
8800 {
8801 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'c');
8802
8803 if (min_val > max_val)
8804 {
8805 min_val = min_pos = 0;
8806 max_val = 1;
8807 }
8808 else if (min_val == max_val)
8809 {
8810 max_val = min_val + 1;
8811 min_val -= 1;
8812 }
8813
8814 limits.resize (1, 2);
8815
8816 limits(0) = min_val;
8817 limits(1) = max_val;
8818
8819 update_type = 'c';
8820 }
8821
8822 }
8823 else if (axis_type == "alphadata" || axis_type == "alimmode"
8824 || axis_type == "alphadatamapping" || axis_type == "aliminclude"
8825 || axis_type == "alim")
8826 {
8827 if (m_properties.alimmode_is ("auto"))
8828 {
8829 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'a');
8830
8831 if (min_val > max_val)
8832 {
8833 min_val = min_pos = 0;
8834 max_val = 1;
8835 }
8836 else if (min_val == max_val)
8837 max_val = min_val + 1;
8838
8839 limits.resize (1, 2);
8840
8841 limits(0) = min_val;
8842 limits(1) = max_val;
8843
8844 update_type = 'a';
8845 }
8846
8847 }
8848
8850 restore_var (updating_axis_limits);
8851
8852 updating_axis_limits.insert (get_handle ().value ());
8853 bool is_auto;
8854
8855 switch (update_type)
8856 {
8857 case 'x':
8858 is_auto = m_properties.xlimmode_is ("auto");
8859 m_properties.set_xlim (limits);
8860 if (is_auto)
8861 m_properties.set_xlimmode ("auto");
8862 m_properties.update_xlim ();
8863 break;
8864
8865 case 'y':
8866 is_auto = m_properties.ylimmode_is ("auto");
8867 m_properties.set_ylim (limits);
8868 if (is_auto)
8869 m_properties.set_ylimmode ("auto");
8870 m_properties.update_ylim ();
8871 break;
8872
8873 case 'z':
8874 is_auto = m_properties.zlimmode_is ("auto");
8875 m_properties.set_zlim (limits);
8876 if (is_auto)
8877 m_properties.set_zlimmode ("auto");
8878 m_properties.update_zlim ();
8879 break;
8880
8881 case 'c':
8882 m_properties.set_clim (limits);
8883 m_properties.set_climmode ("auto");
8884 break;
8885
8886 case 'a':
8887 m_properties.set_alim (limits);
8888 m_properties.set_alimmode ("auto");
8889 break;
8890
8891 default:
8892 break;
8893 }
8894
8895 m_properties.update_transform ();
8896}
8897
8898inline
8899double force_in_range (double x, double lower, double upper)
8900{
8901 if (x < lower)
8902 return lower;
8903 else if (x > upper)
8904 return upper;
8905 else
8906 return x;
8907}
8908
8909static Matrix
8910do_zoom (double val, double factor, const Matrix& lims, bool is_logscale)
8911{
8912 Matrix new_lims = lims;
8913
8914 double lo = lims(0);
8915 double hi = lims(1);
8916
8917 bool is_negative = lo < 0 && hi < 0;
8918
8919 if (is_logscale)
8920 {
8921 if (is_negative)
8922 {
8923 double tmp = hi;
8924 hi = std::log10 (-lo);
8925 lo = std::log10 (-tmp);
8926 val = std::log10 (-val);
8927 }
8928 else
8929 {
8930 hi = std::log10 (hi);
8931 lo = std::log10 (lo);
8932 val = std::log10 (val);
8933 }
8934 }
8935
8936 // Perform the zooming
8937 lo = val + (lo - val) / factor;
8938 hi = val + (hi - val) / factor;
8939
8940 if (is_logscale)
8941 {
8942 if (is_negative)
8943 {
8944 double tmp = -std::pow (10.0, hi);
8945 hi = -std::pow (10.0, lo);
8946 lo = tmp;
8947 }
8948 else
8949 {
8950 lo = std::pow (10.0, lo);
8951 hi = std::pow (10.0, hi);
8952 }
8953 }
8954
8955 new_lims(0) = lo;
8956 new_lims(1) = hi;
8957
8958 return new_lims;
8959}
8960
8961void
8962axes::properties::zoom_about_point (const std::string& mode,
8963 double x, double y, double factor,
8964 bool push_to_zoom_stack)
8965{
8966 // FIXME: Do we need error checking here?
8967 Matrix xlims = get_xlim ().matrix_value ();
8968 Matrix ylims = get_ylim ().matrix_value ();
8969
8970 // Get children axes limits
8971 Matrix kids = get_children ();
8972 double minx = octave::numeric_limits<double>::Inf ();
8973 double maxx = -octave::numeric_limits<double>::Inf ();
8974 double min_pos_x = octave::numeric_limits<double>::Inf ();
8975 double max_neg_x = -octave::numeric_limits<double>::Inf ();
8976 get_children_limits (minx, maxx, min_pos_x, max_neg_x, kids, 'x');
8977
8978 double miny = octave::numeric_limits<double>::Inf ();
8979 double maxy = -octave::numeric_limits<double>::Inf ();
8980 double min_pos_y = octave::numeric_limits<double>::Inf ();
8981 double max_neg_y = -octave::numeric_limits<double>::Inf ();
8982 get_children_limits (miny, maxy, min_pos_y, max_neg_y, kids, 'y');
8983
8984 xlims = do_zoom (x, factor, xlims, xscale_is ("log"));
8985 ylims = do_zoom (y, factor, ylims, yscale_is ("log"));
8986
8987 zoom (mode, xlims, ylims, push_to_zoom_stack);
8988}
8989
8990void
8991axes::properties::zoom (const std::string& mode, double factor,
8992 bool push_to_zoom_stack)
8993{
8994 // FIXME: Do we need error checking here?
8995 Matrix xlims = get_xlim ().matrix_value ();
8996 Matrix ylims = get_ylim ().matrix_value ();
8997
8998 double x = (xlims(0) + xlims(1)) / 2;
8999 double y = (ylims(0) + ylims(1)) / 2;
9000
9001 zoom_about_point (mode, x, y, factor, push_to_zoom_stack);
9002}
9003
9004void
9005axes::properties::push_zoom_stack (void)
9006{
9007 if (m_zoom_stack.empty ())
9008 {
9009 m_zoom_stack.push_front (m_xlimmode.get ());
9010 m_zoom_stack.push_front (m_xlim.get ());
9011 m_zoom_stack.push_front (m_ylimmode.get ());
9012 m_zoom_stack.push_front (m_ylim.get ());
9013 m_zoom_stack.push_front (m_zlimmode.get ());
9014 m_zoom_stack.push_front (m_zlim.get ());
9015 m_zoom_stack.push_front (m_view.get ());
9016 }
9017}
9018
9019void
9020axes::properties::zoom (const std::string& mode,
9021 const Matrix& xl, const Matrix& yl,
9022 bool push_to_zoom_stack)
9023{
9024 if (xl(0) == xl(1) || yl(0) == yl(1))
9025 {
9026 warning ("invalid zoom region");
9027 return;
9028 }
9029
9030 if (push_to_zoom_stack)
9031 push_zoom_stack ();
9032
9033 if (mode == "horizontal" || mode == "both")
9034 {
9035 m_xlim = xl;
9036 m_xlimmode = "manual";
9037 }
9038
9039 if (mode == "vertical" || mode == "both")
9040 {
9041 m_ylim = yl;
9042 m_ylimmode = "manual";
9043 }
9044
9045 update_transform ();
9046
9047 if (mode == "horizontal" || mode == "both")
9048 update_xlim ();
9049
9050 if (mode == "vertical" || mode == "both")
9051 update_ylim ();
9052}
9053
9054static Matrix
9055do_translate (double x0, double x1, const Matrix& lims, bool is_logscale)
9056{
9057 Matrix new_lims = lims;
9058
9059 double lo = lims(0);
9060 double hi = lims(1);
9061
9062 bool is_negative = lo < 0 && hi < 0;
9063
9064 double delta;
9065
9066 if (is_logscale)
9067 {
9068 if (is_negative)
9069 {
9070 double tmp = hi;
9071 hi = std::log10 (-lo);
9072 lo = std::log10 (-tmp);
9073 x0 = -x0;
9074 x1 = -x1;
9075 }
9076 else
9077 {
9078 hi = std::log10 (hi);
9079 lo = std::log10 (lo);
9080 }
9081
9082 delta = std::log10 (x0) - std::log10 (x1);
9083 }
9084 else
9085 {
9086 delta = x0 - x1;
9087 }
9088
9089 // Perform the translation
9090 lo += delta;
9091 hi += delta;
9092
9093 if (is_logscale)
9094 {
9095 if (is_negative)
9096 {
9097 double tmp = -std::pow (10.0, hi);
9098 hi = -std::pow (10.0, lo);
9099 lo = tmp;
9100 }
9101 else
9102 {
9103 lo = std::pow (10.0, lo);
9104 hi = std::pow (10.0, hi);
9105 }
9106 }
9107
9108 new_lims(0) = lo;
9109 new_lims(1) = hi;
9110
9111 return new_lims;
9112}
9113
9114void
9115axes::properties::translate_view (const std::string& mode,
9116 double x0, double x1, double y0, double y1,
9117 bool push_to_zoom_stack)
9118{
9119 // FIXME: Do we need error checking here?
9120 Matrix xlims = get_xlim ().matrix_value ();
9121 Matrix ylims = get_ylim ().matrix_value ();
9122
9123 // Get children axes limits
9124 Matrix kids = get_children ();
9125 double minx = octave::numeric_limits<double>::Inf ();
9126 double maxx = -octave::numeric_limits<double>::Inf ();
9127 double min_pos_x = octave::numeric_limits<double>::Inf ();
9128 double max_neg_x = -octave::numeric_limits<double>::Inf ();
9129 get_children_limits (minx, maxx, min_pos_x, max_neg_x, kids, 'x');
9130
9131 double miny = octave::numeric_limits<double>::Inf ();
9132 double maxy = -octave::numeric_limits<double>::Inf ();
9133 double min_pos_y = octave::numeric_limits<double>::Inf ();
9134 double max_neg_y = -octave::numeric_limits<double>::Inf ();
9135 get_children_limits (miny, maxy, min_pos_y, max_neg_y, kids, 'y');
9136
9137 xlims = do_translate (x0, x1, xlims, xscale_is ("log"));
9138 ylims = do_translate (y0, y1, ylims, yscale_is ("log"));
9139
9140 zoom (mode, xlims, ylims, push_to_zoom_stack);
9141}
9142
9143void
9144axes::properties::pan (const std::string& mode, double factor,
9145 bool push_to_zoom_stack)
9146{
9147 // FIXME: Do we need error checking here?
9148 Matrix xlims = get_xlim ().matrix_value ();
9149 Matrix ylims = get_ylim ().matrix_value ();
9150
9151 double x0 = (xlims(0) + xlims(1)) / 2;
9152 double y0 = (ylims(0) + ylims(1)) / 2;
9153
9154 double x1 = x0 + (xlims(1) - xlims(0)) * factor;
9155 double y1 = y0 + (ylims(1) - ylims(0)) * factor;
9156
9157 translate_view (mode, x0, x1, y0, y1, push_to_zoom_stack);
9158}
9159
9160void
9161axes::properties::rotate3d (double x0, double x1, double y0, double y1,
9162 bool push_to_zoom_stack)
9163{
9164 if (push_to_zoom_stack)
9165 push_zoom_stack ();
9166
9167 Matrix bb = get_boundingbox (true);
9168 Matrix new_view = get_view ().matrix_value ();
9169
9170 // Compute new view angles
9171 new_view(0) += ((x0 - x1) * (180.0 / bb(2)));
9172 new_view(1) += ((y1 - y0) * (180.0 / bb(3)));
9173
9174 // Clipping
9175 new_view(1) = std::min (new_view(1), 90.0);
9176 new_view(1) = std::max (new_view(1), -90.0);
9177 if (new_view(0) > 180.0)
9178 new_view(0) -= 360.0;
9179 else if (new_view(0) < -180.0)
9180 new_view(0) += 360.0;
9181
9182 // Snapping
9183 double snapmargin = 1.0;
9184 for (int a = -90; a <= 90; a += 90)
9185 {
9186 if ((a - snapmargin) < new_view(1) && new_view(1) < (a + snapmargin))
9187 {
9188 new_view(1) = a;
9189 break;
9190 }
9191 }
9192
9193 for (int a = -180; a <= 180; a += 180)
9194 if ((a - snapmargin) < new_view(0) && new_view(0) < (a + snapmargin))
9195 {
9196 if (a == 180)
9197 new_view(0) = -180;
9198 else
9199 new_view(0) = a;
9200 break;
9201 }
9202
9203 // Update axes properties
9204 set_view (new_view);
9205}
9206
9207void
9208axes::properties::rotate_view (double delta_el, double delta_az,
9209 bool push_to_zoom_stack)
9210{
9211 if (push_to_zoom_stack)
9212 push_zoom_stack ();
9213
9214 Matrix v = get_view ().matrix_value ();
9215
9216 v(1) += delta_el;
9217
9218 if (v(1) > 90)
9219 v(1) = 90;
9220 if (v(1) < -90)
9221 v(1) = -90;
9222
9223 v(0) = fmod (v(0) - delta_az + 720, 360);
9224
9225 set_view (v);
9226
9227 update_transform ();
9228}
9229
9230void
9231axes::properties::unzoom (void)
9232{
9233 if (m_zoom_stack.size () >= 7)
9234 {
9235 m_view = m_zoom_stack.front ();
9236 m_zoom_stack.pop_front ();
9237
9238 m_zlim = m_zoom_stack.front ();
9239 m_zoom_stack.pop_front ();
9240
9241 m_zlimmode = m_zoom_stack.front ();
9242 m_zoom_stack.pop_front ();
9243
9244 m_ylim = m_zoom_stack.front ();
9245 m_zoom_stack.pop_front ();
9246
9247 m_ylimmode = m_zoom_stack.front ();
9248 m_zoom_stack.pop_front ();
9249
9250 m_xlim = m_zoom_stack.front ();
9251 m_zoom_stack.pop_front ();
9252
9253 m_xlimmode = m_zoom_stack.front ();
9254 m_zoom_stack.pop_front ();
9255
9256 update_transform ();
9257
9258 update_xlim ();
9259 update_ylim ();
9260 update_zlim ();
9261
9262 update_view ();
9263 }
9264}
9265
9266void
9267axes::properties::update_handlevisibility (void)
9268{
9269 if (! is_handle_visible ())
9270 {
9271 gh_manager& gh_mgr
9272 = octave::__get_gh_manager__ ("axes::properties::update_handlevisibility");
9273
9274 graphics_object go (gh_mgr.get_object (get___myhandle__ ()));
9275
9276 graphics_object fig (go.get_ancestor ("figure"));
9277 octave_value ca = fig.get ("currentaxes");
9278 if (! ca.isempty () && ca.double_value () == m___myhandle__)
9279 {
9280 octave::autolock guard (gh_mgr.graphics_lock ());
9281
9282 octave_value kids = fig.get ("children");
9283 if (kids.isempty ())
9284 fig.set ("currentaxes", Matrix ());
9285 else
9286 {
9287 NDArray kidsarray = kids.array_value ();
9288 fig.set ("currentaxes", kidsarray(0));
9289 }
9290 }
9291 }
9292
9293 base_properties::update_handlevisibility ();
9294}
9295
9296void
9297figure::properties::init_toolkit (void)
9298{
9299 octave::gtk_manager& gtk_mgr
9300 = octave::__get_gtk_manager__ ("figure::properties::init_toolkit");
9301
9302 m_toolkit = gtk_mgr.get_toolkit ();
9303}
9304
9305void
9306axes::properties::clear_zoom_stack (bool do_unzoom)
9307{
9308 std::size_t items_to_leave_on_stack = (do_unzoom ? 7 : 0);
9309
9310 while (m_zoom_stack.size () > items_to_leave_on_stack)
9311 m_zoom_stack.pop_front ();
9312
9313 if (do_unzoom)
9314 unzoom ();
9315}
9316
9317void
9318axes::properties::trigger_normals_calc (void)
9319{
9320 // Find all patch (and surface) objects within axes
9321 std::list<graphics_object> children_list;
9322 std::list<graphics_object>::iterator children_list_iter;
9323 get_children_of_type ("patch", false, true, children_list);
9324 get_children_of_type ("surface", false, true, children_list);
9325
9326 // trigger normals calculation for these objects
9327 for (children_list_iter = children_list.begin ();
9328 children_list_iter != children_list.end (); children_list_iter++)
9329 {
9330 graphics_object kid = *children_list_iter;
9331 if (kid.isa ("patch"))
9332 {
9333 patch::properties& patch_props
9334 = dynamic_cast<patch::properties&> (kid.get_properties ());
9335 patch_props.update_normals (false);
9336 }
9337 else
9338 {
9339 surface::properties& surface_props
9340 = dynamic_cast<surface::properties&> (kid.get_properties ());
9341 surface_props.update_normals (false);
9342 }
9343 }
9344}
9345
9346void
9347axes::reset_default_properties (void)
9348{
9349 // empty list of local defaults
9350 m_default_properties = property_list ();
9351
9352 // Save warning state of "Octave:deprecated-property"
9353 int state = toggle_warn ("Octave:deprecated-property", false);
9354
9355 // reset factory defaults
9356 remove_all_listeners ();
9357 set_defaults ("reset");
9358
9359 toggle_warn ("Octave:deprecated-property", true, state);
9360}
9361
9362void
9363axes::initialize (const graphics_object& go)
9364{
9366
9367 xinitialize (m_properties.get_title ());
9368 xinitialize (m_properties.get_xlabel ());
9369 xinitialize (m_properties.get_ylabel ());
9370 xinitialize (m_properties.get_zlabel ());
9371
9372 m_properties.sync_positions ();
9373}
9374
9375// ---------------------------------------------------------------------
9376
9377Matrix
9378line::properties::compute_xlim (void) const
9379{
9380 Matrix m (1, 4);
9381
9382 m(0) = m_xdata.min_val ();
9383 m(1) = m_xdata.max_val ();
9384 m(2) = m_xdata.min_pos ();
9385 m(3) = m_xdata.max_neg ();
9386
9387 return m;
9388}
9389
9390Matrix
9391line::properties::compute_ylim (void) const
9392{
9393 Matrix m (1, 4);
9394
9395 m(0) = m_ydata.min_val ();
9396 m(1) = m_ydata.max_val ();
9397 m(2) = m_ydata.min_pos ();
9398 m(3) = m_ydata.max_neg ();
9399
9400 return m;
9401}
9402
9403// ---------------------------------------------------------------------
9404
9405Matrix
9406text::properties::get_data_position (void) const
9407{
9408 Matrix pos = get_position ().matrix_value ();
9409
9410 if (! units_is ("data"))
9411 pos = convert_text_position (pos, *this, get_units (), "data");
9412
9413 return pos;
9414}
9415
9416Matrix
9417text::properties::get_extent_matrix (bool rotated) const
9418{
9419 // FIXME: Should this function also add the (x,y) base position?
9420 Matrix ext = m_extent.get ().matrix_value ();
9421
9422 if (rotated && get_rotation () != 0)
9423 {
9424 double rot = get_rotation () * 4.0 * atan (1.0) / 180;
9425 double x0 = ext(0) * cos (rot) - ext(1) * sin (rot);
9426 double x1 = x0;
9427 double y0 = ext(0) * sin (rot) + ext(1) * cos (rot);
9428 double y1 = y0;
9429
9430 double tmp = (ext(0)+ext(2)) * cos (rot) - ext(1) * sin (rot);
9431 x0 = std::min (x0, tmp);
9432 x1 = std::max (x1, tmp);
9433 tmp = (ext(0)+ext(2)) * sin (rot) + ext(1) * cos (rot);
9434 y0 = std::min (y0, tmp);
9435 y1 = std::max (y1, tmp);
9436
9437 tmp = (ext(0)+ext(2)) * cos (rot) - (ext(1)+ext(3)) * sin (rot);
9438 x0 = std::min (x0, tmp);
9439 x1 = std::max (x1, tmp);
9440 tmp = (ext(0)+ext(2)) * sin (rot) + (ext(1)+ext(3)) * cos (rot);
9441 y0 = std::min (y0, tmp);
9442 y1 = std::max (y1, tmp);
9443
9444 tmp = ext(0) * cos (rot) - (ext(1)+ext(3)) * sin (rot);
9445 x0 = std::min (x0, tmp);
9446 x1 = std::max (x1, tmp);
9447 tmp = ext(0) * sin (rot) + (ext(1)+ext(3)) * cos (rot);
9448 y0 = std::min (y0, tmp);
9449 y1 = std::max (y1, tmp);
9450
9451 ext(0) = x0;
9452 ext(1) = y0;
9453 ext(2) = x1 - x0;
9454 ext(3) = y1 - y0;
9455 }
9456
9457 return ext;
9458}
9459
9461text::properties::get_extent (void) const
9462{
9463 // FIXME: This doesn't work right for 3D plots.
9464 // (It doesn't in Matlab either, at least not in version 6.5.)
9465 Matrix m = get_extent_matrix (true);
9466 Matrix pos = get_position ().matrix_value ();
9467 Matrix p = convert_text_position (pos, *this, get_units (), "pixels");
9468
9469 m(0) += p(0);
9470 m(1) += p(1);
9471
9472 Matrix bbox = convert_text_position (m, *this, "pixels", get_units ());
9473
9474 double dpr = device_pixel_ratio (get___myhandle__ ());
9475
9476 for (octave_idx_type ii = 0; ii < bbox.numel (); ii++)
9477 bbox(ii) = bbox(ii) / dpr;
9478
9479 return bbox;
9480}
9481
9482void
9483text::properties::set_fontunits (const octave_value& val)
9484{
9485 caseless_str old_fontunits = get_fontunits ();
9486
9487 if (m_fontunits.set (val, true))
9488 {
9489 update_fontunits (old_fontunits);
9490 mark_modified ();
9491 }
9492}
9493
9494void
9495text::properties::update_fontunits (const caseless_str& old_units)
9496{
9497 caseless_str new_units = get_fontunits ();
9498 double parent_height = 0;
9499 double fontsz = get_fontsize ();
9500
9501 if (new_units == "normalized" || old_units == "normalized")
9502 {
9503 gh_manager& gh_mgr
9504 = octave::__get_gh_manager__ ("text::properties::update_fontunits");
9505
9506 graphics_object go (gh_mgr.get_object (get___myhandle__ ()));
9507
9508 graphics_object ax (go.get_ancestor ("axes"));
9509
9510 parent_height = ax.get_properties ().get_boundingbox (true).elem (3);
9511 }
9512
9513 fontsz = convert_font_size (fontsz, old_units, new_units, parent_height);
9514
9515 set_fontsize (octave_value (fontsz));
9516}
9517
9518void
9519text::properties::update_font (void)
9520{
9521 double dpr = device_pixel_ratio (get___myhandle__ ());
9522
9523 gh_manager& gh_mgr
9524 = octave::__get_gh_manager__ ("text::properties::update_font");
9525
9526 octave::autolock guard (gh_mgr.graphics_lock ());
9527
9528 m_txt_renderer.set_font (get ("fontname").string_value (),
9529 get ("fontweight").string_value (),
9530 get ("fontangle").string_value (),
9531 get ("__fontsize_points__").double_value () * dpr);
9532
9533 m_txt_renderer.set_anti_aliasing (is_fontsmoothing ());
9534
9535 Matrix c = get_color_rgb ();
9536 if (! c.isempty ())
9537 m_txt_renderer.set_color (c);
9538
9539}
9540
9541void
9542text::properties::update_text_extent (void)
9543{
9544 int halign = 0;
9545 int valign = 0;
9546
9547 if (horizontalalignment_is ("center"))
9548 halign = 1;
9549 else if (horizontalalignment_is ("right"))
9550 halign = 2;
9551
9552 if (verticalalignment_is ("middle"))
9553 valign = 1;
9554 else if (verticalalignment_is ("top"))
9555 valign = 2;
9556 else if (verticalalignment_is ("baseline"))
9557 valign = 3;
9558 else if (verticalalignment_is ("cap"))
9559 valign = 4;
9560
9561 Matrix bbox;
9562
9563 // FIXME: string should be parsed only when modified, for efficiency
9564
9565 octave_value string_prop = get_string ();
9566
9567 string_vector sv = string_prop.string_vector_value ();
9568
9569 gh_manager& gh_mgr
9570 = octave::__get_gh_manager__ ("text::properties::update_text_extent");
9571
9572 octave::autolock guard (gh_mgr.graphics_lock ());
9573
9574 m_txt_renderer.text_to_pixels (sv.join ("\n"), m_pixels, bbox,
9575 halign, valign, 0.0, get_interpreter ());
9576 // The bbox is relative to the text's position. We'll leave it that
9577 // way, because get_position does not return valid results when the
9578 // text is first constructed. Conversion to proper coordinates is
9579 // performed in get_extent.
9580 set_extent (bbox);
9581
9582 if (__autopos_tag___is ("xlabel") || __autopos_tag___is ("ylabel")
9583 || __autopos_tag___is ("zlabel") || __autopos_tag___is ("title"))
9584 update_autopos ("sync");
9585}
9586
9587void
9588text::properties::request_autopos (void)
9589{
9590 if (__autopos_tag___is ("xlabel") || __autopos_tag___is ("ylabel")
9591 || __autopos_tag___is ("zlabel") || __autopos_tag___is ("title"))
9592 update_autopos (get___autopos_tag__ ());
9593}
9594
9595void
9596text::properties::update_units (void)
9597{
9598 if (! units_is ("data"))
9599 {
9600 set_xliminclude ("off");
9601 set_yliminclude ("off");
9602 set_zliminclude ("off");
9603 }
9604
9605 Matrix pos = get_position ().matrix_value ();
9606
9607 pos = convert_text_position (pos, *this, m_cached_units, get_units ());
9608
9609 // FIXME: if the current axes view is 2D, then one should probably drop
9610 // the z-component of "pos" and leave "zliminclude" to "off".
9611
9612 bool autopos = positionmode_is ("auto");
9613
9614 set_position (pos);
9615
9616 if (autopos)
9617 set_positionmode ("auto");
9618
9619 if (units_is ("data"))
9620 {
9621 set_xliminclude ("on");
9622 set_yliminclude ("on");
9623 // FIXME: see above
9624 set_zliminclude ("off");
9625 }
9626
9627 m_cached_units = get_units ();
9628}
9629
9630double
9631text::properties::get___fontsize_points__ (double box_pix_height) const
9632{
9633 double fontsz = get_fontsize ();
9634 double parent_height = box_pix_height;
9635
9636 gh_manager& gh_mgr
9637 = octave::__get_gh_manager__ ("text::properties::get___fontsize_points__");
9638
9639 graphics_object go (gh_mgr.get_object (get___myhandle__ ()));
9640
9641 if (fontunits_is ("normalized") && parent_height <= 0)
9642 {
9643 graphics_object ax (go.get_ancestor ("axes"));
9644
9645 parent_height = ax.get_properties ().get_boundingbox (true).elem (3);
9646 }
9647
9648 return convert_font_size (fontsz, get_fontunits (), "points", parent_height);
9649}
9650
9651// ---------------------------------------------------------------------
9652
9654image::properties::get_color_data (void) const
9655{
9656 return convert_cdata (*this, get_cdata (), cdatamapping_is ("scaled"), 3);
9657}
9658
9659// ---------------------------------------------------------------------
9660
9661void
9662light::initialize (const graphics_object& go)
9663{
9665
9666 // trigger normals calculation for the respective children of this axes object
9667 axes::properties& parent_axes_prop
9668 = dynamic_cast<axes::properties&> (go.get_ancestor ("axes").get_properties ());
9669 parent_axes_prop.trigger_normals_calc ();
9670}
9671
9672void
9673light::properties::update_visible (void)
9674{
9675 gh_manager& gh_mgr
9676 = octave::__get_gh_manager__ ("light::properties::update_visible");
9677
9678 graphics_object go = gh_mgr.get_object (get___myhandle__ ());
9679
9680 axes::properties& ax_props = dynamic_cast<axes::properties&>
9681 (go.get_ancestor ("axes").get_properties ());
9682 if (is_visible ())
9683 ax_props.increase_num_lights ();
9684 else
9685 ax_props.decrease_num_lights ();
9686}
9687
9688// ---------------------------------------------------------------------
9689
9690bool
9691patch::properties::get_do_lighting (void) const
9692{
9693 gh_manager& gh_mgr
9694 = octave::__get_gh_manager__ ("patch::properties::get_do_lighting");
9695
9696 graphics_object go = gh_mgr.get_object (get___myhandle__ ());
9697
9698 axes::properties& ax_props = dynamic_cast<axes::properties&>
9699 (go.get_ancestor ("axes").get_properties ());
9700
9701 return (ax_props.get_num_lights () > 0);
9702}
9703
9705patch::properties::get_color_data (void) const
9706{
9707 octave_value fvc = get_facevertexcdata ();
9708 if (fvc.is_undefined () || fvc.isempty ())
9709 return Matrix ();
9710 else
9711 return convert_cdata (*this, fvc, cdatamapping_is ("scaled"), 2);
9712}
9713
9714static bool updating_patch_data = false;
9715
9716void
9717patch::properties::update_fvc (void)
9718{
9720 return;
9721
9722 Matrix xd = get_xdata ().matrix_value ();
9723 Matrix yd = get_ydata ().matrix_value ();
9724 Matrix zd = get_zdata ().matrix_value ();
9725 NDArray cd = get_cdata ().array_value ();
9726
9727 m_bad_data_msg = "";
9728 if (xd.dims () != yd.dims ()
9729 || (xd.dims () != zd.dims () && ! zd.isempty ()))
9730 {
9731 m_bad_data_msg = "x/y/zdata must have the same dimensions";
9732 return;
9733 }
9734
9735 // Faces and Vertices
9736 dim_vector dv;
9737 bool is3D = false;
9738 octave_idx_type nr = xd.rows ();
9739 octave_idx_type nc = xd.columns ();
9740 if (nr == 1 && nc > 1)
9741 {
9742 nr = nc;
9743 nc = 1;
9744 xd = xd.as_column ();
9745 yd = yd.as_column ();
9746 zd = zd.as_column ();
9747 }
9748
9749 dv(0) = nr * nc;
9750 if (zd.isempty ())
9751 dv(1) = 2;
9752 else
9753 {
9754 dv(1) = 3;
9755 is3D = true;
9756 }
9757
9758 Matrix vert (dv);
9759 Matrix idx (nc, nr);
9760
9761 octave_idx_type kk = 0;
9762 for (octave_idx_type jj = 0; jj < nc; jj++)
9763 {
9764 for (octave_idx_type ii = 0; ii < nr; ii++)
9765 {
9766 vert(kk, 0) = xd(ii, jj);
9767 vert(kk, 1) = yd(ii, jj);
9768 if (is3D)
9769 vert(kk, 2) = zd(ii, jj);
9770
9771 idx(jj, ii) = static_cast<double> (kk+1);
9772
9773 kk++;
9774 }
9775 }
9776
9777 // facevertexcdata
9778 Matrix fvc;
9779 if (cd.ndims () == 3)
9780 {
9781 dv(0) = cd.rows () * cd.columns ();
9782 dv(1) = cd.dims ()(2);
9783 fvc = cd.reshape (dv);
9784 }
9785 else
9786 fvc = cd.as_column ();
9787
9788 // FIXME: shouldn't we update facevertexalphadata here ?
9789
9791
9792 m_faces.set (idx);
9793 m_vertices.set (vert);
9794 m_facevertexcdata.set (fvc);
9795}
9796
9797// core coplanar tester
9798bool is_coplanar (const Matrix& cov)
9799{
9800 // Accuracy note: this test will also accept single precision input (although
9801 // stored in double precision). This is because the error threshold is
9802 // sqrt(tol) = 1.5e-7.
9803 double tol = 100 * std::numeric_limits<double>::epsilon ();
9804 EIG eig (cov, false, false, true);
9805 ColumnVector ev = real (eig.eigenvalues ());
9806 return ev.min () <= tol * ev.max ();
9807}
9808
9809std::vector<octave_idx_type>
9810coplanar_partition (const Matrix& vert, const Matrix& idx,
9812{
9813 std::vector<octave_idx_type> coplanar_ends;
9814
9815 Matrix plane_pivot = Matrix (1, 3, 0.0);
9816 for (octave_idx_type i = 0; i < 3; i++)
9817 plane_pivot(0, i) = vert(idx(0, jj)-1, i);
9818
9819 Matrix fc = Matrix (0, 3, 0.0); // face corner vertex coordinates
9820 Matrix fa = Matrix (1, 3, 0.0); // for append face corner
9821 Matrix coor_cov = Matrix (3, 3, 0.0);
9822
9823 if (nc >= 5)
9824 {
9825 // Coplanar test that involves all points.
9826 // For nc == 4, this initial test is not beneficial at all.
9827 // If the probability of coplanar input is more than half, for
9828 // the best average performance, we should use nc >= 5.
9829 // Higher threshold is meaningful only when input is known to be
9830 // non-coplanar and nc is small.
9831
9832 fc.resize (nc - 1, 3);
9833 for (octave_idx_type j = 1; j < nc; j++)
9834 for (octave_idx_type i = 0; i < 3; i++)
9835 fc(j-1, i) = vert(idx(j, jj)-1, i) - plane_pivot(i);
9836
9837 coor_cov = fc.transpose () * fc;
9838 if (is_coplanar (coor_cov))
9839 {
9840 coplanar_ends.push_back (nc - 1);
9841 return coplanar_ends;
9842 }
9843 }
9844
9845 fc.resize (3, 3);
9846 octave_idx_type i_start = 1;
9847 octave_idx_type i_end = 2;
9848
9849 // Split the polygon into coplanar segments.
9850 // The first point is common corner of all planes.
9851 while (i_start < nc - 1)
9852 {
9853 i_end = i_start + 2;
9854 if (i_end > nc - 1)
9855 {
9856 coplanar_ends.push_back (nc - 1);
9857 break;
9858 }
9859
9860 // Algorithm: Start from 3 points, keep adding points until the point set
9861 // is no more in a plane. Record the coplanar point set, then advance
9862 // i_start.
9863
9864 // Prepare 1+3 points for coplanar test.
9865 // The first point is implicitly included.
9866 for (octave_idx_type j = 0; j < 3; j++)
9867 for (octave_idx_type i = 0; i < 3; i++)
9868 fc(j, i) = vert(idx(j+i_start, jj)-1, i) - plane_pivot(i);
9869
9870 // covariance matrix between coordinates of vertices
9871 coor_cov = fc.transpose () * fc;
9872
9873 while (true)
9874 {
9875 // coplanar test
9876 if (! is_coplanar (coor_cov))
9877 break;
9878
9879 i_end++;
9880 if (i_end > nc - 1)
9881 break;
9882
9883 // add a point to plane
9884 for (octave_idx_type i = 0; i < 3; i++)
9885 fa(0, i) = vert(idx(i_end, jj)-1, i) - plane_pivot(i);
9886 coor_cov += fa.transpose () * fa;
9887 }
9888
9889 i_start = i_end - 1;
9890 coplanar_ends.push_back (i_start);
9891 }
9892 return coplanar_ends;
9893}
9894
9895void
9896patch::properties::update_data (void)
9897{
9899 return;
9900
9901 Matrix idx = get_faces ().matrix_value ().transpose ();
9902 Matrix vert = get_vertices ().matrix_value ();
9903 NDArray fvc = get_facevertexcdata ().array_value ();
9904
9905 octave_idx_type nfaces = idx.columns ();
9906 octave_idx_type nvert = vert.rows ();
9907
9908 // Check all vertices in faces are defined
9909 m_bad_data_msg = "";
9910 if (static_cast<double> (nvert) < idx.row_max ().max ())
9911 {
9912 m_bad_data_msg = R"(some vertices in "faces" property are undefined)";
9913 return;
9914 }
9915
9916 // Replace NaNs
9917 if (idx.any_element_is_inf_or_nan ())
9918 {
9919 for (octave_idx_type jj = 0; jj < idx.columns (); jj++)
9920 {
9921 double valid_vert = idx(0, jj);
9922 bool turn_valid = false;
9923 for (octave_idx_type ii = 0; ii < idx.rows (); ii++)
9924 {
9925 if (octave::math::isnan (idx(ii, jj)) || turn_valid)
9926 {
9927 idx(ii, jj) = valid_vert;
9928 turn_valid = true;
9929 }
9930 else
9931 valid_vert = idx(ii, jj);
9932 }
9933 }
9934 }
9935
9936 // check coplanarity for 3D-faces with more than 3 corners
9937 int fcmax = idx.rows ();
9938 if (fcmax > 3 && vert.columns () > 2
9939 && ! (facecolor_is ("none") && edgecolor_is ("none")))
9940 {
9941 m_coplanar_last_idx.resize (idx.columns ());
9942 for (octave_idx_type jj = 0; jj < idx.columns (); jj++)
9943 {
9944 if (octave::math::isnan (idx(3, jj)))
9945 continue;
9946
9947 // find first element that is NaN to get number of corners
9948 octave_idx_type nc = 3;
9949 while (nc < fcmax && ! octave::math::isnan (idx(nc, jj)))
9950 nc++;
9951
9952 // If any of the corners is NaN or Inf, skip coplanar test.
9953 // FIXME: Add support for non-coplanar faces with unclosed contour.
9954 bool is_unclosed = false;
9955 for (octave_idx_type j = 0; j < nc; j++)
9956 {
9957 const octave_idx_type k = idx(j, jj) - 1;
9958 if (! (octave::math::isfinite (vert(k, 0))
9959 && octave::math::isfinite (vert(k, 1))
9960 && octave::math::isfinite (vert(k, 2))))
9961 {
9962 is_unclosed = true;
9963 break;
9964 }
9965 }
9966 if (is_unclosed)
9967 continue;
9968
9969 m_coplanar_last_idx[jj] = coplanar_partition (vert, idx, nc, jj);
9970 }
9971 }
9972 else
9973 m_coplanar_last_idx.resize (0);
9974
9975 // Build cdata
9977 NDArray cd;
9978 bool pervertex = false;
9979
9980 if (fvc.rows () == nfaces || fvc.rows () == 1)
9981 {
9982 dv(0) = 1;
9983 dv(1) = fvc.rows ();
9984 dv(2) = fvc.columns ();
9985 cd = fvc.reshape (dv);
9986 }
9987 else
9988 {
9989 if (! fvc.isempty ())
9990 {
9991 dv(0) = idx.rows ();
9992 dv(1) = nfaces;
9993 dv(2) = fvc.columns ();
9994 cd.resize (dv);
9995 pervertex = true;
9996 }
9997 }
9998
9999 // Build x,y,zdata and eventually per vertex cdata
10000 Matrix xd (idx.dims ());
10001 Matrix yd (idx.dims ());
10002 Matrix zd;
10003 bool has_zd = false;
10004 if (vert.columns () > 2)
10005 {
10006 zd = Matrix (idx.dims ());
10007 has_zd = true;
10008 }
10009
10010 for (octave_idx_type jj = 0; jj < nfaces; jj++)
10011 {
10012 for (octave_idx_type ii = 0; ii < idx.rows (); ii++)
10013 {
10014 octave_idx_type row = static_cast<octave_idx_type> (idx(ii, jj)-1);
10015 xd(ii, jj) = vert(row, 0);
10016 yd(ii, jj) = vert(row, 1);
10017
10018 if (has_zd)
10019 zd(ii, jj) = vert(row, 2);
10020
10021 if (pervertex)
10022 for (int kk = 0; kk < fvc.columns (); kk++)
10023 cd(ii, jj, kk) = fvc(row, kk);
10024 }
10025 }
10026
10027 // Update normals
10028 update_normals (true);
10029
10031
10032 set_xdata (xd);
10033 set_ydata (yd);
10034 set_zdata (zd);
10035 set_cdata (cd);
10036}
10037
10038inline void
10039cross_product (double x1, double y1, double z1,
10040 double x2, double y2, double z2,
10041 double& x, double& y, double& z)
10042{
10043 x += (y1 * z2 - z1 * y2);
10044 y += (z1 * x2 - x1 * z2);
10045 z += (x1 * y2 - y1 * x2);
10046}
10047
10048void
10049patch::properties::calc_face_normals (Matrix& fn)
10050{
10051 Matrix v = get_vertices ().matrix_value ();
10052 Matrix f = get_faces ().matrix_value ();
10053
10054 bool is_3D = (v.columns () == 3); // 2D or 3D patches
10055 octave_idx_type num_f = f.rows (); // number of faces
10056 octave_idx_type max_nc = f.columns (); // max. number of polygon corners
10057
10058 // In which cases can we skip updating the normals?
10059 if (max_nc < 3)
10060 {
10061 fn = Matrix ();
10062 return;
10063 }
10064
10065 // Calculate normals for all faces
10066 octave_idx_type i1, i2, i3;
10067 octave_idx_type j1, j2;
10068 for (octave_idx_type i = 0; i < num_f; i++)
10069 {
10070 bool is_coplanar = true;
10071 if (m_coplanar_last_idx.size () > 0 && m_coplanar_last_idx[i].size () > 1)
10072 is_coplanar = false;
10073
10074 // get number of corners
10075 octave_idx_type nc = 3;
10076 if (max_nc > 3)
10077 {
10078 while (nc < max_nc && ! octave::math::isnan (f(i, nc)))
10079 nc++;
10080 }
10081
10082 RowVector fnc (3, 0.0);
10083 double& nx = fnc(0);
10084 double& ny = fnc(1);
10085 double& nz = fnc(2);
10086
10087 if (is_coplanar)
10088 {
10089 // fast way for coplanar polygons
10090 i1 = f(i, 0) - 1; i2 = f(i, 1) - 1; i3 = f(i, nc-1) - 1;
10091
10092 if (is_3D)
10094 (v(i3, 0) - v(i1, 0), v(i3, 1) - v(i1, 1), v(i3, 2) - v(i1, 2),
10095 v(i2, 0) - v(i1, 0), v(i2, 1) - v(i1, 1), v(i2, 2) - v(i1, 2),
10096 nx, ny, nz);
10097 else
10098 {
10099 nz = (v(i2, 0) - v(i1, 0)) * (v(i3, 1) - v(i1, 1)) -
10100 (v(i2, 1) - v(i1, 1)) * (v(i3, 0) - v(i1, 0));
10101 // 2-d vertices always point towards +z
10102 nz = (nz < 0) ? -nz : nz;
10103 }
10104 }
10105 else
10106 {
10107 // more general for non-planar polygons
10108
10109 // calculate face normal with Newell's method
10110 // https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal#Newell.27s_Method
10111
10112 j1 = nc - 1; j2 = 0;
10113 i1 = f(i, j1) - 1; i2 = f(i, j2) - 1;
10114
10115 nx = (v(i2, 1) - v(i1, 1)) * (v(i1, 2) + v(i2, 2));
10116 ny = (v(i2, 2) - v(i1, 2)) * (v(i1, 0) + v(i2, 0));
10117 nz = (v(i2, 0) - v(i1, 0)) * (v(i1, 1) + v(i2, 1));
10118
10119 for (octave_idx_type j = 1; j < nc; j++)
10120 {
10121 j1 = j-1; j2 = j;
10122 i1 = f(i, j1) - 1; i2 = f(i, j2) - 1;
10123
10124 nx += (v(i2, 1) - v(i1, 1)) * (v(i1, 2) + v(i2, 2));
10125 ny += (v(i2, 2) - v(i1, 2)) * (v(i1, 0) + v(i2, 0));
10126 nz += (v(i2, 0) - v(i1, 0)) * (v(i1, 1) + v(i2, 1));
10127 }
10128 }
10129
10130 // normalize normal vector
10131 double n_len = sqrt (nx*nx+ny*ny+nz*nz);
10132
10133 // assign normal to current face
10134 if ( n_len < std::numeric_limits<double>::epsilon () )
10135 for (octave_idx_type j = 0; j < 3; j++)
10136 fn(i, j) = 0.0;
10137 else
10138 for (octave_idx_type j = 0; j < 3; j++)
10139 fn(i, j) = fnc(j) / n_len;
10140 }
10141}
10142
10143void
10144patch::properties::update_face_normals (bool reset, bool force)
10145{
10146 if (updating_patch_data || ! facenormalsmode_is ("auto"))
10147 return;
10148
10149 if (force || ((facelighting_is ("flat") || edgelighting_is ("flat"))
10150 && get_do_lighting ()))
10151 {
10152 Matrix f = get_faces ().matrix_value ();
10153
10154 octave_idx_type num_f = f.rows (); // number of faces
10155 Matrix fn (num_f, 3, 0.0);
10156
10157 calc_face_normals (fn);
10158 m_facenormals = fn;
10159 }
10160 else if (reset)
10161 m_facenormals = Matrix ();
10162}
10163
10164void
10165patch::properties::update_vertex_normals (bool reset, bool force)
10166{
10167 if (updating_patch_data || ! vertexnormalsmode_is ("auto"))
10168 return;
10169
10170 if (force || ((facelighting_is ("gouraud") || facelighting_is ("phong")
10171 || edgelighting_is ("gouraud") || edgelighting_is ("phong"))
10172 && get_do_lighting ()))
10173 {
10174 Matrix v = get_vertices ().matrix_value ();
10175 Matrix f = get_faces ().matrix_value ();
10176
10177 octave_idx_type num_v = v.rows (); // number of vertices
10178 octave_idx_type num_f = f.rows (); // number of faces
10179 octave_idx_type max_nc = f.columns (); // max. number of polygon corners
10180
10181 // In which cases can we skip updating the normals?
10182 if (max_nc < 3)
10183 return;
10184
10185 // First step: Calculate the normals for all faces
10186 Matrix fn = get_facenormals ().matrix_value ();
10187 if ( fn.isempty () )
10188 {
10189 // calculate facenormals here
10190 fn = Matrix (num_f, 3, 0.0);
10191 calc_face_normals (fn);
10192 }
10193
10194 // Second step: assign normals to the respective vertices
10195
10196 // The following code collects the face normals for all faces adjacent to
10197 // each vertex. For this, a std::vector of length NUM_V (which might be
10198 // very large) is used so that memory is allocated from the heap rather
10199 // than the stack. Each element of this vector corresponds to one vertex
10200 // of the patch. The element itself is a variable length std::vector.
10201 // This second vector contains the face normals (of type RowVector) of
10202 // the adjacent faces.
10203 std::vector<std::vector<RowVector>> vec_vn (num_v);
10204 for (octave_idx_type i = 0; i < num_f; i++)
10205 {
10206 // get number of corners
10207 octave_idx_type nc = 3;
10208 if (max_nc > 3)
10209 {
10210 while (nc < max_nc && ! octave::math::isnan (f(i, nc)))
10211 nc++;
10212 }
10213
10214 for (octave_idx_type j = 0; j < nc; j++)
10215 vec_vn[static_cast<octave_idx_type> (f(i, j) - 1)].push_back (fn.row (i));
10216 }
10217
10218 // Third step: Calculate the normal for the vertices taking the average
10219 // of the normals determined from all adjacent faces
10220 Matrix vn (num_v, 3, 0.0);
10221 for (octave_idx_type i = 0; i < num_v; i++)
10222 {
10223 std::vector<RowVector>::iterator it = vec_vn[i].begin ();
10224
10225 // The normal of unused vertices is NaN.
10226 RowVector vn0 (3, octave_NaN);
10227
10228 if (it != vec_vn[i].end ())
10229 {
10230 // FIXME: Currently, the first vector also determines the
10231 // direction of the normal. How to determine the inner and outer
10232 // faces of all parts of the patch and point the normals outwards?
10233 // (Necessary for correct lighting with "backfacelighting" set to
10234 // "lit" or "unlit".) Matlab does not seem to do it correctly
10235 // either. So should we bother?
10236
10237 vn0 = *it;
10238
10239 for (++it; it != vec_vn[i].end (); ++it)
10240 {
10241 RowVector vn1 = *it;
10242 // Use sign of dot product to point vectors in a similar
10243 // direction before taking the average.
10244 double dir = (vn0(0)*vn1(0) + vn0(1)*vn1(1) + vn0(2)*vn1(2) < 0) ? -1 : 1;
10245 for (octave_idx_type j = 0; j < 3; j++)
10246 vn0(j) += dir * vn1(j);
10247 }
10248
10249 // normalize normal vector
10250 double n_len = sqrt (vn0(0)*vn0(0)+vn0(1)*vn0(1)+vn0(2)*vn0(2));
10251
10252 // save normal in matrix
10253 for (octave_idx_type j = 0; j < 3; j++)
10254 vn(i, j) = vn0(j)/n_len;
10255 }
10256 }
10257
10258 m_vertexnormals = vn;
10259 }
10260 else if (reset)
10261 m_vertexnormals = Matrix ();
10262}
10263
10264void
10265patch::initialize (const graphics_object& go)
10266{
10268
10269 // calculate normals for default data
10270 // This is done because the normals for the default data do not match
10271 // get(0, "DefaultPatchVertexNormals") in Matlab.
10272 m_properties.update_normals (true);
10273}
10274
10275
10276void
10277patch::reset_default_properties (void)
10278{
10279 // empty list of local defaults
10280 m_default_properties = property_list ();
10281 xreset_default_properties (get_handle (), m_properties.factory_defaults ());
10282
10283 // calculate normals for default data
10284 // This is done because the normals for the default data do not match
10285 // get(0, "DefaultPatchVertexNormals") in Matlab.
10286 m_properties.update_normals (true);
10287}
10288
10289// ---------------------------------------------------------------------
10290
10292scatter::properties::get_color_data (void) const
10293{
10294 octave_value c = get_cdata ();
10295 if (c.is_undefined () || c.isempty ())
10296 return Matrix ();
10297 else
10298 return convert_cdata (*this, c, c.columns () == 1, 2);
10299}
10300
10301void
10302scatter::properties::update_data (void)
10303{
10304 Matrix xd = get_xdata ().matrix_value ();
10305 Matrix yd = get_ydata ().matrix_value ();
10306 Matrix zd = get_zdata ().matrix_value ();
10307 Matrix cd = get_cdata ().matrix_value ();
10308 Matrix sd = get_sizedata ().matrix_value ();
10309
10310 m_bad_data_msg = "";
10311 if (xd.dims () != yd.dims ()
10312 || (xd.dims () != zd.dims () && ! zd.isempty ()))
10313 {
10314 m_bad_data_msg = "x/y/zdata must have the same dimensions";
10315 return;
10316 }
10317
10318 octave_idx_type x_rows = xd.rows ();
10319 octave_idx_type c_cols = cd.columns ();
10320 octave_idx_type c_rows = cd.rows ();
10321
10322 if (! cd.isempty () && (c_rows != 1 || c_cols != 3)
10323 && (c_rows != x_rows || (c_cols != 1 && c_cols != 3)))
10324 {
10325 m_bad_data_msg = "cdata must be an rgb triplet or have the same number of "
10326 "rows as X and one or three columns";
10327 return;
10328 }
10329
10330 octave_idx_type s_rows = sd.rows ();
10331 if (s_rows != 1 && s_rows != x_rows)
10332 {
10333 m_bad_data_msg = "sizedata must be a scalar or a vector with the same "
10334 "dimensions as X";
10335 return;
10336 }
10337}
10338
10339static bool updating_scatter_cdata = false;
10340
10341void
10342scatter::properties::update_color (void)
10343{
10345 return;
10346
10347 Matrix series_idx = get_seriesindex ().matrix_value ();
10348 if (series_idx.isempty ())
10349 return;
10350
10351 gh_manager& gh_mgr
10352 = octave::__get_gh_manager__ ("scatter::properties::update_color");
10353
10354 graphics_object go = gh_mgr.get_object (get___myhandle__ ());
10355
10356 axes::properties& parent_axes_prop
10357 = dynamic_cast<axes::properties&>
10358 (go.get_ancestor ("axes").get_properties ());
10359
10360 Matrix color_order = parent_axes_prop.get_colororder ().matrix_value ();
10361 octave_idx_type s = (static_cast<octave_idx_type> (series_idx(0)) - 1)
10362 % color_order.rows ();
10363
10364 Matrix color = Matrix (1, 3, 0.);
10365 color(0) = color_order(s, 0);
10366 color(1) = color_order(s, 1);
10367 color(2) = color_order(s, 2);
10368
10370
10371 set_cdata (color);
10372 set_cdatamode ("auto");
10373}
10374
10375void
10376scatter::initialize (const graphics_object& go)
10377{
10379
10380 Matrix series_idx = m_properties.get_seriesindex ().matrix_value ();
10381 if (series_idx.isempty ())
10382 {
10383 // Increment series index counter in parent axes
10384 axes::properties& parent_axes_prop
10385 = dynamic_cast<axes::properties&>
10386 (go.get_ancestor ("axes").get_properties ());
10387
10388 if (! parent_axes_prop.nextplot_is ("add"))
10389 parent_axes_prop.set_nextseriesindex (1);
10390
10391 series_idx.resize (1, 1);
10392 series_idx(0) = parent_axes_prop.get_nextseriesindex ();
10393 m_properties.set_seriesindex (series_idx);
10394
10395 parent_axes_prop.set_nextseriesindex
10396 (parent_axes_prop.get_nextseriesindex () + 1);
10397 }
10398
10399 if (m_properties.cdatamode_is ("auto"))
10400 m_properties.update_color ();
10401}
10402
10403// ---------------------------------------------------------------------
10404
10406surface::properties::get_color_data (void) const
10407{
10408 return convert_cdata (*this, get_cdata (), cdatamapping_is ("scaled"), 3);
10409}
10410
10411bool
10412surface::properties::get_do_lighting (void) const
10413{
10414 gh_manager& gh_mgr
10415 = octave::__get_gh_manager__ ("surface::properties::get_do_lighting");
10416
10417 graphics_object go = gh_mgr.get_object (get___myhandle__ ());
10418
10419 axes::properties& ax_prop = dynamic_cast<axes::properties&>
10420 (go.get_ancestor ("axes").get_properties ());
10421
10422 return (ax_prop.get_num_lights () > 0);
10423}
10424
10425void
10426surface::properties::update_face_normals (bool reset, bool force)
10427{
10428 if (! facenormalsmode_is ("auto"))
10429 return;
10430
10431 if (force || ((facelighting_is ("flat") || edgelighting_is ("flat"))
10432 && get_do_lighting ()))
10433 {
10434 Matrix x = get_xdata ().matrix_value ();
10435 Matrix y = get_ydata ().matrix_value ();
10436 Matrix z = get_zdata ().matrix_value ();
10437
10438 int p = z.columns ();
10439 int q = z.rows ();
10440
10441 // FIXME: There might be a cleaner way to do this. When data is changed
10442 // the update_xdata, update_ydata, update_zdata routines are called in a
10443 // serial fashion. Until the final call to update_zdata the matrices
10444 // will be of mismatched dimensions which can cause an out-of-bound
10445 // indexing in the code below. This one-liner prevents calculating
10446 // normals until dimensions match.
10447 if (x.columns () != p || y.rows () != q)
10448 return;
10449
10450 bool x_mat = (x.rows () == q);
10451 bool y_mat = (y.columns () == p);
10452
10453 NDArray n (dim_vector (q-1, p-1, 3), 1);
10454
10455 int i1, i2, j1, j2;
10456 i1 = i2 = 0;
10457 j1 = j2 = 0;
10458 double x0, x1, x2, x3, y0, y1, y2, y3, z0, z1, z2, z3;
10459 double x1m0, x2m1, x3m2, x0m3, y1m0, y2m1, y3m2, y0m3;
10460 double x1p0, x2p1, x3p2, x0p3, y1p0, y2p1, y3p2, y0p3;
10461 x3m2 = y0m3 = -1;
10462 x2m1 = x0m3 = y1m0 = y3m2 = 0;
10463 x1m0 = y2m1 = 1;
10464 x0p3 = y1p0 = 0;
10465 x1p0 = x3p2 = y2p1 = y0p3 = 1;
10466 x2p1 = y3p2 = 2;
10467
10468 for (int i = 0; i < p-1; i++)
10469 {
10470 i1 = i;
10471 i2 = i + 1;
10472
10473 for (int j = 0; j < q-1; j++)
10474 {
10475 j1 = j;
10476 j2 = j + 1;
10477
10478 if (x_mat || y_mat)
10479 {
10480 x0 = x(x_mat?j1:0, y_mat?i1:0);
10481 x1 = x(x_mat?j1:0, y_mat?i2:0);
10482 x2 = x(x_mat?j2:0, y_mat?i2:0);
10483 x3 = x(x_mat?j2:0, y_mat?i1:0);
10484 x1m0 = x1 - x0;
10485 x2m1 = x2 - x1;
10486 x3m2 = x3 - x2;
10487 x0m3 = x0 - x3;
10488 x1p0 = x1 + x0;
10489 x2p1 = x2 + x1;
10490 x3p2 = x3 + x2;
10491 x0p3 = x0 + x3;
10492 y0 = y(x_mat?j1:0, y_mat?i1:0);
10493 y1 = y(x_mat?j1:0, y_mat?i2:0);
10494 y2 = y(x_mat?j2:0, y_mat?i2:0);
10495 y3 = y(x_mat?j2:0, y_mat?i1:0);
10496 y1m0 = y1 - y0;
10497 y2m1 = y2 - y1;
10498 y3m2 = y3 - y2;
10499 y0m3 = y0 - y3;
10500 y1p0 = y1 + y0;
10501 y2p1 = y2 + y1;
10502 y3p2 = y3 + y2;
10503 y0p3 = y0 + y3;
10504 }
10505
10506 double& nx = n(j, i, 0);
10507 double& ny = n(j, i, 1);
10508 double& nz = n(j, i, 2);
10509
10510 z0 = z(j1, i1);
10511 z1 = z(j1, i2);
10512 z2 = z(j2, i2);
10513 z3 = z(j2, i1);
10514
10515 // calculate face normal with Newell's method
10516 // https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal#Newell.27s_Method
10517
10518 nx = y1m0 * (z1 + z0) + y2m1 * (z2 + z1)
10519 + y3m2 * (z3 + z2) + y0m3 * (z0 + z3);
10520 ny = (z1 - z0) * x1p0 + (z2 - z1) * x2p1
10521 + (z3 - z2) * x3p2 + (z0 - z3) * x0p3;
10522 nz = x1m0 * y1p0 + x2m1 * y2p1 + x3m2 * y3p2 + x0m3 * y0p3;
10523
10524 double d = std::max (std::max (fabs (nx), fabs (ny)), fabs (nz));
10525
10526 nx /= d;
10527 ny /= d;
10528 nz /= d;
10529 }
10530 }
10531 m_facenormals = n;
10532 }
10533 else if (reset)
10534 m_facenormals = Matrix ();
10535}
10536
10537void
10538surface::properties::update_vertex_normals (bool reset, bool force)
10539{
10540 if (! vertexnormalsmode_is ("auto"))
10541 return;
10542
10543 if (force || ((facelighting_is ("gouraud") || facelighting_is ("phong")
10544 || edgelighting_is ("gouraud") || edgelighting_is ("phong"))
10545 && get_do_lighting ()))
10546 {
10547 Matrix x = get_xdata ().matrix_value ();
10548 Matrix y = get_ydata ().matrix_value ();
10549 Matrix z = get_zdata ().matrix_value ();
10550
10551 int p = z.columns ();
10552 int q = z.rows ();
10553
10554 // FIXME: There might be a cleaner way to do this. When data is changed
10555 // the update_xdata, update_ydata, update_zdata routines are called in a
10556 // serial fashion. Until the final call to update_zdata the matrices
10557 // will be of mismatched dimensions which can cause an out-of-bound
10558 // indexing in the code below. This one-liner prevents calculating
10559 // normals until dimensions match.
10560 if (x.columns () != p || y.rows () != q)
10561 return;
10562
10563 NDArray n (dim_vector (q, p, 3), 0.0);
10564
10565 bool x_mat = (x.rows () == q);
10566 bool y_mat = (y.columns () == p);
10567
10568 int i1, i2, i3, j1, j2, j3;
10569 i1 = i2 = i3 = 0;
10570 j1 = j2 = j3 = 0;
10571
10572 for (int i = 0; i < p; i++)
10573 {
10574 if (y_mat)
10575 {
10576 i1 = i - 1;
10577 i2 = i;
10578 i3 = i + 1;
10579 }
10580
10581 for (int j = 0; j < q; j++)
10582 {
10583 if (x_mat)
10584 {
10585 j1 = j - 1;
10586 j2 = j;
10587 j3 = j + 1;
10588 }
10589
10590 double& nx = n(j, i, 0);
10591 double& ny = n(j, i, 1);
10592 double& nz = n(j, i, 2);
10593
10594 if ((j > 0) && (i > 0))
10595 // upper left quadrangle
10597 (x(j1,i-1)-x(j2,i), y(j-1,i1)-y(j,i2), z(j-1,i-1)-z(j,i),
10598 x(j2,i-1)-x(j1,i), y(j,i1)-y(j-1,i2), z(j,i-1)-z(j-1,i),
10599 nx, ny, nz);
10600
10601 if ((j > 0) && (i < (p -1)))
10602 // upper right quadrangle
10604 (x(j1,i+1)-x(j2,i), y(j-1,i3)-y(j,i2), z(j-1,i+1)-z(j,i),
10605 x(j1,i)-x(j2,i+1), y(j-1,i2)-y(j,i3), z(j-1,i)-z(j,i+1),
10606 nx, ny, nz);
10607
10608 if ((j < (q - 1)) && (i > 0))
10609 // lower left quadrangle
10611 (x(j2,i-1)-x(j3,i), y(j,i1)-y(j+1,i2), z(j,i-1)-z(j+1,i),
10612 x(j3,i-1)-x(j2,i), y(j+1,i1)-y(j,i2), z(j+1,i-1)-z(j,i),
10613 nx, ny, nz);
10614
10615 if ((j < (q - 1)) && (i < (p -1)))
10616 // lower right quadrangle
10618 (x(j3,i)-x(j2,i+1), y(j+1,i2)-y(j,i3), z(j+1,i)-z(j,i+1),
10619 x(j3,i+1)-x(j2,i), y(j+1,i3)-y(j,i2), z(j+1,i+1)-z(j,i),
10620 nx, ny, nz);
10621
10622 double d = -std::max (std::max (fabs (nx), fabs (ny)), fabs (nz));
10623
10624 nx /= d;
10625 ny /= d;
10626 nz /= d;
10627 }
10628 }
10629 m_vertexnormals = n;
10630 }
10631 else if (reset)
10632 m_vertexnormals = Matrix ();
10633}
10634
10635DEFMETHOD (__update_normals__, interp, args, ,
10636 doc: /* -*- texinfo -*-
10637@deftypefn {} {} __update_normals__ (@var{h})
10638Update FaceNormals and VertexNormals of the patch or surface referred to by
10639@var{h}.
10640
10641@end deftypefn */)
10642{
10643 gh_manager& gh_mgr = interp.get_gh_manager ();
10644
10645 octave::autolock guard (gh_mgr.graphics_lock ());
10646
10647 if (args.length () != 1)
10648 print_usage ();
10649
10650 octave_value val = args(0);
10651
10652 graphics_object go = gh_mgr.get_object (val);
10653
10654 if (go.isa ("surface"))
10655 {
10656 surface::properties& props
10657 = dynamic_cast <surface::properties&> (go.get_properties ());
10658 props.update_normals (false, true);
10659 }
10660 else if (go.isa ("patch"))
10661 {
10662 patch::properties& props
10663 = dynamic_cast <patch::properties&> (go.get_properties ());
10664 props.update_normals (false, true);
10665 }
10666 else
10667 error ("__update_normals__: "
10668 "H must be a handle to a valid surface or patch object.");
10669
10670 return ovl ();
10671}
10672
10673/*
10674%!test
10675%! hf = figure ("visible", "off");
10676%! unwind_protect
10677%! Z = peaks ();
10678%! hs = surf (Z, "facelighting", "none");
10679%! assert (isempty (get (hs, "vertexnormals")));
10680%! assert (isempty (get (hs, "facenormals")));
10681%! __update_normals__ (hs);
10682%! assert (! isempty (get (hs, "vertexnormals")));
10683%! assert (! isempty (get (hs, "facenormals")));
10684%! unwind_protect_cleanup
10685%! close (hf);
10686%! end_unwind_protect
10687
10688%!test
10689%! hf = figure ("visible", "off");
10690%! unwind_protect
10691%! hp = patch ("facelighting", "none");
10692%! assert (isempty (get (hp, "vertexnormals")));
10693%! assert (isempty (get (hp, "facenormals")));
10694%! __update_normals__ (hp);
10695%! assert (! isempty (get (hp, "vertexnormals")));
10696%! assert (! isempty (get (hp, "facenormals")));
10697%! unwind_protect_cleanup
10698%! close (hf);
10699%! end_unwind_protect
10700*/
10701
10702// ---------------------------------------------------------------------
10703
10704void
10705hggroup::properties::remove_child (const graphics_handle& h, bool from_root)
10706{
10707 gh_manager& gh_mgr
10708 = octave::__get_gh_manager__ ("hggroup::properties::remove_child");
10709
10710 graphics_object go = gh_mgr.get_object (h);
10711
10712 if (! from_root && go.isa ("light") && go.get_properties ().is_visible ())
10713 {
10714 axes::properties& ax_props
10715 = dynamic_cast<axes::properties&> (go.get_ancestor ("axes").get_properties ());
10716 ax_props.decrease_num_lights ();
10717 }
10718 base_properties::remove_child (h, from_root);
10719 update_limits ();
10720}
10721
10722void
10724{
10725 gh_manager& gh_mgr
10726 = octave::__get_gh_manager__ ("hggroup::properties::adopt");
10727
10728 graphics_object go = gh_mgr.get_object (h);
10729
10730 if (go.isa ("light") && go.get_properties ().is_visible ())
10731 {
10732 axes::properties& ax_props
10733 = dynamic_cast<axes::properties&> (go.get_ancestor ("axes").get_properties ());
10734 ax_props.increase_num_lights ();
10735 }
10737 update_limits (h);
10738}
10739
10740void
10741hggroup::properties::update_limits (void) const
10742{
10743 gh_manager& gh_mgr
10744 = octave::__get_gh_manager__ ("hggroup::properties::update_limits");
10745
10746 graphics_object go = gh_mgr.get_object (m___myhandle__);
10747
10748 if (go)
10749 {
10750 go.update_axis_limits ("xlim");
10751 go.update_axis_limits ("ylim");
10752 go.update_axis_limits ("zlim");
10753 go.update_axis_limits ("clim");
10754 go.update_axis_limits ("alim");
10755 }
10756}
10757
10758void
10759hggroup::properties::update_limits (const graphics_handle& h) const
10760{
10761 gh_manager& gh_mgr
10762 = octave::__get_gh_manager__ ("hggroup::properties::update_limits");
10763
10764 graphics_object go = gh_mgr.get_object (m___myhandle__);
10765
10766 if (go)
10767 {
10768 go.update_axis_limits ("xlim", h);
10769 go.update_axis_limits ("ylim", h);
10770 go.update_axis_limits ("zlim", h);
10771 go.update_axis_limits ("clim", h);
10772 go.update_axis_limits ("alim", h);
10773 }
10774}
10775
10776static bool updating_hggroup_limits = false;
10777
10778void
10779hggroup::update_axis_limits (const std::string& axis_type,
10780 const graphics_handle& h)
10781{
10783 return;
10784
10785 Matrix kids = Matrix (1, 1, h.value ());
10786
10787 double min_val = octave::numeric_limits<double>::Inf ();
10788 double max_val = -octave::numeric_limits<double>::Inf ();
10789 double min_pos = octave::numeric_limits<double>::Inf ();
10790 double max_neg = -octave::numeric_limits<double>::Inf ();
10791
10792 Matrix limits;
10793 double val;
10794
10795 char update_type = 0;
10796
10797 if (axis_type == "xlim" || axis_type == "xliminclude")
10798 {
10799 limits = m_properties.get_xlim ().matrix_value ();
10800 update_type = 'x';
10801 }
10802 else if (axis_type == "ylim" || axis_type == "yliminclude")
10803 {
10804 limits = m_properties.get_ylim ().matrix_value ();
10805 update_type = 'y';
10806 }
10807 else if (axis_type == "zlim" || axis_type == "zliminclude")
10808 {
10809 limits = m_properties.get_zlim ().matrix_value ();
10810 update_type = 'z';
10811 }
10812 else if (axis_type == "clim" || axis_type == "climinclude")
10813 {
10814 limits = m_properties.get_clim ().matrix_value ();
10815 update_type = 'c';
10816 }
10817 else if (axis_type == "alim" || axis_type == "aliminclude")
10818 {
10819 limits = m_properties.get_alim ().matrix_value ();
10820 update_type = 'a';
10821 }
10822
10823 if (limits.numel () == 4)
10824 {
10825 val = limits(0);
10826 if (octave::math::isfinite (val))
10827 min_val = val;
10828 val = limits(1);
10829 if (octave::math::isfinite (val))
10830 max_val = val;
10831 val = limits(2);
10832 if (octave::math::isfinite (val))
10833 min_pos = val;
10834 val = limits(3);
10835 if (octave::math::isfinite (val))
10836 max_neg = val;
10837 }
10838 else
10839 {
10840 limits.resize (1, 4);
10841 limits(0) = min_val;
10842 limits(1) = max_val;
10843 limits(2) = min_pos;
10844 limits(3) = max_neg;
10845 }
10846
10847 get_children_limits (min_val, max_val, min_pos, max_neg, kids, update_type);
10848
10850
10851 if (limits(0) != min_val || limits(1) != max_val
10852 || limits(2) != min_pos || limits(3) != max_neg)
10853 {
10854 limits(0) = min_val;
10855 limits(1) = max_val;
10856 limits(2) = min_pos;
10857 limits(3) = max_neg;
10858
10859 switch (update_type)
10860 {
10861 case 'x':
10862 m_properties.set_xlim (limits);
10863 break;
10864
10865 case 'y':
10866 m_properties.set_ylim (limits);
10867 break;
10868
10869 case 'z':
10870 m_properties.set_zlim (limits);
10871 break;
10872
10873 case 'c':
10874 m_properties.set_clim (limits);
10875 break;
10876
10877 case 'a':
10878 m_properties.set_alim (limits);
10879 break;
10880
10881 default:
10882 break;
10883 }
10884
10885 graphics_handle hg = m_properties.get___myhandle__ ();
10886 base_graphics_object::update_axis_limits (axis_type, hg);
10887 }
10888}
10889
10890void
10891hggroup::update_axis_limits (const std::string& axis_type)
10892{
10894 return;
10895
10896 Matrix kids = m_properties.get_children ();
10897
10898 double min_val = octave::numeric_limits<double>::Inf ();
10899 double max_val = -octave::numeric_limits<double>::Inf ();
10900 double min_pos = octave::numeric_limits<double>::Inf ();
10901 double max_neg = -octave::numeric_limits<double>::Inf ();
10902
10903 char update_type = 0;
10904
10905 if (axis_type == "xlim" || axis_type == "xliminclude")
10906 {
10907 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'x');
10908
10909 update_type = 'x';
10910 }
10911 else if (axis_type == "ylim" || axis_type == "yliminclude")
10912 {
10913 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'y');
10914
10915 update_type = 'y';
10916 }
10917 else if (axis_type == "zlim" || axis_type == "zliminclude")
10918 {
10919 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'z');
10920
10921 update_type = 'z';
10922 }
10923 else if (axis_type == "clim" || axis_type == "climinclude")
10924 {
10925 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'c');
10926
10927 update_type = 'c';
10928 }
10929 else if (axis_type == "alim" || axis_type == "aliminclude")
10930 {
10931 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'a');
10932
10933 update_type = 'a';
10934 }
10935
10937
10938 Matrix limits (1, 4);
10939
10940 limits(0) = min_val;
10941 limits(1) = max_val;
10942 limits(2) = min_pos;
10943 limits(3) = max_neg;
10944
10945 switch (update_type)
10946 {
10947 case 'x':
10948 m_properties.set_xlim (limits);
10949 break;
10950
10951 case 'y':
10952 m_properties.set_ylim (limits);
10953 break;
10954
10955 case 'z':
10956 m_properties.set_zlim (limits);
10957 break;
10958
10959 case 'c':
10960 m_properties.set_clim (limits);
10961 break;
10962
10963 case 'a':
10964 m_properties.set_alim (limits);
10965 break;
10966
10967 default:
10968 break;
10969 }
10970
10971 base_graphics_object::update_axis_limits (axis_type);
10972}
10973
10974// ---------------------------------------------------------------------
10975
10976void
10977uicontextmenu::properties::update_beingdeleted (void)
10978{
10979 // Clear the uicontextmenu property of dependent objects
10980 if (m_beingdeleted.is ("on"))
10981 {
10982 gh_manager& gh_mgr
10983 = octave::__get_gh_manager__ ("uicontextmenu::properties::update_beingdeleted");
10984
10985 std::list<graphics_handle> lst = get_dependent_obj_list ();
10986
10987 for (auto& hobj : lst)
10988 {
10989 graphics_object go = gh_mgr.get_object (hobj);
10990
10991 if (go.valid_object ()
10992 && go.get ("contextmenu") == get___myhandle__ ())
10993 go.set ("contextmenu", Matrix ());
10994 }
10995 }
10996}
10997
10998/*
10999## Test deletion/reset of uicontextmenu
11000%!test
11001%! hf = figure ("visible", "off");
11002%! hax = axes ("parent", hf);
11003%! unwind_protect
11004%! hctx1 = uicontextmenu ("parent", hf);
11005%! hctx2 = uicontextmenu ("parent", hf);
11006%! set (hf, "uicontextmenu", hctx2);
11007%! set (hax, "uicontextmenu", hctx2);
11008%! assert (get (hf, "uicontextmenu"), hctx2);
11009%! assert (get (hax, "uicontextmenu"), hctx2);
11010%! assert (get (hf, "children"), [hctx2; hctx1; hax]);
11011%! delete (hctx2);
11012%! assert (get (hf, "uicontextmenu"), []);
11013%! assert (get (hax, "uicontextmenu"), []);
11014%! assert (get (hf, "children"), [hctx1; hax]);
11015%! set (hf, "uicontextmenu", hctx1);
11016%! assert (get (hf, "uicontextmenu"), hctx1);
11017%! set (hf, "uicontextmenu", []);
11018%! assert (get (hf, "uicontextmenu"), []);
11019%! assert (get (hf, "children"), [hctx1; hax]);
11020%! unwind_protect_cleanup
11021%! close (hf);
11022%! end_unwind_protect;
11023*/
11024
11025// ---------------------------------------------------------------------
11026
11028uicontrol::properties::get_extent (void) const
11029{
11030 Matrix m = m_extent.get ().matrix_value ();
11031
11032 gh_manager& gh_mgr
11033 = octave::__get_gh_manager__ ("uicontrol::properties::get_extent");
11034
11035 graphics_object parent_go = gh_mgr.get_object (get_parent ());
11036
11037 Matrix parent_bbox = parent_go.get_properties ().get_boundingbox (true);
11038 Matrix parent_size = parent_bbox.extract_n (0, 2, 1, 2);
11039
11040 return convert_position (m, "pixels", get_units (), parent_size);
11041}
11042
11043void
11044uicontrol::properties::update_text_extent (void)
11045{
11046 // FIXME: support multiline text
11047
11048 gh_manager& gh_mgr
11049 = octave::__get_gh_manager__ ("uicontrol::properties::update_text_extent");
11050
11051 graphics_object go = gh_mgr.get_object (get___myhandle__ ());
11052
11053 set_extent (go.get_toolkit ().get_text_extent (go));
11054}
11055
11056void
11057uicontrol::properties::update_units (void)
11058{
11059 Matrix pos = get_position ().matrix_value ();
11060
11061 gh_manager& gh_mgr
11062 = octave::__get_gh_manager__ ("uicontrol::properties::update_units");
11063
11064 graphics_object parent_go = gh_mgr.get_object (get_parent ());
11065
11066 Matrix parent_bbox = parent_go.get_properties ().get_boundingbox (true);
11067 Matrix parent_size = parent_bbox.extract_n (0, 2, 1, 2);
11068
11069 pos = convert_position (pos, m_cached_units, get_units (), parent_size);
11070 set_position (pos);
11071
11072 m_cached_units = get_units ();
11073}
11074
11075void
11076uicontrol::properties::set_style (const octave_value& st)
11077{
11078 gh_manager& gh_mgr
11079 = octave::__get_gh_manager__ ("uicontrol::properties::set_style");
11080
11081 graphics_object go_parent = gh_mgr.get_object (get_parent ());
11082 if (go_parent.valid_object () && go_parent.isa ("uibuttongroup"))
11083 {
11084 bool was_button = style_is ("radiobutton") || style_is ("togglebutton");
11085 m_style = st;
11086 bool now_button = style_is ("radiobutton") || style_is ("togglebutton");
11088 dynamic_cast<uibuttongroup::properties&> (go_parent.get_properties ());
11089 // update selectedobject
11090 if (! was_button && now_button && ! props.get_selectedobject ().ok ())
11091 {
11092 props.set_selectedobject (get___myhandle__ ().value ());
11093 m_value.set (octave_value (1));
11094 }
11095 else if (was_button && ! now_button
11096 && (props.get_selectedobject ().value ()
11097 == get___myhandle__ ().value ()))
11098 props.set_selectedobject (Matrix ());
11099 }
11100
11101 // Don't notify the style change until the "value" property is fixed
11102 bool modified = m_style.set (st, true, false);
11103
11104 // Override "value" property for listbox and popupmenu.
11105 if (modified)
11106 {
11107 if (style_is ("listbox") || style_is ("popupmenu"))
11108 {
11109 Matrix v = m_value.get ().matrix_value ();
11110 if (v.numel () == 1 && v(0) == 0)
11111 m_value.set (octave_value (1), true, false);
11112 }
11113
11114 // Notify toolkit
11115
11116 graphics_object go = gh_mgr.get_object (get___myhandle__ ());
11117
11118 if (go)
11119 go.update (m_style.get_id ());
11120 }
11121}
11122
11123Matrix
11124uicontrol::properties::get_boundingbox (bool,
11125 const Matrix& parent_pix_size) const
11126{
11127 Matrix pos = get_position ().matrix_value ();
11128 Matrix parent_size (parent_pix_size);
11129
11130 if (parent_size.isempty ())
11131 {
11132 gh_manager& gh_mgr
11133 = octave::__get_gh_manager__ ("uicontrol::properties::get_boundingbox");
11134
11135 graphics_object go = gh_mgr.get_object (get_parent ());
11136
11137 if (go.valid_object ())
11138 parent_size = go.get_properties ().get_boundingbox (true).extract_n (0, 2, 1, 2);
11139 else
11140 parent_size = default_figure_position ();
11141 }
11142
11143 pos = convert_position (pos, get_units (), "pixels", parent_size);
11144
11145 pos(0)--;
11146 pos(1)--;
11147 pos(1) = parent_size(1) - pos(1) - pos(3);
11148
11149 return pos;
11150}
11151
11152void
11153uicontrol::properties::set_fontunits (const octave_value& val)
11154{
11155 caseless_str old_fontunits = get_fontunits ();
11156
11157 if (m_fontunits.set (val, true))
11158 {
11159 update_fontunits (old_fontunits);
11160 mark_modified ();
11161 }
11162}
11163
11164void
11165uicontrol::properties::update_fontunits (const caseless_str& old_units)
11166{
11167 caseless_str new_units = get_fontunits ();
11168 double parent_height = get_boundingbox (false).elem (3);
11169 double fontsz = get_fontsize ();
11170
11171 fontsz = convert_font_size (fontsz, old_units, new_units, parent_height);
11172
11173 m_fontsize.set (octave_value (fontsz), true);
11174}
11175
11176double
11177uicontrol::properties::get___fontsize_points__ (double box_pix_height) const
11178{
11179 double fontsz = get_fontsize ();
11180 double parent_height = box_pix_height;
11181
11182 if (fontunits_is ("normalized") && parent_height <= 0)
11183 parent_height = get_boundingbox (false).elem (3);
11184
11185 return convert_font_size (fontsz, get_fontunits (), "points", parent_height);
11186}
11187
11188// ---------------------------------------------------------------------
11189
11190Matrix
11191uibuttongroup::properties::get_boundingbox (bool internal,
11192 const Matrix& parent_pix_size) const
11193{
11194 Matrix pos = get_position ().matrix_value ();
11195 Matrix parent_size (parent_pix_size);
11196
11197 if (parent_size.isempty ())
11198 {
11199 gh_manager& gh_mgr
11200 = octave::__get_gh_manager__ ("uibuttongroup::properties::get_boundingbox");
11201
11202 graphics_object go = gh_mgr.get_object (get_parent ());
11203
11204 parent_size = go.get_properties ().get_boundingbox (true).extract_n (0, 2, 1, 2);
11205 }
11206
11207 pos = convert_position (pos, get_units (), "pixels", parent_size);
11208
11209 pos(0)--;
11210 pos(1)--;
11211 pos(1) = parent_size(1) - pos(1) - pos(3);
11212
11213 if (internal)
11214 {
11215 double outer_height = pos(3);
11216
11217 pos(0) = pos(1) = 0;
11218
11219 if (! bordertype_is ("none"))
11220 {
11221 double bw = get_borderwidth ();
11222 double mul = 1.0;
11223
11224 if (bordertype_is ("etchedin") || bordertype_is ("etchedout"))
11225 mul = 2.0;
11226
11227 pos(0) += mul * bw;
11228 pos(1) += mul * bw;
11229 pos(2) -= 2 * mul * bw;
11230 pos(3) -= 2 * mul * bw;
11231 }
11232
11233 if (! get_title ().empty ())
11234 {
11235 double fontsz = get_fontsize ();
11236
11237 if (! fontunits_is ("pixels"))
11238 {
11239 double res = xget (0, "screenpixelsperinch").double_value ();
11240
11241 if (fontunits_is ("points"))
11242 fontsz *= (res / 72.0);
11243 else if (fontunits_is ("inches"))
11244 fontsz *= res;
11245 else if (fontunits_is ("centimeters"))
11246 fontsz *= (res / 2.54);
11247 else if (fontunits_is ("normalized"))
11248 fontsz *= outer_height;
11249 }
11250
11251 if (titleposition_is ("lefttop") || titleposition_is ("centertop")
11252 || titleposition_is ("righttop"))
11253 pos(1) += (fontsz / 2);
11254 pos(3) -= (fontsz / 2);
11255 }
11256 }
11257
11258 return pos;
11259}
11260
11261void
11262uibuttongroup::properties::set_position (const octave_value& v)
11263{
11264 Matrix old_bb, new_bb;
11265 bool modified = false;
11266
11267 old_bb = get_boundingbox (true);
11268 modified = m_position.set (v, false);
11269 new_bb = get_boundingbox (true);
11270
11271 if (old_bb != new_bb)
11272 {
11273 if (old_bb(2) != new_bb(2) || old_bb(3) != new_bb(3))
11274 {
11275 gh_manager& gh_mgr
11276 = octave::__get_gh_manager__ ("uibuttongroup::properties::set_position");
11277
11278 if (! get_resizefcn ().isempty ())
11279 gh_mgr.post_callback (m___myhandle__, "resizefcn");
11280
11281 if (! get_sizechangedfcn ().isempty ())
11282 gh_mgr.post_callback (m___myhandle__, "sizechangedfcn");
11283
11284 update_boundingbox ();
11285 }
11286 }
11287
11288 if (modified)
11289 {
11290 m_position.run_listeners (GCB_POSTSET);
11291 mark_modified ();
11292 }
11293}
11294
11295void
11296uibuttongroup::properties::set_units (const octave_value& val)
11297{
11298 caseless_str old_units = get_units ();
11299
11300 if (m_units.set (val, true))
11301 {
11302 update_units (old_units);
11303 mark_modified ();
11304 }
11305}
11306
11307void
11308uibuttongroup::properties::update_units (const caseless_str& old_units)
11309{
11310 Matrix pos = get_position ().matrix_value ();
11311
11312 gh_manager& gh_mgr
11313 = octave::__get_gh_manager__ ("uibuttongroup::properties::update_units");
11314
11315 graphics_object parent_go = gh_mgr.get_object (get_parent ());
11316
11317 Matrix parent_bbox = parent_go.get_properties ().get_boundingbox (true);
11318 Matrix parent_size = parent_bbox.extract_n (0, 2, 1, 2);
11319
11320 pos = convert_position (pos, old_units, get_units (), parent_size);
11321 set_position (pos);
11322}
11323
11324void
11325uibuttongroup::properties::set_fontunits (const octave_value& val)
11326{
11327 caseless_str old_fontunits = get_fontunits ();
11328
11329 if (m_fontunits.set (val, true))
11330 {
11331 update_fontunits (old_fontunits);
11332 mark_modified ();
11333 }
11334}
11335
11336void
11337uibuttongroup::properties::update_fontunits (const caseless_str& old_units)
11338{
11339 caseless_str new_units = get_fontunits ();
11340 double parent_height = get_boundingbox (false).elem (3);
11341 double fontsz = get_fontsize ();
11342
11343 fontsz = convert_font_size (fontsz, old_units, new_units, parent_height);
11344
11345 set_fontsize (octave_value (fontsz));
11346}
11347
11348double
11349uibuttongroup::properties::get___fontsize_points__ (double box_pix_height) const
11350{
11351 double fontsz = get_fontsize ();
11352 double parent_height = box_pix_height;
11353
11354 if (fontunits_is ("normalized") && parent_height <= 0)
11355 parent_height = get_boundingbox (false).elem (3);
11356
11357 return convert_font_size (fontsz, get_fontunits (), "points", parent_height);
11358}
11359
11360void
11361uibuttongroup::properties::set_selectedobject (const octave_value& v)
11362{
11363 graphics_handle current_selectedobject = get_selectedobject();
11364 m_selectedobject = current_selectedobject;
11365 if (v.isempty ())
11366 {
11367 if (current_selectedobject.ok ())
11368 {
11369 m_selectedobject = graphics_handle ();
11370 mark_modified ();
11371 }
11372 return;
11373 }
11374
11375 graphics_handle val (v);
11376 if (val.ok ())
11377 {
11378 gh_manager& gh_mgr
11379 = octave::__get_gh_manager__ ("uibuttongroup::properties::set_selectedobject");
11380
11381 graphics_object go (gh_mgr.get_object (val));
11382
11383 base_properties& gop = go.get_properties ();
11384
11385 if (go.valid_object ()
11386 && gop.get_parent () == get___myhandle__ ()
11387 && go.isa ("uicontrol"))
11388 {
11390 = dynamic_cast<uicontrol::properties&> (go.get_properties ());
11391 const caseless_str& style = cop.get_style ();
11392 if (style.compare ("radiobutton") || style.compare ("togglebutton"))
11393 {
11394 m_selectedobject = val;
11395 mark_modified ();
11396 return;
11397 }
11398 }
11399 }
11400 err_set_invalid ("selectedobject");
11401}
11402
11403void
11404uibuttongroup::properties::remove_child (const graphics_handle& h,
11405 bool from_root)
11406{
11407 graphics_handle current_selected = get_selectedobject ();
11408 if (h.value () == current_selected.value ())
11409 set_selectedobject (Matrix ());
11410
11411 base_properties::remove_child (h, from_root);
11412}
11413
11414void
11416{
11418
11419 graphics_handle current_selected = get_selectedobject ();
11420 bool has_selected = current_selected.ok ();
11421
11422 gh_manager& gh_mgr
11423 = octave::__get_gh_manager__ ("uibuttongroup::properties::adopt");
11424
11425 graphics_object go = gh_mgr.get_object (h);
11426
11427 if (! has_selected && go.valid_object () && go.isa ("uicontrol"))
11428 {
11429 const uicontrol::properties& props =
11430 dynamic_cast<const uicontrol::properties&> (go.get_properties ());
11431 if (props.style_is ("radiobutton") || props.style_is ("togglebutton"))
11432 set_selectedobject (h.value ());
11433 }
11434}
11435
11436// ---------------------------------------------------------------------
11437
11438Matrix
11439uipanel::properties::get_boundingbox (bool internal,
11440 const Matrix& parent_pix_size) const
11441{
11442 Matrix pos = get_position ().matrix_value ();
11443 Matrix parent_size (parent_pix_size);
11444
11445 if (parent_size.isempty ())
11446 {
11447 gh_manager& gh_mgr
11448 = octave::__get_gh_manager__ ("uipanel::properties::get_boundingbox");
11449
11450 graphics_object go = gh_mgr.get_object (get_parent ());
11451
11452 parent_size = go.get_properties ().get_boundingbox (true).extract_n (0, 2, 1, 2);
11453 }
11454
11455 pos = convert_position (pos, get_units (), "pixels", parent_size);
11456
11457 pos(0)--;
11458 pos(1)--;
11459 pos(1) = parent_size(1) - pos(1) - pos(3);
11460
11461 if (internal)
11462 {
11463 double outer_height = pos(3);
11464
11465 pos(0) = pos(1) = 0;
11466
11467 if (! bordertype_is ("none"))
11468 {
11469 double bw = get_borderwidth ();
11470 double mul = 1.0;
11471
11472 if (bordertype_is ("etchedin") || bordertype_is ("etchedout"))
11473 mul = 2.0;
11474
11475 pos(0) += mul * bw;
11476 pos(1) += mul * bw;
11477 pos(2) -= 2 * mul * bw;
11478 pos(3) -= 2 * mul * bw;
11479 }
11480
11481 if (! get_title ().empty ())
11482 {
11483 double fontsz = get_fontsize ();
11484
11485 if (! fontunits_is ("pixels"))
11486 {
11487 double res = xget (0, "screenpixelsperinch").double_value ();
11488
11489 if (fontunits_is ("points"))
11490 fontsz *= (res / 72.0);
11491 else if (fontunits_is ("inches"))
11492 fontsz *= res;
11493 else if (fontunits_is ("centimeters"))
11494 fontsz *= (res / 2.54);
11495 else if (fontunits_is ("normalized"))
11496 fontsz *= outer_height;
11497 }
11498
11499 if (titleposition_is ("lefttop") || titleposition_is ("centertop")
11500 || titleposition_is ("righttop"))
11501 pos(1) += (fontsz / 2);
11502 pos(3) -= (fontsz / 2);
11503 }
11504 }
11505
11506 return pos;
11507}
11508
11509void
11510uipanel::properties::set_position (const octave_value& v)
11511{
11512 Matrix old_bb, new_bb;
11513 bool modified = false;
11514
11515 old_bb = get_boundingbox (true);
11516 modified = m_position.set (v, false);
11517 new_bb = get_boundingbox (true);
11518
11519 if (old_bb != new_bb)
11520 {
11521 if (old_bb(2) != new_bb(2) || old_bb(3) != new_bb(3))
11522 {
11523 gh_manager& gh_mgr
11524 = octave::__get_gh_manager__ ("uipanel::properties::set_position");
11525
11526 if (! get_resizefcn ().isempty ())
11527 gh_mgr.post_callback (m___myhandle__, "resizefcn");
11528
11529 if (! get_sizechangedfcn ().isempty ())
11530 gh_mgr.post_callback (m___myhandle__, "sizechangedfcn");
11531
11532 update_boundingbox ();
11533 }
11534 }
11535
11536 if (modified)
11537 {
11538 m_position.run_listeners (GCB_POSTSET);
11539 mark_modified ();
11540 }
11541}
11542
11543
11544void
11545uipanel::properties::set_units (const octave_value& val)
11546{
11547 caseless_str old_units = get_units ();
11548
11549 if (m_units.set (val, true))
11550 {
11551 update_units (old_units);
11552 mark_modified ();
11553 }
11554}
11555
11556void
11557uipanel::properties::update_units (const caseless_str& old_units)
11558{
11559 Matrix pos = get_position ().matrix_value ();
11560
11561 gh_manager& gh_mgr
11562 = octave::__get_gh_manager__ ("uipanel::properties::update_units");
11563
11564 graphics_object parent_go = gh_mgr.get_object (get_parent ());
11565
11566 Matrix parent_bbox = parent_go.get_properties ().get_boundingbox (true);
11567 Matrix parent_size = parent_bbox.extract_n (0, 2, 1, 2);
11568
11569 pos = convert_position (pos, old_units, get_units (), parent_size);
11570 set_position (pos);
11571}
11572
11573void
11574uipanel::properties::set_fontunits (const octave_value& val)
11575{
11576 caseless_str old_fontunits = get_fontunits ();
11577
11578 if (m_fontunits.set (val, true))
11579 {
11580 update_fontunits (old_fontunits);
11581 mark_modified ();
11582 }
11583}
11584
11585void
11586uipanel::properties::update_fontunits (const caseless_str& old_units)
11587{
11588 caseless_str new_units = get_fontunits ();
11589 double parent_height = get_boundingbox (false).elem (3);
11590 double fontsz = get_fontsize ();
11591
11592 fontsz = convert_font_size (fontsz, old_units, new_units, parent_height);
11593
11594 set_fontsize (octave_value (fontsz));
11595}
11596
11597double
11598uipanel::properties::get___fontsize_points__ (double box_pix_height) const
11599{
11600 double fontsz = get_fontsize ();
11601 double parent_height = box_pix_height;
11602
11603 if (fontunits_is ("normalized") && parent_height <= 0)
11604 parent_height = get_boundingbox (false).elem (3);
11605
11606 return convert_font_size (fontsz, get_fontunits (), "points", parent_height);
11607}
11608
11609// ---------------------------------------------------------------------
11610
11611Matrix
11612uitable::properties::get_boundingbox (bool,
11613 const Matrix& parent_pix_size) const
11614{
11615 Matrix pos = get_position ().matrix_value ();
11616 Matrix parent_size (parent_pix_size);
11617
11618 if (parent_size.isempty ())
11619 {
11620 gh_manager& gh_mgr
11621 = octave::__get_gh_manager__ ("uitable::properties::get_boundingbox");
11622
11623 graphics_object go = gh_mgr.get_object (get_parent ());
11624
11625 parent_size = go.get_properties ().get_boundingbox (true).extract_n (0, 2, 1, 2);
11626 }
11627
11628 pos = convert_position (pos, get_units (), "pixels", parent_size);
11629
11630 pos(0)--;
11631 pos(1)--;
11632 pos(1) = parent_size(1) - pos(1) - pos(3);
11633
11634 return pos;
11635}
11636
11637void
11638uitable::properties::set_columnformat (const octave_value& val)
11639{
11640 /* Matlab only allows certain values for ColumnFormat. Here we only check the
11641 * structure of the argument. Values will be checked in Table.cc */
11642
11643 if (val.iscellstr ())
11644 {
11645 if (m_columnformat.set (val, true))
11646 mark_modified ();
11647 }
11648 else if (val.iscell ())
11649 {
11650 Cell cell_value = val.cell_value ();
11651
11652 for (int i = 0; i < cell_value.numel (); i++)
11653 {
11654 octave_value v = cell_value(i);
11655 if (v.iscell ())
11656 {
11657 /* We are in a pop-up menu selection.
11658 * Matlab only allows non-empty strings here. */
11659 Cell popup = v.cell_value ();
11660 for (int j = 0; j < popup.numel (); j++)
11661 {
11662 octave_value p = popup(j);
11663 if (! p.is_string () || p.isempty ())
11664 error ("set: pop-up menu definitions must be non-empty strings");
11665 }
11666 }
11667 else if (! (v.is_string () || v.isempty ()))
11668 {
11669 error ("set: columnformat definintions must be a cellstr of "
11670 "either 'char', 'short [e|g|eng]?', 'long [e|g|eng]?', "
11671 "'numeric', 'bank', '+', 'rat', 'logical', "
11672 "or a cellstr of non-empty pop-up menu definitions.");
11673 }
11674 }
11675
11676 if (m_columnformat.set (val, true))
11677 mark_modified ();
11678 }
11679 else if (val.isempty ())
11680 {
11681 if (m_columnformat.set (Cell (), true))
11682 mark_modified ();
11683 }
11684 else
11685 {
11686 error ("set: expecting cell of strings");
11687 }
11688}
11689
11690void
11691uitable::properties::set_columnwidth (const octave_value& val)
11692{
11693 bool error_exists = false;
11694
11695 if (val.is_string () && val.string_value (false) == "auto")
11696 error_exists = false;
11697 else if (val.iscell ())
11698 {
11699 Cell cell_value = val.cell_value ();
11700 for (int i = 0; i < cell_value.numel (); i++)
11701 {
11702 octave_value v = cell_value(i);
11703 if (v.is_string ())
11704 {
11705 if (v.string_value (false) != "auto")
11706 error_exists = true;
11707 }
11708 else if (v.iscell ())
11709 {
11710 error_exists = true;
11711 }
11712 else if (! v.is_scalar_type ())
11713 {
11714 error_exists = true;
11715 }
11716 }
11717 }
11718 else
11719 error_exists = true;
11720
11721 if (error_exists)
11722 error ("set: expecting either 'auto' or a cell of pixel values or auto");
11723 else
11724 {
11725 if (m_columnwidth.set (val, true))
11726 mark_modified ();
11727 }
11728}
11729
11730void
11731uitable::properties::set_units (const octave_value& val)
11732{
11733 caseless_str old_units = get_units ();
11734
11735 if (m_units.set (val, true))
11736 {
11737 update_units (old_units);
11738 mark_modified ();
11739 }
11740}
11741
11742void
11743uitable::properties::update_units (const caseless_str& old_units)
11744{
11745 Matrix pos = get_position ().matrix_value ();
11746
11747 gh_manager& gh_mgr
11748 = octave::__get_gh_manager__ ("uitable::properties::update_units");
11749
11750 graphics_object parent_go = gh_mgr.get_object (get_parent ());
11751
11752 Matrix parent_bbox = parent_go.get_properties ().get_boundingbox (true);
11753 Matrix parent_size = parent_bbox.extract_n (0, 2, 1, 2);
11754
11755 pos = convert_position (pos, old_units, get_units (), parent_size);
11756 set_position (pos);
11757}
11758
11759void
11760uitable::properties::set_fontunits (const octave_value& val)
11761{
11762 caseless_str old_fontunits = get_fontunits ();
11763
11764 if (m_fontunits.set (val, true))
11765 {
11766 update_fontunits (old_fontunits);
11767 mark_modified ();
11768 }
11769}
11770
11771void
11772uitable::properties::update_fontunits (const caseless_str& old_units)
11773{
11774 caseless_str new_units = get_fontunits ();
11775 double parent_height = get_boundingbox (false).elem (3);
11776 double fontsz = get_fontsize ();
11777
11778 fontsz = convert_font_size (fontsz, old_units, new_units, parent_height);
11779
11780 set_fontsize (octave_value (fontsz));
11781}
11782
11783double
11784uitable::properties::get___fontsize_points__ (double box_pix_height) const
11785{
11786 double fontsz = get_fontsize ();
11787 double parent_height = box_pix_height;
11788
11789 if (fontunits_is ("normalized") && parent_height <= 0)
11790 parent_height = get_boundingbox (false).elem (3);
11791
11792 return convert_font_size (fontsz, get_fontunits (), "points", parent_height);
11793}
11794
11795double
11796uitable::properties::get_fontsize_pixels (double box_pix_height) const
11797{
11798 double fontsz = get_fontsize ();
11799 double parent_height = box_pix_height;
11800
11801 if (fontunits_is ("normalized") && parent_height <= 0)
11802 parent_height = get_boundingbox (false).elem (3);
11803
11804 return convert_font_size (fontsz, get_fontunits (), "pixels", parent_height);
11805}
11806
11807Matrix
11808uitable::properties::get_backgroundcolor_rgb (void)
11809{
11810 Matrix bg = m_backgroundcolor.get ().matrix_value ();
11811 return bg.row (0);
11812}
11813
11814Matrix
11815uitable::properties::get_alternatebackgroundcolor_rgb (void)
11816{
11817 int i = 0;
11818 Matrix bg = m_backgroundcolor.get ().matrix_value ();
11819 if (bg.rows () > 1)
11820 i = 1;
11821
11822 return bg.row (i);
11823}
11824
11825Matrix
11826uitable::properties::get_extent_matrix (void) const
11827{
11828 return m_extent.get ().matrix_value ();
11829}
11830
11832uitable::properties::get_extent (void) const
11833{
11834 // FIXME: Is it really acceptable to just let the toolkit update the extent?
11835 Matrix m = m_extent.get ().matrix_value ();
11836
11837 gh_manager& gh_mgr
11838 = octave::__get_gh_manager__ ("uitable::properties::get_extent");
11839
11840 graphics_object parent_go = gh_mgr.get_object (get_parent ());
11841
11842 if (parent_go)
11843 {
11844 Matrix parent_bbox = parent_go.get_properties ().get_boundingbox (true);
11845 Matrix parent_size = parent_bbox.extract_n (0, 2, 1, 2);
11846
11847 return convert_position (m, "pixels", get_units (), parent_size);
11848 }
11849
11850 return m;
11851}
11852
11853// ---------------------------------------------------------------------
11854
11856uitoolbar::get_default (const caseless_str& pname) const
11857{
11858 octave_value retval = m_default_properties.lookup (pname);
11859
11860 if (retval.is_undefined ())
11861 {
11862 graphics_handle parent_h = get_parent ();
11863
11864 gh_manager& gh_mgr
11865 = octave::__get_gh_manager__ ("uitoolbar::get_default");
11866
11867 graphics_object parent_go = gh_mgr.get_object (parent_h);
11868
11869 retval = parent_go.get_default (pname);
11870 }
11871
11872 return retval;
11873}
11874
11875void
11876uitoolbar::reset_default_properties (void)
11877{
11878 // empty list of local defaults
11879 m_default_properties = property_list ();
11880
11881 remove_all_listeners ();
11882 xreset_default_properties (get_handle (), m_properties.factory_defaults ());
11883}
11884
11885// ---------------------------------------------------------------------
11886
11888base_graphics_object::get_default (const caseless_str& pname) const
11889{
11890 graphics_handle parent_h = get_parent ();
11891
11892 gh_manager& gh_mgr
11893 = octave::__get_gh_manager__ ("base_graphics_object::get_default");
11894
11895 graphics_object parent_go = gh_mgr.get_object (parent_h);
11896
11897 return parent_go.get_default (type () + pname);
11898}
11899
11901base_graphics_object::get_factory_default (const caseless_str& name) const
11902{
11903 gh_manager& gh_mgr
11904 = octave::__get_gh_manager__ ("base_graphics_object::get_factory_default");
11905
11906 graphics_object parent_go = gh_mgr.get_object (0);
11907
11908 return parent_go.get_factory_default (type () + name);
11909}
11910
11911// We use a random value for the handle to avoid issues with plots and
11912// scalar values for the first argument.
11913gh_manager::gh_manager (octave::interpreter& interp)
11914 : m_interpreter (interp), m_handle_map (), m_handle_free_list (),
11915 m_next_handle (-1.0 - (rand () + 1.0) / (RAND_MAX + 2.0)),
11916 m_figure_list (), m_graphics_lock (), m_event_queue (),
11917 m_callback_objects (), m_event_processing (0)
11918{
11919 m_handle_map[0] = graphics_object (new root_figure ());
11920
11921 octave::gtk_manager& gtk_mgr = octave::__get_gtk_manager__ ("gh_manager");
11922
11923 // Make sure the default graphics toolkit is registered.
11924 gtk_mgr.default_toolkit ();
11925}
11926
11928gh_manager::make_graphics_handle (const std::string& go_name,
11929 const graphics_handle& p,
11930 bool integer_figure_handle,
11931 bool call_createfcn, bool notify_toolkit)
11932{
11933 graphics_handle h = get_handle (integer_figure_handle);
11934
11935 base_graphics_object *bgo = make_graphics_object_from_type (go_name, h, p);
11936
11937 if (! bgo)
11938 error ("gh_manager::make_graphics_handle: invalid object type '%s'",
11939 go_name.c_str ());
11940
11941 graphics_object go (bgo);
11942
11943 m_handle_map[h] = go;
11944
11945 if (go_name == "axes")
11946 {
11947 // Handle defaults for labels since overriding defaults for
11948 // them can't work before the axes object is fully
11949 // constructed.
11950
11951 axes::properties& props
11952 = dynamic_cast<axes::properties&> (go.get_properties ());
11953
11954 graphics_object tgo;
11955
11956 tgo = get_object (props.get_xlabel ());
11957 tgo.override_defaults ();
11958
11959 tgo = get_object (props.get_ylabel ());
11960 tgo.override_defaults ();
11961
11962 tgo = get_object (props.get_zlabel ());
11963 tgo.override_defaults ();
11964
11965 tgo = get_object (props.get_title ());
11966 tgo.override_defaults ();
11967 }
11968
11969 // Overriding defaults will work now because the handle is valid
11970 // and we can find parent objects (not just handles).
11971 go.override_defaults ();
11972
11973 if (call_createfcn)
11974 bgo->get_properties ().execute_createfcn ();
11975
11976 // Notify graphics toolkit.
11977 if (notify_toolkit)
11978 go.initialize ();
11979
11980 return h;
11981}
11982
11984gh_manager::make_figure_handle (double val, bool notify_toolkit)
11985{
11986 graphics_handle h = val;
11987
11988 base_graphics_object *bgo = new figure (h, 0);
11989 graphics_object go (bgo);
11990
11991 m_handle_map[h] = go;
11992
11993 // Notify graphics toolkit.
11994 if (notify_toolkit)
11995 go.initialize ();
11996
11997 go.override_defaults ();
11998
11999 return h;
12000}
12001
12002void
12003gh_manager::push_figure (const graphics_handle& h)
12004{
12005 pop_figure (h);
12006
12007 m_figure_list.push_front (h);
12008}
12009
12010void
12011gh_manager::pop_figure (const graphics_handle& h)
12012{
12013 for (auto it = m_figure_list.begin (); it != m_figure_list.end (); it++)
12014 {
12015 if (*it == h)
12016 {
12017 m_figure_list.erase (it);
12018 break;
12019 }
12020 }
12021}
12022
12023class
12025{
12026public:
12027 callback_event (const graphics_handle& h, const std::string& name,
12028 const octave_value& data = Matrix (),
12029 int busyaction = base_graphics_event::QUEUE)
12030 : base_graphics_event (busyaction), m_handle (h), m_callback_name (name),
12031 m_callback (), m_callback_data (data) { }
12032
12034 const octave_value& data = Matrix (),
12035 int busyaction = base_graphics_event::QUEUE)
12036 : base_graphics_event (busyaction), m_handle (h), m_callback_name (),
12037 m_callback (cb), m_callback_data (data) { }
12038
12039 void execute (void)
12040 {
12041 gh_manager& gh_mgr
12042 = octave::__get_gh_manager__ ("callback_event::execute");
12043
12044 if (m_callback.is_defined ())
12045 gh_mgr.execute_callback (m_handle, m_callback, m_callback_data);
12046 else
12047 gh_mgr.execute_callback (m_handle, m_callback_name, m_callback_data);
12048 }
12049
12050private:
12052 : base_graphics_event (), m_handle (), m_callback_name (),
12053 m_callback_data ()
12054 { }
12055
12056private:
12058 std::string m_callback_name;
12061};
12062
12063class
12065{
12066public:
12067 mcode_event (const graphics_handle& h, const std::string& cmd,
12068 int busyaction = base_graphics_event::QUEUE)
12069 : base_graphics_event (busyaction), m_handle (h), m_mcode (cmd)
12070 { }
12071
12072 void execute (void)
12073 {
12074 if (! m_mcode.empty ())
12075 {
12076 gh_manager& gh_mgr
12077 = octave::__get_gh_manager__ ("mcode_event::execute");
12078
12079 graphics_object go = gh_mgr.get_object (m_handle);
12080
12081 if (go.valid_object ())
12082 {
12083 octave_value cb (m_mcode);
12084 gh_mgr.execute_callback (m_handle, cb);
12085 }
12086 }
12087 }
12088
12089private:
12091 : base_graphics_event (), m_handle (), m_mcode ()
12092 { }
12093
12094private:
12096 std::string m_mcode;
12097};
12098
12099class
12101{
12102public:
12103
12104 // function_event objects must be created with at least a function.
12105
12106 function_event (void) = delete;
12107
12108 function_event (graphics_event::event_fcn fcn, void *data = nullptr)
12109 : base_graphics_event (), m_function (fcn), m_function_data (data)
12110 { }
12111
12112 // No copying!
12113
12115
12116 function_event& operator = (const function_event&) = delete;
12117
12118 void execute (void)
12119 {
12120 m_function (m_function_data);
12121 }
12122
12123private:
12124
12125 graphics_event::event_fcn m_function;
12126
12128};
12129
12130class
12132{
12133public:
12134 set_event (const graphics_handle& h, const std::string& name,
12135 const octave_value& value, bool do_notify_toolkit = true,
12136 bool redraw_figure = false)
12137 : base_graphics_event (), m_handle (h), m_property_name (name),
12138 m_property_value (value), m_notify_toolkit (do_notify_toolkit),
12139 m_redraw_figure (redraw_figure)
12140 { }
12141
12142 void execute (void)
12143 {
12144 gh_manager& gh_mgr = octave::__get_gh_manager__ ("set_event::execute");
12145
12146 octave::autolock guard (gh_mgr.graphics_lock ());
12147
12148 graphics_object go = gh_mgr.get_object (m_handle);
12149
12150 if (go)
12151 {
12152 property p = go.get_properties ().get_property (m_property_name);
12153
12154 if (p.ok ())
12155 {
12156 // FIXME: figure position and outerposition properties set_xxx have
12157 // a signature that allows passing the notify_toolkit argument.
12158 // Should we change all set_xxx signatures and allow
12159 // base_properties::set to accept this also? This would allow for
12160 // the use of high level set_xxx instead of directly changing the
12161 // property value.
12162 if (go.isa ("figure") && m_property_name == "position")
12163 {
12164 figure::properties& fprops
12165 = dynamic_cast<figure::properties&> (go.get_properties ());
12166 fprops.set_position (m_property_value, m_notify_toolkit);
12167 }
12168 else if (go.isa ("figure") && m_property_name == "outerposition")
12169 {
12170 figure::properties& fprops
12171 = dynamic_cast<figure::properties&> (go.get_properties ());
12172 fprops.set_outerposition (m_property_value, m_notify_toolkit);
12173 }
12174 else
12175 p.set (m_property_value, true, m_notify_toolkit);
12176
12177 if (m_redraw_figure)
12178 {
12179 if (! go.isa ("figure"))
12180 go = go.get_ancestor ("figure");
12181
12182 if (go.valid_object ())
12183 {
12184 figure::properties& fprops
12185 = dynamic_cast<figure::properties&> (go.get_properties ());
12186 fprops.get_toolkit ().redraw_figure (go);
12187 }
12188 }
12189 }
12190 }
12191 }
12192
12193private:
12195 : base_graphics_event (), m_handle (), m_property_name (), m_property_value ()
12196 { }
12197
12198private:
12200 std::string m_property_name;
12204};
12205
12206graphics_event
12207graphics_event::create_callback_event (const graphics_handle& h,
12208 const std::string& name,
12209 const octave_value& data,
12210 int busyaction)
12211{
12212 return graphics_event (new callback_event (h, name, data, busyaction));
12213}
12214
12215graphics_event
12216graphics_event::create_callback_event (const graphics_handle& h,
12217 const octave_value& cb,
12218 const octave_value& data,
12219 int busyaction)
12220{
12221 return graphics_event (new callback_event (h, cb, data, busyaction));
12222}
12223
12224graphics_event
12225graphics_event::create_mcode_event (const graphics_handle& h,
12226 const std::string& cmd,
12227 int busyaction)
12228{
12229 return graphics_event (new mcode_event (h, cmd, busyaction));
12230}
12231
12232graphics_event
12233graphics_event::create_function_event (graphics_event::event_fcn fcn,
12234 void *data)
12235{
12236 return graphics_event (new function_event (fcn, data));
12237}
12238
12239graphics_event
12240graphics_event::create_set_event (const graphics_handle& h,
12241 const std::string& name,
12242 const octave_value& data,
12243 bool notify_toolkit, bool redraw_figure)
12244{
12245 return graphics_event (new set_event (h, name, data, notify_toolkit,
12246 redraw_figure));
12247}
12248
12249static void
12251{
12252 gh_manager& gh_mgr = octave::__get_gh_manager__ ("xset_gcbo");
12253
12254 graphics_object go = gh_mgr.get_object (0);
12255
12257 = dynamic_cast<root_figure::properties&> (go.get_properties ());
12258
12259 props.set_callbackobject (h.as_octave_value ());
12260}
12261
12262void
12263gh_manager::restore_gcbo (void)
12264{
12265 octave::autolock guard (m_graphics_lock);
12266
12267 m_callback_objects.pop_front ();
12268
12269 xset_gcbo (m_callback_objects.empty ()
12270 ? graphics_handle () : m_callback_objects.front ().get_handle ());
12271}
12272
12273void
12274gh_manager::execute_listener (const graphics_handle& h, const octave_value& l)
12275{
12277 execute_callback (h, l, octave_value ());
12278 else
12279 {
12280 octave::autolock guard (m_graphics_lock);
12281
12282 post_event (graphics_event::create_callback_event (h, l));
12283 }
12284}
12285
12286void
12287gh_manager::execute_callback (const graphics_handle& h,
12288 const octave_value& cb_arg,
12289 const octave_value& data)
12290{
12291 if (cb_arg.is_defined () && ! cb_arg.isempty ())
12292 {
12293 octave_value_list args;
12294 octave_value ov_fcn;
12295 octave_function *fcn = nullptr;
12296
12297 args(0) = h.as_octave_value ();
12298 if (data.is_defined ())
12299 args(1) = data;
12300 else
12301 args(1) = Matrix ();
12302
12303 octave::unwind_action_safe restore_gcbo_action
12304 (&gh_manager::restore_gcbo, this);
12305
12306 graphics_object go (get_object (h));
12307 if (go)
12308 {
12309 // FIXME: Is the lock necessary when we're only calling a
12310 // const "get" method?
12311 octave::autolock guard (m_graphics_lock);
12312 m_callback_objects.push_front (go);
12313 xset_gcbo (h);
12314 }
12315
12316 // Copy CB because "function_value" method is non-const.
12317 octave_value cb = cb_arg;
12318
12319 if (cb.is_function ())
12320 fcn = cb.function_value ();
12321 else if (cb.is_function_handle ())
12322 ov_fcn = cb;
12323 else if (cb.is_string ())
12324 {
12325 int status;
12326 std::string s = cb.string_value ();
12327
12328 try
12329 {
12330 m_interpreter.eval_string (s, false, status, 0);
12331 }
12332 catch (const octave::execution_exception& ee)
12333 {
12334 m_interpreter.handle_exception (ee);
12335 }
12336 }
12337 else if (cb.iscell () && cb.length () > 0
12338 && (cb.rows () == 1 || cb.columns () == 1)
12339 && (cb.cell_value ()(0).is_function ()
12340 || cb.cell_value ()(0).is_function_handle ()))
12341 {
12342 Cell c = cb.cell_value ();
12343
12344 ov_fcn = c(0);
12345
12346 for (int i = 1; i < c.numel () ; i++)
12347 args(1+i) = c(i);
12348 }
12349 else
12350 {
12351 std::string nm = cb.class_name ();
12352 error ("trying to execute non-executable object (class = %s)",
12353 nm.c_str ());
12354 }
12355
12356 if (fcn || ov_fcn.is_defined ())
12357 try
12358 {
12359 if (ov_fcn.is_defined ())
12360 octave::feval (ov_fcn, args);
12361 else
12362 octave::feval (fcn, args);
12363 }
12364 catch (const octave::execution_exception& ee)
12365 {
12366 m_interpreter.handle_exception (ee);
12367 }
12368
12369 // Redraw after interacting with a user-interface (ui*) object.
12371 {
12372 if (go)
12373 {
12374 std::string go_name
12375 = go.get_properties ().graphics_object_name ();
12376
12377 if (go_name.length () > 1
12378 && go_name[0] == 'u' && go_name[1] == 'i')
12379 {
12380 Fdrawnow (m_interpreter);
12381 Vdrawnow_requested = false;
12382 }
12383 }
12384 }
12385 }
12386}
12387
12388static int
12390{
12391 gh_manager& gh_mgr = octave::__get_gh_manager__ ("process_graphics_events");
12392
12393 return gh_mgr.process_events ();
12394}
12395
12396void
12397gh_manager::post_event (const graphics_event& e)
12398{
12399 m_event_queue.push_back (e);
12400
12402}
12403
12404void
12405gh_manager::post_callback (const graphics_handle& h, const std::string& name,
12406 const octave_value& data)
12407{
12408 octave::autolock guard (m_graphics_lock);
12409
12410 graphics_object go = get_object (h);
12411
12412 if (go.valid_object ())
12413 {
12414 caseless_str cname (name);
12415 int busyaction = base_graphics_event::QUEUE;
12416
12417 if (cname == "deletefcn" || cname == "createfcn"
12418 || cname == "closerequestfcn"
12419 || ((go.isa ("figure") || go.isa ("uipanel")
12420 || go.isa ("uibuttongroup"))
12421 && (cname == "resizefcn" || cname == "sizechangedfcn")))
12422 busyaction = base_graphics_event::INTERRUPT;
12423 else if (go.get_properties ().get_busyaction () == "cancel")
12424 busyaction = base_graphics_event::CANCEL;
12425
12426 // The "closerequestfcn" callback must be executed once the figure has
12427 // been made current. Let "close" do the job.
12428 if (cname == "closerequestfcn")
12429 {
12430 std::string cmd ("close (gcbf ());");
12431 post_event (graphics_event::create_mcode_event (h, cmd, busyaction));
12432 }
12433 else
12434 post_event (graphics_event::create_callback_event (h, name, data,
12435 busyaction));
12436 }
12437}
12438
12439void
12440gh_manager::post_function (graphics_event::event_fcn fcn, void *fcn_data)
12441{
12442 octave::autolock guard (m_graphics_lock);
12443
12444 post_event (graphics_event::create_function_event (fcn, fcn_data));
12445}
12446
12447void
12448gh_manager::post_set (const graphics_handle& h, const std::string& name,
12449 const octave_value& value, bool notify_toolkit,
12450 bool redraw_figure)
12451{
12452 octave::autolock guard (m_graphics_lock);
12453
12454 post_event (graphics_event::create_set_event (h, name, value, notify_toolkit,
12455 redraw_figure));
12456}
12457
12458int
12459gh_manager::process_events (bool force)
12460{
12461 graphics_event e;
12462 bool old_Vdrawnow_requested = Vdrawnow_requested;
12463 bool events_executed = false;
12464
12465 do
12466 {
12467 e = graphics_event ();
12468
12469 {
12470 octave::autolock guard (m_graphics_lock);
12471
12472 if (! m_event_queue.empty ())
12473 {
12474 if (m_callback_objects.empty () || force)
12475 {
12476 e = m_event_queue.front ();
12477
12478 m_event_queue.pop_front ();
12479 }
12480 else
12481 {
12482 const graphics_object& go = m_callback_objects.front ();
12483
12484 if (go.get_properties ().is_interruptible ())
12485 {
12486 e = m_event_queue.front ();
12487
12488 m_event_queue.pop_front ();
12489 }
12490 else
12491 {
12492 std::list<graphics_event>::iterator p = m_event_queue.begin ();
12493
12494 while (p != m_event_queue.end ())
12495 if (p->get_busyaction () == base_graphics_event::CANCEL)
12496 {
12497 p = m_event_queue.erase (p);
12498 }
12499 else if (p->get_busyaction ()
12500 == base_graphics_event::INTERRUPT)
12501 {
12502 e = (*p);
12503 m_event_queue.erase (p);
12504 break;
12505 }
12506 else
12507 p++;
12508 }
12509 }
12510 }
12511 }
12512
12513 if (e.ok ())
12514 {
12515 e.execute ();
12516 events_executed = true;
12517 }
12518 }
12519 while (e.ok ());
12520
12521 {
12522 octave::autolock guard (m_graphics_lock);
12523
12524 if (m_event_queue.empty () && m_event_processing == 0)
12526 }
12527
12528 if (events_executed)
12530
12531 if (Vdrawnow_requested && ! old_Vdrawnow_requested)
12532 {
12533 Fdrawnow (m_interpreter);
12534
12535 Vdrawnow_requested = false;
12536 }
12537
12538 return 0;
12539}
12540
12541
12542/*
12543## Test interruptible/busyaction properties
12544%!function cb (h, ~)
12545%! setappdata (gcbf (), "cb_exec", [getappdata(gcbf (), "cb_exec") h]);
12546%! drawnow ();
12547%! setappdata (gcbf (), "cb_exec", [getappdata(gcbf (), "cb_exec") h]);
12548%!endfunction
12549%!
12550%!testif HAVE_OPENGL, HAVE_QT; have_window_system () && any (strcmp ("qt", available_graphics_toolkits ()))
12551%! hf = figure ("visible", "off", "resizefcn", @cb);
12552%! graphics_toolkit (hf, "qt");
12553%! unwind_protect
12554%! ## Default
12555%! hui1 = uicontrol ("parent", hf, "interruptible", "on", "callback", @cb);
12556%! hui2 = uicontrol ("parent", hf, "busyaction", "queue", "callback", @cb);
12557%! hui3 = uicontrol ("parent", hf, "busyaction", "queue", "callback", @cb);
12558%! __go_post_callback__ (hui1, "callback");
12559%! __go_post_callback__ (hui2, "callback");
12560%! __go_post_callback__ (hui3, "callback");
12561%!
12562%! assert (getappdata (hf, "cb_exec"), []);
12563%! drawnow ();
12564%! assert (getappdata (hf, "cb_exec"), [hui1 hui2 hui3 hui3 hui2 hui1]);
12565%!
12566%! ## Interruptible off
12567%! setappdata (hf, "cb_exec", []);
12568%! set (hui1, "interruptible", "off");
12569%! __go_post_callback__ (hui1, "callback");
12570%! __go_post_callback__ (hui2, "callback");
12571%! __go_post_callback__ (hui3, "callback");
12572%! drawnow ();
12573%! assert (getappdata (hf, "cb_exec"), [hui1 hui1 hui2 hui3 hui3 hui2]);
12574%!
12575%! ## "resizefcn" callback interrupts regardless of interruptible property
12576%! setappdata (hf, "cb_exec", []);
12577%! __go_post_callback__ (hui1, "callback");
12578%! __go_post_callback__ (hf, "resizefcn");
12579%! drawnow ();
12580%! assert (getappdata (hf, "cb_exec"), [hui1 hf hf hui1]);
12581%!
12582%! ## test "busyaction" "cancel"
12583%! setappdata (hf, "cb_exec", []);
12584%! set (hui2, "busyaction", "cancel");
12585%! __go_post_callback__ (hui1, "callback");
12586%! __go_post_callback__ (hui2, "callback");
12587%! __go_post_callback__ (hui3, "callback");
12588%! __go_post_callback__ (hf, "resizefcn");
12589%! drawnow ();
12590%! assert (getappdata (hf, "cb_exec"), [hui1 hf hui3 hui3 hf hui1]);
12591%! unwind_protect_cleanup
12592%! close (hf)
12593%! end_unwind_protect
12594*/
12595
12596void
12597gh_manager::enable_event_processing (bool enable)
12598{
12599 octave::autolock guard (m_graphics_lock);
12600
12601 if (enable)
12602 {
12603 m_event_processing++;
12604
12606 }
12607 else
12608 {
12609 m_event_processing--;
12610
12611 if (m_event_queue.empty () && m_event_processing == 0)
12613 }
12614}
12615
12616property_list::plist_map_type
12617root_figure::init_factory_properties (void)
12618{
12619 property_list::plist_map_type plist_map;
12620
12621 plist_map["figure"] = figure::properties::factory_defaults ();
12622 plist_map["axes"] = axes::properties::factory_defaults ();
12623 plist_map["line"] = line::properties::factory_defaults ();
12624 plist_map["text"] = text::properties::factory_defaults ();
12625 plist_map["image"] = image::properties::factory_defaults ();
12626 plist_map["patch"] = patch::properties::factory_defaults ();
12627 plist_map["scatter"] = scatter::properties::factory_defaults ();
12628 plist_map["surface"] = surface::properties::factory_defaults ();
12629 plist_map["light"] = light::properties::factory_defaults ();
12630 plist_map["hggroup"] = hggroup::properties::factory_defaults ();
12631 plist_map["uimenu"] = uimenu::properties::factory_defaults ();
12632 plist_map["uicontrol"] = uicontrol::properties::factory_defaults ();
12633 plist_map["uibuttongroup"] = uibuttongroup::properties::factory_defaults ();
12634 plist_map["uipanel"] = uipanel::properties::factory_defaults ();
12635 plist_map["uicontextmenu"] = uicontextmenu::properties::factory_defaults ();
12636 plist_map["uitoolbar"] = uitoolbar::properties::factory_defaults ();
12637 plist_map["uipushtool"] = uipushtool::properties::factory_defaults ();
12638 plist_map["uitoggletool"] = uitoggletool::properties::factory_defaults ();
12639
12640 return plist_map;
12641}
12642
12643// ---------------------------------------------------------------------
12644
12645DEFMETHOD (ishghandle, interp, args, ,
12646 doc: /* -*- texinfo -*-
12647@deftypefn {} {} ishghandle (@var{h})
12648Return true if @var{h} is a graphics handle and false otherwise.
12649
12650@var{h} may also be a matrix of handles in which case a logical array is
12651returned that is true where the elements of @var{h} are graphics handles and
12652false where they are not.
12653@seealso{isgraphics, isaxes, isfigure, ishandle}
12654@end deftypefn */)
12655{
12656 gh_manager& gh_mgr = interp.get_gh_manager ();
12657
12658 octave::autolock guard (gh_mgr.graphics_lock ());
12659
12660 if (args.length () != 1)
12661 print_usage ();
12662
12663 return ovl (ishghandle (args(0)));
12664}
12665
12666/*
12667%!test
12668%! hf = figure ("visible", "off");
12669%! unwind_protect
12670%! assert (ishghandle (hf));
12671%! assert (! ishghandle (-hf));
12672%! ax = gca ();
12673%! l = line ();
12674%! assert (ishghandle (ax));
12675%! assert (! ishghandle (-ax));
12676%! assert (ishghandle ([l, -1, ax, hf]), logical ([1, 0, 1, 1]));
12677%! assert (ishghandle ([l, -1, ax, hf]'), logical ([1, 0, 1, 1]'));
12678%! unwind_protect_cleanup
12679%! close (hf);
12680%! end_unwind_protect
12681
12682%!assert (ishghandle ([-1 0]), [false true])
12683*/
12684
12685static bool
12687{
12688 gh_manager& gh_mgr = octave::__get_gh_manager__ ("is_handle_visible");
12689
12690 return h.ok () && gh_mgr.is_handle_visible (h);
12691}
12692
12693static bool
12695{
12696 gh_manager& gh_mgr = octave::__get_gh_manager__ ("is_handle_visible");
12697
12698 return is_handle_visible (gh_mgr.lookup (val));
12699}
12700
12701static octave_value
12703{
12704 octave_value retval = false;
12705
12706 if (val.is_real_scalar () && is_handle_visible (val.double_value ()))
12707 retval = true;
12708 else if (val.isnumeric () && val.isreal ())
12709 {
12710 const NDArray handles = val.array_value ();
12711
12712 boolNDArray result (handles.dims ());
12713
12714 for (octave_idx_type i = 0; i < handles.numel (); i++)
12715 result.xelem (i) = is_handle_visible (handles(i));
12716
12717 retval = result;
12718 }
12719
12720 return retval;
12721}
12722
12723DEFUN (__is_handle_visible__, args, ,
12724 doc: /* -*- texinfo -*-
12725@deftypefn {} {} __is_handle_visible__ (@var{h})
12726Undocumented internal function.
12727@end deftypefn */)
12728{
12729 if (args.length () != 1)
12730 print_usage ();
12731
12732 return ovl (is_handle_visible (args(0)));
12733}
12734
12735DEFMETHOD (reset, interp, args, ,
12736 doc: /* -*- texinfo -*-
12737@deftypefn {} {} reset (@var{h})
12738Reset the properties of the graphic object @var{h} to their default values.
12739
12740For figures, the properties @qcode{"position"}, @qcode{"units"},
12741@qcode{"windowstyle"}, and @qcode{"paperunits"} are not affected.
12742For axes, the properties @qcode{"position"} and @qcode{"units"} are
12743not affected.
12744
12745The input @var{h} may also be a vector of graphic handles in which case
12746each individual object will be reset.
12747@seealso{cla, clf, newplot}
12748@end deftypefn */)
12749{
12750 if (args.length () != 1)
12751 print_usage ();
12752
12753 // get vector of graphics handles
12754 ColumnVector hcv = args(0).xvector_value ("reset: H must be a graphics handle");
12755
12756 gh_manager& gh_mgr = interp.get_gh_manager ();
12757
12758 // loop over graphics objects
12759 for (octave_idx_type n = 0; n < hcv.numel (); n++)
12760 gh_mgr.get_object (hcv(n)).reset_default_properties ();
12761
12762 Vdrawnow_requested = true;
12763
12764 return ovl ();
12765}
12766
12767/*
12768
12769%!test # line object
12770%! hf = figure ("visible", "off");
12771%! unwind_protect
12772%! tol = 20 * eps;
12773%! hax = axes ("defaultlinelinewidth", 3);
12774%!
12775%! hli = line (1:10, 1:10, 1:10, "marker", "o",
12776%! "markerfacecolor", "b", "linestyle", ":");
12777%!
12778%! reset (hli);
12779%! assert (get (hli, "marker"), get (0, "defaultlinemarker"));
12780%! assert (get (hli, "markerfacecolor"),
12781%! get (0, "defaultlinemarkerfacecolor"));
12782%! assert (get (hli, "linestyle"), get (0, "defaultlinelinestyle"));
12783%! assert (get (hli, "linewidth"), 3, tol); # parent axes defaults
12784%!
12785%! unwind_protect_cleanup
12786%! close (hf);
12787%! end_unwind_protect
12788
12789%!test # patch object
12790%! hf = figure ("visible", "off");
12791%! unwind_protect
12792%! tol = 20 * eps;
12793%! t1 = (1/16:1/8:1)' * 2*pi;
12794%! t2 = ((1/16:1/16:1)' + 1/32) * 2*pi;
12795%! x1 = sin (t1) - 0.8;
12796%! y1 = cos (t1);
12797%! x2 = sin (t2) + 0.8;
12798%! y2 = cos (t2);
12799%! vert = [x1, y1; x2, y2];
12800%! fac = [1:8,NaN(1,8);9:24];
12801%! hpa = patch ("Faces",fac, "Vertices",vert, "FaceColor","r");
12802%!
12803%! reset (hpa);
12804%! assert (get (hpa, "faces"), get (0, "defaultpatchfaces"), tol);
12805%! assert (get (hpa, "vertices"), get (0, "defaultpatchvertices"), tol);
12806%! assert (get (hpa, "facevertexcdata"),
12807%! get (0, "defaultpatchfacevertexcdata"), tol);
12808%! unwind_protect_cleanup
12809%! close (hf);
12810%! end_unwind_protect
12811
12812%!test # surface object
12813%! hf = figure ("visible", "off");
12814%! unwind_protect
12815%! tol = 20 * eps;
12816%! hsu = surface (peaks, "edgecolor", "none");
12817%!
12818%! reset (hsu);
12819%! assert (get (hsu, "xdata"), get (0, "defaultsurfacexdata"), tol);
12820%! assert (get (hsu, "ydata"), get (0, "defaultsurfaceydata"), tol);
12821%! assert (get (hsu, "zdata"), get (0, "defaultsurfacezdata"), tol);
12822%! assert (get (hsu, "edgecolor"), get (0, "defaultsurfaceedgecolor"), tol);
12823%! unwind_protect_cleanup
12824%! close (hf);
12825%! end_unwind_protect
12826
12827%!test # image object
12828%! hf = figure ("visible", "off");
12829%! unwind_protect
12830%! tol = 20 * eps;
12831%! him = image (rand (10,10), "cdatamapping", "scaled");
12832%!
12833%! reset (him);
12834%! assert (get (him, "cdata"), get (0, "defaultimagecdata"), tol);
12835%! assert (get (him, "cdatamapping"),
12836%! get (0, "defaultimagecdatamapping"), tol);
12837%! unwind_protect_cleanup
12838%! close (hf);
12839%! end_unwind_protect
12840
12841%!test # text object
12842%! hf = figure ("visible", "off");
12843%! unwind_protect
12844%! tol = 20 * eps;
12845%! hte = text (5, 5, "Hi!", "fontsize", 20 ,"color", "r");
12846%!
12847%! reset (hte);
12848%! assert (get (hte, "position"), get (0, "defaulttextposition"), tol);
12849%! assert (get (hte, "fontsize"), get (0, "defaulttextfontsize"), tol);
12850%! assert (get (hte, "color"), get (0, "defaulttextcolor"), tol);
12851%! unwind_protect_cleanup
12852%! close (hf);
12853%! end_unwind_protect
12854
12855%!test # axes object
12856%! hf = figure ("visible", "off");
12857%! unwind_protect
12858%! tol = 20 * eps;
12859%! pos = get (0, "defaultaxesposition") * .5;
12860%! hax = axes ("linewidth", 2, "position", pos);
12861%! title ("Reset me, please!");
12862%!
12863%! reset (hax);
12864%! assert (get (hax, "linewidth"), get (0, "defaultaxeslinewidth"), tol);
12865%! assert (get (hax, "position"), pos, tol); # axes position is unchanged
12866%! assert (get (hax, "default"), struct ()); # no more axes' defaults
12867%! assert (get (get (hax, "title"), "string"), "");
12868%! unwind_protect_cleanup
12869%! close (hf);
12870%! end_unwind_protect
12871
12872%!test # root object
12873%! set (0, "defaultfigurevisible", "off");
12874%! hf = figure ("visible", "off", "paperunits", "centimeters",
12875%! "papertype", "a4");
12876%! unwind_protect
12877%! reset (hf);
12878%! assert (get (hf, "papertype"), get (0, "defaultfigurepapertype"));
12879%! assert (get (hf, "paperunits"), "centimeters"); # paperunits is unchanged
12880%! assert (get (hf, "visible"), get (0, "defaultfigurevisible"));
12881%! unwind_protect_cleanup
12882%! close (hf);
12883%! set (0, "defaultfigurevisible", "remove");
12884%! end_unwind_protect
12885
12886*/
12887
12888DEFMETHOD (set, interp, args, nargout,
12889 doc: /* -*- texinfo -*-
12890@deftypefn {} {} set (@var{h}, @var{property}, @var{value}, @dots{})
12891@deftypefnx {} {} set (@var{h}, @var{properties}, @var{values})
12892@deftypefnx {} {} set (@var{h}, @var{pv})
12893@deftypefnx {} {@var{value_list} =} set (@var{h}, @var{property})
12894@deftypefnx {} {@var{all_value_list} =} set (@var{h})
12895Set named property values for the graphics handle (or vector of graphics
12896handles) @var{h}.
12897
12898There are three ways to give the property names and values:
12899
12900@itemize
12901@item as a comma separated list of @var{property}, @var{value} pairs
12902
12903Here, each @var{property} is a string containing the property name, each
12904@var{value} is a value of the appropriate type for the property.
12905
12906@item as a cell array of strings @var{properties} containing property names
12907and a cell array @var{values} containing property values.
12908
12909In this case, the number of columns of @var{values} must match the number of
12910elements in @var{properties}. The first column of @var{values} contains
12911values for the first entry in @var{properties}, etc. The number of rows of
12912@var{values} must be 1 or match the number of elements of @var{h}. In the
12913first case, each handle in @var{h} will be assigned the same values. In the
12914latter case, the first handle in @var{h} will be assigned the values from
12915the first row of @var{values} and so on.
12916
12917@item as a structure array @var{pv}
12918
12919Here, the field names of @var{pv} represent the property names, and the
12920field values give the property values. In contrast to the previous case,
12921all elements of @var{pv} will be set in all handles in @var{h} independent
12922of the dimensions of @var{pv}.
12923@end itemize
12924
12925@code{set} is also used to query the list of values a named property will
12926take. @code{@var{clist} = set (@var{h}, "property")} will return the list
12927of possible values for @qcode{"property"} in the cell list @var{clist}.
12928If no output variable is used then the list is formatted and printed to the
12929screen.
12930
12931If no property is specified (@code{@var{slist} = set (@var{h})}) then a
12932structure @var{slist} is returned where the fieldnames are the properties of
12933the object @var{h} and the fields are the list of possible values for each
12934property. If no output variable is used then the list is formatted and
12935printed to the screen.
12936
12937For example,
12938
12939@example
12940@group
12941hf = figure ();
12942set (hf, "paperorientation")
12943@result{} [ landscape | @{portrait@} ]
12944@end group
12945@end example
12946
12947@noindent
12948shows the paperorientation property can take two values with the default
12949being @qcode{"portrait"}.
12950@seealso{get}
12951@end deftypefn */)
12952{
12953 gh_manager& gh_mgr = interp.get_gh_manager ();
12954
12955 octave::autolock guard (gh_mgr.graphics_lock ());
12956
12957 int nargin = args.length ();
12958
12959 if (nargin == 0)
12960 print_usage ();
12961
12962 octave_value retval;
12963
12964 // get vector of graphics handles
12965 ColumnVector hcv = args(0).xvector_value ("set: H must be a graphics handle");
12966
12967 bool request_drawnow = false;
12968
12969 // loop over graphics objects
12970 for (octave_idx_type n = 0; n < hcv.numel (); n++)
12971 {
12972 graphics_object go = gh_mgr.get_object (hcv(n));
12973
12974 if (! go)
12975 error ("set: invalid handle (= %g)", hcv(n));
12976
12977 if (nargin == 3 && args(1).iscellstr () && args(2).iscell ())
12978 {
12979 if (args(2).cell_value ().rows () == 1)
12980 go.set (args(1).cellstr_value (), args(2).cell_value (), 0);
12981 else if (hcv.numel () == args(2).cell_value ().rows ())
12982 go.set (args(1).cellstr_value (), args(2).cell_value (), n);
12983 else
12984 error ("set: number of graphics handles must match number of "
12985 "value rows (%" OCTAVE_IDX_TYPE_FORMAT " != "
12986 "%" OCTAVE_IDX_TYPE_FORMAT ")",
12987 hcv.numel (), args(2).cell_value ().rows ());
12988 }
12989 else if (nargin == 2 && args(1).isstruct ())
12990 go.set (args(1).map_value ());
12991 else if (nargin == 2 && args(1).is_string ())
12992 {
12993 std::string property = args(1).string_value ();
12994 std::transform (property.begin (), property.end (),
12995 property.begin (), tolower);
12996
12997 octave_map pmap = go.values_as_struct ();
12998
12999 if (go.has_readonly_property (property))
13000 if (nargout != 0)
13001 retval = Matrix ();
13002 else
13003 octave_stdout << "set: " << property
13004 <<" is read-only" << std::endl;
13005 else if (pmap.isfield (property))
13006 {
13007 if (nargout != 0)
13008 retval = pmap.getfield (property)(0);
13009 else
13010 {
13011 std::string s = go.value_as_string (property);
13012
13013 octave_stdout << s;
13014 }
13015 }
13016 else
13017 error (R"(set: unknown property "%s")", property.c_str ());
13018 }
13019 else if (nargin == 1)
13020 {
13021 if (nargout != 0)
13022 retval = go.values_as_struct ();
13023 else
13024 {
13025 std::string s = go.values_as_string ();
13026
13027 octave_stdout << s;
13028 }
13029 }
13030 else
13031 {
13032 go.set (args.splice (0, 1));
13033 }
13034
13035 request_drawnow = true;
13036 }
13037
13038 if (request_drawnow)
13039 Vdrawnow_requested = true;
13040
13041 return retval;
13042}
13043
13044static std::string
13046{
13047 std::string retval;
13048
13049 gh_manager& gh_mgr = octave::__get_gh_manager__ ("get_graphics_object_type");
13050
13051 graphics_object go = gh_mgr.get_object (val);
13052
13053 if (! go)
13054 error ("get: invalid handle (= %g)", val);
13055
13056 return go.type ();
13057}
13058
13059DEFMETHOD (get, interp, args, ,
13060 doc: /* -*- texinfo -*-
13061@deftypefn {} {@var{val} =} get (@var{h})
13062@deftypefnx {} {@var{val} =} get (@var{h}, @var{p})
13063Return the value of the named property @var{p} from the graphics handle
13064@var{h}.
13065
13066If @var{p} is omitted, return the complete property list for @var{h}.
13067
13068If @var{h} is a vector, return a cell array including the property values or
13069lists respectively.
13070@seealso{set}
13071@end deftypefn */)
13072{
13073 gh_manager& gh_mgr = interp.get_gh_manager ();
13074
13075 octave::autolock guard (gh_mgr.graphics_lock ());
13076
13077 int nargin = args.length ();
13078
13079 if (nargin < 1 || nargin > 2)
13080 print_usage ();
13081
13082 if (args(0).isempty ())
13083 return ovl (Matrix ());
13084
13085 ColumnVector hcv = args(0).xvector_value ("get: H must be a graphics handle");
13086
13087 octave_idx_type hcv_len = hcv.numel ();
13088
13089 if (nargin == 1 && hcv_len > 1)
13090 {
13091 std::string typ0 = get_graphics_object_type (hcv(0));
13092
13093 for (octave_idx_type n = 1; n < hcv_len; n++)
13094 {
13095 std::string typ = get_graphics_object_type (hcv(n));
13096
13097 if (typ != typ0)
13098 error ("get: vector of handles must all have the same type");
13099 }
13100 }
13101
13102 octave_value retval;
13103 Cell vals;
13104 bool use_cell_format = false;
13105
13106 if (nargin > 1 && args(1).iscellstr ())
13107 {
13108 Array<std::string> plist = args(1).cellstr_value ();
13109
13110 octave_idx_type plen = plist.numel ();
13111
13112 use_cell_format = true;
13113
13114 vals.resize (dim_vector (hcv_len, plen));
13115
13116 for (octave_idx_type n = 0; n < hcv_len; n++)
13117 {
13118 graphics_object go = gh_mgr.get_object (hcv(n));
13119
13120 if (! go)
13121 error ("get: invalid handle (= %g)", hcv(n));
13122
13123 for (octave_idx_type m = 0; m < plen; m++)
13124 {
13125 caseless_str property = plist(m);
13126
13127 vals(n, m) = go.get (property);
13128 }
13129 }
13130 }
13131 else
13132 {
13133 caseless_str property;
13134
13135 if (nargin > 1)
13136 property = args(1).xstring_value ("get: second argument must be property name or cell array of property names");
13137
13138 vals.resize (dim_vector (hcv_len, 1));
13139
13140 for (octave_idx_type n = 0; n < hcv_len; n++)
13141 {
13142 graphics_object go = gh_mgr.get_object (hcv(n));
13143
13144 if (! go)
13145 error ("get: invalid handle (= %g)", hcv(n));
13146
13147 if (nargin == 1)
13148 vals(n) = go.get ();
13149 else
13150 vals(n) = go.get (property);
13151 }
13152 }
13153
13154 if (use_cell_format)
13155 retval = vals;
13156 else
13157 {
13158 octave_idx_type vals_len = vals.numel ();
13159
13160 if (vals_len == 0)
13161 retval = Matrix ();
13162 else if (vals_len == 1)
13163 retval = vals(0);
13164 else if (vals_len > 1 && nargin == 1)
13165 {
13166 OCTAVE_LOCAL_BUFFER (octave_scalar_map, tmp, vals_len);
13167
13168 for (octave_idx_type n = 0; n < vals_len; n++)
13169 tmp[n] = vals(n).scalar_map_value ();
13170
13171 retval = octave_map::cat (0, vals_len, tmp);
13172 }
13173 else
13174 retval = vals;
13175 }
13176
13177 return retval;
13178}
13179
13180/*
13181%!assert (get (findobj (0, "Tag", "nonexistenttag"), "nonexistentproperty"), [])
13182*/
13183
13184// Return all properties from the graphics handle @var{h}.
13185// If @var{h} is a vector, return a cell array including the
13186// property values or lists respectively.
13187
13188DEFMETHOD (__get__, interp, args, ,
13189 doc: /* -*- texinfo -*-
13190@deftypefn {} {} __get__ (@var{h})
13191Undocumented internal function.
13192@end deftypefn */)
13193{
13194 gh_manager& gh_mgr = interp.get_gh_manager ();
13195
13196 octave::autolock guard (gh_mgr.graphics_lock ());
13197
13198 if (args.length () != 1)
13199 print_usage ();
13200
13201 ColumnVector hcv = args(0).xvector_value ("get: H must be a graphics handle");
13202
13203 octave_idx_type hcv_len = hcv.numel ();
13204
13205 Cell vals (dim_vector (hcv_len, 1));
13206
13207// vals.resize (dim_vector (hcv_len, 1));
13208
13209 for (octave_idx_type n = 0; n < hcv_len; n++)
13210 {
13211 graphics_object go = gh_mgr.get_object (hcv(n));
13212
13213 if (! go)
13214 error ("get: invalid handle (= %g)", hcv(n));
13215
13216 // Disable "Octave:deprecated-property" warnings
13217 int state = toggle_warn ("Octave:deprecated-property", false);
13218
13219 vals(n) = go.get (true);
13220
13221 toggle_warn ("Octave:deprecated-property", true, state);
13222 }
13223
13224 octave_idx_type vals_len = vals.numel ();
13225
13226 if (vals_len > 1)
13227 return ovl (vals);
13228 else if (vals_len == 1)
13229 return ovl (vals(0));
13230 else
13231 return ovl ();
13232}
13233
13234static octave_value
13235make_graphics_object (const std::string& go_name,
13236 bool integer_figure_handle,
13237 const octave_value_list& args)
13238{
13239 octave_value retval;
13240
13242
13243 octave_value_list xargs = args.splice (0, 1);
13244
13245 caseless_str p ("parent");
13246
13247 // Remove all "parent" property overrides of the first argument to function
13248 // and accept only the last one (bug #55322).
13249 for (int i = 0; i < xargs.length (); i += 2)
13250 {
13251 if (xargs(i).is_string () && p.compare (xargs(i).string_value ()))
13252 {
13253 if (i >= (xargs.length () - 1))
13254 error ("__go_%s__: missing value for parent property",
13255 go_name.c_str ());
13256
13257 val = xargs(i+1).double_value ();
13258
13259 xargs = xargs.splice (i, 2);
13260 i -= 2;
13261 }
13262 }
13263
13264 if (octave::math::isnan (val))
13265 val = args(0).xdouble_value ("__go_%s__: invalid parent", go_name.c_str ());
13266
13267 gh_manager& gh_mgr = octave::__get_gh_manager__ ("make_graphics_object");
13268
13269 graphics_handle parent = gh_mgr.lookup (val);
13270
13271 if (! parent.ok ())
13272 error ("__go_%s__: invalid parent", go_name.c_str ());
13273
13275
13276 try
13277 {
13278 h = gh_mgr.make_graphics_handle (go_name, parent,
13279 integer_figure_handle, false, false);
13280 }
13281 catch (octave::execution_exception& ee)
13282 {
13283 error (ee, "__go_%s__: %s, unable to create graphics handle",
13284 go_name.c_str (), ee.message ().c_str ());
13285 }
13286
13287 try
13288 {
13289 xset (h, xargs);
13290 }
13291 catch (octave::execution_exception& ee)
13292 {
13294 error (ee, "__go_%s__: %s, unable to create graphics handle",
13295 go_name.c_str (), ee.message ().c_str ());
13296 }
13297
13298 adopt (parent, h);
13299
13300 xcreatefcn (h);
13301 xinitialize (h);
13302
13303 retval = h.value ();
13304
13305 Vdrawnow_requested = true;
13306
13307 return retval;
13308}
13309
13310DEFMETHOD (__go_figure__, interp, args, ,
13311 doc: /* -*- texinfo -*-
13312@deftypefn {} {} __go_figure__ (@var{fignum})
13313Undocumented internal function.
13314@end deftypefn */)
13315{
13316 gh_manager& gh_mgr = interp.get_gh_manager ();
13317
13318 octave::autolock guard (gh_mgr.graphics_lock ());
13319
13320 if (args.length () == 0)
13321 print_usage ();
13322
13323 double val = args(0).xdouble_value ("__go_figure__: figure number must be a double value");
13324
13325 octave_value retval;
13326
13327 if (isfigure (val))
13328 {
13329 graphics_handle h = gh_mgr.lookup (val);
13330
13331 xset (h, args.splice (0, 1));
13332
13333 retval = h.value ();
13334 }
13335 else
13336 {
13337 bool int_fig_handle = true;
13338
13339 octave_value_list xargs = args.splice (0, 1);
13340
13342
13343 if (octave::math::isnan (val))
13344 {
13345 caseless_str pname ("integerhandle");
13346
13347 for (int i = 0; i < xargs.length (); i++)
13348 {
13349 if (xargs(i).is_string ()
13350 && pname.compare (xargs(i).string_value ()))
13351 {
13352 if (i < (xargs.length () - 1))
13353 {
13354 std::string pval = xargs(i+1).string_value ();
13355
13356 caseless_str on ("on");
13357 int_fig_handle = on.compare (pval);
13358 xargs = xargs.splice (i, 2);
13359
13360 break;
13361 }
13362 }
13363 }
13364
13365 h = gh_mgr.make_graphics_handle ("figure", 0, int_fig_handle,
13366 false, false);
13367
13368 if (! int_fig_handle)
13369 {
13370 // We need to initialize the integerhandle property
13371 // without calling the set_integerhandle method,
13372 // because doing that will generate a new handle value...
13373 graphics_object go = gh_mgr.get_object (h);
13374 go.get_properties ().init_integerhandle ("off");
13375 }
13376 }
13377 else if (val > 0 && octave::math::x_nint (val) == val)
13378 h = gh_mgr.make_figure_handle (val, false);
13379
13380 if (! h.ok ())
13381 error ("__go_figure__: failed to create figure handle");
13382
13383 try
13384 {
13385 xset (h, xargs);
13386 }
13387 catch (octave::execution_exception& ee)
13388 {
13390 error (ee, "__go_figure__: unable to create figure handle");
13391 }
13392
13393 adopt (0, h);
13394
13395 gh_mgr.push_figure (h);
13396
13397 xcreatefcn (h);
13398 xinitialize (h);
13399
13400 retval = h.value ();
13401 }
13402
13403 return retval;
13404}
13405
13406#define GO_BODY(TYPE) \
13407 gh_manager& gh_mgr = interp.get_gh_manager (); \
13408 \
13409 octave::autolock guard (gh_mgr.graphics_lock ()); \
13410 \
13411 if (args.length () == 0) \
13412 print_usage (); \
13413 \
13414 return octave_value (make_graphics_object (#TYPE, false, args)); \
13415
13416int
13417calc_dimensions (const graphics_object& go)
13418{
13419 int nd = 2;
13420
13421 if (go.isa ("surface"))
13422 nd = 3;
13423 else if ((go.isa ("line") || go.isa ("patch") || go.isa ("scatter"))
13424 && ! go.get ("zdata").isempty ())
13425 nd = 3;
13426 else
13427 {
13428 Matrix kids = go.get_properties ().get_children ();
13429
13430 gh_manager& gh_mgr = octave::__get_gh_manager__ ("calc_dimensions");
13431
13432 for (octave_idx_type i = 0; i < kids.numel (); i++)
13433 {
13434 graphics_handle hkid = gh_mgr.lookup (kids(i));
13435
13436 if (hkid.ok ())
13437 {
13438 const graphics_object& kid = gh_mgr.get_object (hkid);
13439
13440 if (kid.valid_object ())
13441 nd = calc_dimensions (kid);
13442
13443 if (nd == 3)
13444 break;
13445 }
13446 }
13447 }
13448
13449 return nd;
13450}
13451
13452DEFMETHOD (__calc_dimensions__, interp, args, ,
13453 doc: /* -*- texinfo -*-
13454@deftypefn {} {} __calc_dimensions__ (@var{axes})
13455Internal function.
13456
13457Determine the number of dimensions in a graphics object, either 2 or 3.
13458@end deftypefn */)
13459{
13460 gh_manager& gh_mgr = interp.get_gh_manager ();
13461
13462 octave::autolock guard (gh_mgr.graphics_lock ());
13463
13464 if (args.length () != 1)
13465 print_usage ();
13466
13467 double h = args(0).xdouble_value ("__calc_dimensions__: first argument must be a graphics handle");
13468
13469 return ovl (calc_dimensions (gh_mgr.get_object (h)));
13470}
13471
13472DEFMETHOD (__go_axes__, interp, args, ,
13473 doc: /* -*- texinfo -*-
13474@deftypefn {} {} __go_axes__ (@var{parent})
13475Undocumented internal function.
13476@end deftypefn */)
13477{
13478 GO_BODY (axes);
13479}
13480
13481DEFMETHOD (__go_line__, interp, args, ,
13482 doc: /* -*- texinfo -*-
13483@deftypefn {} {} __go_line__ (@var{parent})
13484Undocumented internal function.
13485@end deftypefn */)
13486{
13487 GO_BODY (line);
13488}
13489
13490DEFMETHOD (__go_text__, interp, args, ,
13491 doc: /* -*- texinfo -*-
13492@deftypefn {} {} __go_text__ (@var{parent})
13493Undocumented internal function.
13494@end deftypefn */)
13495{
13496 GO_BODY (text);
13497}
13498
13499DEFMETHOD (__go_image__, interp, args, ,
13500 doc: /* -*- texinfo -*-
13501@deftypefn {} {} __go_image__ (@var{parent})
13502Undocumented internal function.
13503@end deftypefn */)
13504{
13505 GO_BODY (image);
13506}
13507
13508DEFMETHOD (__go_surface__, interp, args, ,
13509 doc: /* -*- texinfo -*-
13510@deftypefn {} {} __go_surface__ (@var{parent})
13511Undocumented internal function.
13512@end deftypefn */)
13513{
13514 GO_BODY (surface);
13515}
13516
13517DEFMETHOD (__go_patch__, interp, args, ,
13518 doc: /* -*- texinfo -*-
13519@deftypefn {} {} __go_patch__ (@var{parent})
13520Undocumented internal function.
13521@end deftypefn */)
13522{
13523 GO_BODY (patch);
13524}
13525
13526DEFMETHOD (__go_scatter__, interp, args, ,
13527 doc: /* -*- texinfo -*-
13528@deftypefn {} {} __go_scatter__ (@var{parent})
13529Undocumented internal function.
13530@end deftypefn */)
13531{
13532 GO_BODY (scatter);
13533}
13534
13535DEFMETHOD (__go_light__, interp, args, ,
13536 doc: /* -*- texinfo -*-
13537@deftypefn {} {} __go_light__ (@var{parent})
13538Undocumented internal function.
13539@end deftypefn */)
13540{
13541 GO_BODY (light);
13542}
13543
13544DEFMETHOD (__go_hggroup__, interp, args, ,
13545 doc: /* -*- texinfo -*-
13546@deftypefn {} {} __go_hggroup__ (@var{parent})
13547Undocumented internal function.
13548@end deftypefn */)
13549{
13550 GO_BODY (hggroup);
13551}
13552
13553DEFMETHOD (__go_uimenu__, interp, args, ,
13554 doc: /* -*- texinfo -*-
13555@deftypefn {} {} __go_uimenu__ (@var{parent})
13556Undocumented internal function.
13557@end deftypefn */)
13558{
13559 GO_BODY (uimenu);
13560}
13561
13562DEFMETHOD (__go_uicontrol__, interp, args, ,
13563 doc: /* -*- texinfo -*-
13564@deftypefn {} {} __go_uicontrol__ (@var{parent})
13565Undocumented internal function.
13566@end deftypefn */)
13567{
13568 GO_BODY (uicontrol);
13569}
13570
13571DEFMETHOD (__go_uibuttongroup__, interp, args, ,
13572 doc: /* -*- texinfo -*-
13573@deftypefn {} {} __go_uibuttongroup__ (@var{parent})
13574Undocumented internal function.
13575@end deftypefn */)
13576{
13577 GO_BODY (uibuttongroup);
13578}
13579
13580DEFMETHOD (__go_uipanel__, interp, args, ,
13581 doc: /* -*- texinfo -*-
13582@deftypefn {} {} __go_uipanel__ (@var{parent})
13583Undocumented internal function.
13584@end deftypefn */)
13585{
13586 GO_BODY (uipanel);
13587}
13588
13589DEFMETHOD (__go_uicontextmenu__, interp, args, ,
13590 doc: /* -*- texinfo -*-
13591@deftypefn {} {} __go_uicontextmenu__ (@var{parent})
13592Undocumented internal function.
13593@end deftypefn */)
13594{
13595 GO_BODY (uicontextmenu);
13596}
13597
13598DEFMETHOD (__go_uitable__, interp, args, ,
13599 doc: /* -*- texinfo -*-
13600@deftypefn {} {} __go_uitable__ (@var{parent})
13601Undocumented internal function.
13602@end deftypefn */)
13603{
13604 GO_BODY (uitable);
13605}
13606
13607DEFMETHOD (__go_uitoolbar__, interp, args, ,
13608 doc: /* -*- texinfo -*-
13609@deftypefn {} {} __go_uitoolbar__ (@var{parent})
13610Undocumented internal function.
13611@end deftypefn */)
13612{
13613 GO_BODY (uitoolbar);
13614}
13615
13616DEFMETHOD (__go_uipushtool__, interp, args, ,
13617 doc: /* -*- texinfo -*-
13618@deftypefn {} {} __go_uipushtool__ (@var{parent})
13619Undocumented internal function.
13620@end deftypefn */)
13621{
13622 GO_BODY (uipushtool);
13623}
13624
13625DEFMETHOD (__go_uitoggletool__, interp, args, ,
13626 doc: /* -*- texinfo -*-
13627@deftypefn {} {} __go_uitoggletool__ (@var{parent})
13628Undocumented internal function.
13629@end deftypefn */)
13630{
13631 GO_BODY (uitoggletool);
13632}
13633
13634DEFMETHOD (__go_delete__, interp, args, ,
13635 doc: /* -*- texinfo -*-
13636@deftypefn {} {} __go_delete__ (@var{h})
13637Undocumented internal function.
13638@end deftypefn */)
13639{
13640 gh_manager& gh_mgr = interp.get_gh_manager ();
13641
13642 octave::autolock guard (gh_mgr.graphics_lock ());
13643
13644 if (args.length () != 1)
13645 print_usage ();
13646
13648
13649 const NDArray vals = args(0).xarray_value ("delete: invalid graphics object");
13650
13651 // Check all the handles to delete are valid first,
13652 // as callbacks might delete one of the handles we later want to delete.
13653 for (octave_idx_type i = 0; i < vals.numel (); i++)
13654 {
13655 h = gh_mgr.lookup (vals(i));
13656
13657 if (! h.ok ())
13658 error ("delete: invalid graphics object (= %g)", vals(i));
13659 }
13660
13662
13663 return ovl ();
13664}
13665
13666DEFMETHOD (__go_handles__, interp, args, ,
13667 doc: /* -*- texinfo -*-
13668@deftypefn {} {} __go_handles__ (@var{show_hidden})
13669Undocumented internal function.
13670@end deftypefn */)
13671{
13672 gh_manager& gh_mgr = interp.get_gh_manager ();
13673
13674 octave::autolock guard (gh_mgr.graphics_lock ());
13675
13676 bool show_hidden = false;
13677
13678 if (args.length () > 0)
13679 show_hidden = args(0).bool_value ();
13680
13681 return ovl (gh_mgr.handle_list (show_hidden));
13682}
13683
13684DEFMETHOD (__go_figure_handles__, interp, args, ,
13685 doc: /* -*- texinfo -*-
13686@deftypefn {} {} __go_figure_handles__ (@var{show_hidden})
13687Undocumented internal function.
13688@end deftypefn */)
13689{
13690 gh_manager& gh_mgr = interp.get_gh_manager ();
13691
13692 octave::autolock guard (gh_mgr.graphics_lock ());
13693
13694 bool show_hidden = false;
13695
13696 if (args.length () > 0)
13697 show_hidden = args(0).bool_value ();
13698
13699 return ovl (gh_mgr.figure_handle_list (show_hidden));
13700}
13701
13702DEFMETHOD (__go_execute_callback__, interp, args, ,
13703 doc: /* -*- texinfo -*-
13704@deftypefn {} {} __go_execute_callback__ (@var{h}, @var{name})
13705@deftypefnx {} {} __go_execute_callback__ (@var{h}, @var{name}, @var{param})
13706Undocumented internal function.
13707@end deftypefn */)
13708{
13709 int nargin = args.length ();
13710
13711 if (nargin < 2 || nargin > 3)
13712 print_usage ();
13713
13714 const NDArray vals = args(0).xarray_value ("__go_execute_callback__: invalid graphics object");
13715
13716 std::string name = args(1).xstring_value ("__go_execute_callback__: invalid callback name");
13717
13718 gh_manager& gh_mgr = interp.get_gh_manager ();
13719
13720 for (octave_idx_type i = 0; i < vals.numel (); i++)
13721 {
13722 double val = vals(i);
13723
13724 graphics_handle h = gh_mgr.lookup (val);
13725
13726 if (! h.ok ())
13727 error ("__go_execute_callback__: invalid graphics object (= %g)", val);
13728
13729 if (nargin == 2)
13730 gh_mgr.execute_callback (h, name);
13731 else
13732 gh_mgr.execute_callback (h, name, args(2));
13733 }
13734
13735 return ovl ();
13736}
13737
13738DEFMETHOD (__go_post_callback__, interp, args, ,
13739 doc: /* -*- texinfo -*-
13740@deftypefn {} {} __go_post_callback__ (@var{h}, @var{name})
13741@deftypefnx {} {} __go_post_callback__ (@var{h}, @var{name}, @var{param})
13742Undocumented internal function.
13743@end deftypefn */)
13744{
13745 int nargin = args.length ();
13746
13747 if (nargin < 2 || nargin > 3)
13748 print_usage ();
13749
13750 const NDArray vals = args(0).xarray_value ("__go_post_callback__: invalid graphics object");
13751
13752 std::string name = args(1).xstring_value ("__go_post_callback__: invalid callback name");
13753
13754 gh_manager& gh_mgr = interp.get_gh_manager ();
13755
13756 for (octave_idx_type i = 0; i < vals.numel (); i++)
13757 {
13758 double val = vals(i);
13759
13760 graphics_handle h = gh_mgr.lookup (val);
13761
13762 if (! h.ok ())
13763 error ("__go_execute_callback__: invalid graphics object (= %g)", val);
13764
13765 if (nargin == 2)
13766 gh_mgr.post_callback (h, name);
13767 else
13768 gh_mgr.post_callback (h, name, args(2));
13769 }
13770
13771 return ovl ();
13772}
13773
13774DEFMETHOD (__image_pixel_size__, interp, args, ,
13775 doc: /* -*- texinfo -*-
13776@deftypefn {} {@var{sz} =} __image_pixel_size__ (@var{h})
13777Internal function: returns the pixel size of the image in normalized units.
13778@end deftypefn */)
13779{
13780 if (args.length () != 1)
13781 print_usage ();
13782
13783 gh_manager& gh_mgr = interp.get_gh_manager ();
13784
13785 double h = args(0).xdouble_value ("__image_pixel_size__: argument is not a handle");
13786
13787 graphics_object go = gh_mgr.get_object (h);
13788
13789 if (! go || ! go.isa ("image"))
13790 error ("__image_pixel_size__: object is not an image");
13791
13793 = dynamic_cast<image::properties&> (go.get_properties ());
13794
13795 Matrix dp = Matrix (1, 2);
13796 dp(0) = ip.pixel_xsize ();
13797 dp(1) = ip.pixel_ysize ();
13798 return ovl (dp);
13799}
13800
13801DEFMETHOD (available_graphics_toolkits, interp, , ,
13802 doc: /* -*- texinfo -*-
13803@deftypefn {} {} available_graphics_toolkits ()
13804Return a cell array of registered graphics toolkits.
13805@seealso{graphics_toolkit, register_graphics_toolkit}
13806@end deftypefn */)
13807{
13808 gh_manager& gh_mgr = interp.get_gh_manager ();
13809
13810 octave::autolock guard (gh_mgr.graphics_lock ());
13811
13812 octave::gtk_manager& gtk_mgr = interp.get_gtk_manager ();
13813
13814 return ovl (gtk_mgr.available_toolkits_list ());
13815}
13816
13817DEFMETHOD (register_graphics_toolkit, interp, args, ,
13818 doc: /* -*- texinfo -*-
13819@deftypefn {} {} register_graphics_toolkit (@var{toolkit})
13820List @var{toolkit} as an available graphics toolkit.
13821@seealso{available_graphics_toolkits}
13822@end deftypefn */)
13823{
13824 gh_manager& gh_mgr = interp.get_gh_manager ();
13825
13826 octave::autolock guard (gh_mgr.graphics_lock ());
13827
13828 if (args.length () != 1)
13829 print_usage ();
13830
13831 std::string name = args(0).xstring_value ("register_graphics_toolkit: TOOLKIT must be a string");
13832
13833 octave::gtk_manager& gtk_mgr = interp.get_gtk_manager ();
13834
13835 gtk_mgr.register_toolkit (name);
13836
13837 return ovl ();
13838}
13839
13840DEFMETHOD (loaded_graphics_toolkits, interp, , ,
13841 doc: /* -*- texinfo -*-
13842@deftypefn {} {} loaded_graphics_toolkits ()
13843Return a cell array of the currently loaded graphics toolkits.
13844@seealso{available_graphics_toolkits}
13845@end deftypefn */)
13846{
13847 gh_manager& gh_mgr = interp.get_gh_manager ();
13848
13849 octave::autolock guard (gh_mgr.graphics_lock ());
13850
13851 octave::gtk_manager& gtk_mgr = interp.get_gtk_manager ();
13852
13853 return ovl (gtk_mgr.loaded_toolkits_list ());
13854}
13855
13856DEFMETHOD (__show_figure__, interp, args, ,
13857 doc: /* -*- texinfo -*-
13858@deftypefn {} {} __show_figure__ (@var{n})
13859Undocumented internal function.
13860@end deftypefn */)
13861{
13862 if (args.length () != 1)
13863 print_usage ();
13864
13865 gh_manager& gh_mgr = interp.get_gh_manager ();
13866
13867 double h = args(0).xdouble_value ("__show_figure__: invalid handle H");
13868
13869 graphics_handle gh = gh_mgr.lookup (h);
13870
13871 if (! gh.ok ())
13872 error ("__show_figure__: invalid graphics object (= %g)", h);
13873
13874 graphics_object go = gh_mgr.get_object (gh);
13875
13876 figure::properties& fprops
13877 = dynamic_cast<figure::properties&> (go.get_properties ());
13878
13879 fprops.get_toolkit ().show_figure (go);
13880
13881 return ovl ();
13882}
13883
13884DEFMETHOD (drawnow, interp, args, ,
13885 doc: /* -*- texinfo -*-
13886@deftypefn {} {} drawnow ()
13887@deftypefnx {} {} drawnow ("expose")
13888@deftypefnx {} {} drawnow (@var{term}, @var{file}, @var{debug_file})
13889Update figure windows and their children.
13890
13891The event queue is flushed and any callbacks generated are executed.
13892
13893With the optional argument @qcode{"expose"}, only graphic objects are
13894updated and no other events or callbacks are processed.
13895
13896The third calling form of @code{drawnow} is for debugging and is
13897undocumented.
13898@seealso{refresh}
13899@end deftypefn */)
13900{
13901 if (args.length () > 3)
13902 print_usage ();
13903
13905
13906 // Redraw unless we are in the middle of a deletion.
13907
13908 if (! delete_executing)
13909 {
13910 gh_manager& gh_mgr = interp.get_gh_manager ();
13911
13912 octave::autolock guard (gh_mgr.graphics_lock ());
13913
13914 if (args.length () <= 1)
13915 {
13916 // First process events so that the redraw happens when all
13917 // objects are in their definite state.
13918 bool do_events = true;
13919
13920 if (args.length () == 1)
13921 {
13922 caseless_str val (args(0).xstring_value ("drawnow: first argument must be a string"));
13923
13924 if (val.compare ("expose"))
13925 do_events = false;
13926 else
13927 error ("drawnow: invalid argument, 'expose' is only valid option");
13928 }
13929
13930 if (do_events)
13931 {
13932 gh_mgr.unlock ();
13933
13934 gh_mgr.process_events ();
13935
13936 gh_mgr.lock ();
13937 }
13938
13939 Matrix hlist = gh_mgr.figure_handle_list (true);
13940
13941 // Redraw modified figures
13942 for (int i = 0; i < hlist.numel (); i++)
13943 {
13944 graphics_handle h = gh_mgr.lookup (hlist(i));
13945
13946 if (h.ok () && h != 0)
13947 {
13948 graphics_object go = gh_mgr.get_object (h);
13949 figure::properties& fprops
13950 = dynamic_cast<figure::properties&> (go.get_properties ());
13951
13952 if (fprops.is_modified ())
13953 {
13954 if (fprops.is_visible ())
13955 {
13956 gh_mgr.unlock ();
13957
13958 fprops.get_toolkit ().redraw_figure (go);
13959
13960 gh_mgr.lock ();
13961 }
13962
13963 fprops.set_modified (false);
13964 }
13965 }
13966
13967 }
13968 }
13969 else if (args.length () >= 2 && args.length () <= 3)
13970 {
13971 std::string term, file, debug_file;
13972
13973 term = args(0).xstring_value ("drawnow: TERM must be a string");
13974
13975 file = args(1).xstring_value ("drawnow: FILE must be a string");
13976
13977 if (file.empty ())
13978 error ("drawnow: empty output ''");
13979 else if (file.length () == 1 && file[0] == '|')
13980 error ("drawnow: empty pipe '|'");
13981 else if (file[0] != '|')
13982 {
13983 std::size_t pos = file.find_last_of (octave::sys::file_ops::dir_sep_chars ());
13984
13985 if (pos != std::string::npos)
13986 {
13987 std::string dirname = file.substr (0, pos+1);
13988
13990
13991 if (! fs || ! fs.is_dir ())
13992 error ("drawnow: nonexistent directory '%s'",
13993 dirname.c_str ());
13994
13995 }
13996 }
13997
13998 debug_file = (args.length () > 2 ? args(2).xstring_value ("drawnow: DEBUG_FILE must be a string") : "");
13999
14000 graphics_handle h = gcf ();
14001
14002 if (! h.ok ())
14003 error ("drawnow: nothing to draw");
14004
14005 graphics_object go = gh_mgr.get_object (h);
14006
14007 gh_mgr.unlock ();
14008
14009 go.get_toolkit ().print_figure (go, term, file, debug_file);
14010
14011 gh_mgr.lock ();
14012 }
14013 }
14014
14015 return ovl ();
14016}
14017
14018DEFMETHOD (addlistener, interp, args, ,
14019 doc: /* -*- texinfo -*-
14020@deftypefn {} {} addlistener (@var{h}, @var{prop}, @var{fcn})
14021Register @var{fcn} as listener for the property @var{prop} of the graphics
14022object @var{h}.
14023
14024Property listeners are executed (in order of registration) when the property
14025is set. The new value is already available when the listeners are executed.
14026
14027@var{prop} must be a string naming a valid property in @var{h}.
14028
14029@var{fcn} can be a function handle, a string or a cell array whose first
14030element is a function handle. If @var{fcn} is a function handle, the
14031corresponding function should accept at least 2 arguments, that will be
14032set to the object handle and the empty matrix respectively. If @var{fcn}
14033is a string, it must be any valid octave expression. If @var{fcn} is a cell
14034array, the first element must be a function handle with the same signature
14035as described above. The next elements of the cell array are passed
14036as additional arguments to the function.
14037
14038Example:
14039
14040@example
14041@group
14042function my_listener (h, dummy, p1)
14043 fprintf ("my_listener called with p1=%s\n", p1);
14044endfunction
14045
14046addlistener (gcf, "position", @{@@my_listener, "my string"@})
14047@end group
14048@end example
14049
14050@seealso{dellistener, addproperty, hggroup}
14051@end deftypefn */)
14052{
14053 gh_manager& gh_mgr = interp.get_gh_manager ();
14054
14055 octave::autolock guard (gh_mgr.graphics_lock ());
14056
14057 int nargin = args.length ();
14058
14059 if (nargin < 3 || nargin > 4)
14060 print_usage ();
14061
14062 double h = args(0).xdouble_value ("addlistener: invalid handle H");
14063
14064 std::string pname = args(1).xstring_value ("addlistener: PROP must be a string");
14065
14066 graphics_handle gh = gh_mgr.lookup (h);
14067
14068 if (! gh.ok ())
14069 error ("addlistener: invalid graphics object (= %g)", h);
14070
14071 graphics_object go = gh_mgr.get_object (gh);
14072
14073 go.add_property_listener (pname, args(2), GCB_POSTSET);
14074
14075 if (args.length () == 4)
14076 {
14077 caseless_str persistent = args(3).string_value ();
14078 if (persistent.compare ("persistent"))
14079 go.add_property_listener (pname, args(2), GCB_PERSISTENT);
14080 }
14081
14082 return ovl ();
14083}
14084
14085DEFMETHOD (dellistener, interp, args, ,
14086 doc: /* -*- texinfo -*-
14087@deftypefn {} {} dellistener (@var{h}, @var{prop}, @var{fcn})
14088Remove the registration of @var{fcn} as a listener for the property
14089@var{prop} of the graphics object @var{h}.
14090
14091The function @var{fcn} must be the same variable (not just the same value),
14092as was passed to the original call to @code{addlistener}.
14093
14094If @var{fcn} is not defined then all listener functions of @var{prop}
14095are removed.
14096
14097Example:
14098
14099@example
14100@group
14101function my_listener (h, dummy, p1)
14102 fprintf ("my_listener called with p1=%s\n", p1);
14103endfunction
14104
14105c = @{@@my_listener, "my string"@};
14106addlistener (gcf, "position", c);
14107dellistener (gcf, "position", c);
14108@end group
14109@end example
14110
14111@seealso{addlistener}
14112@end deftypefn */)
14113{
14114 gh_manager& gh_mgr = interp.get_gh_manager ();
14115
14116 octave::autolock guard (gh_mgr.graphics_lock ());
14117
14118 if (args.length () < 2 || args.length () > 3)
14119 print_usage ();
14120
14121 double h = args(0).xdouble_value ("dellistener: invalid handle");
14122
14123 std::string pname = args(1).xstring_value ("dellistener: PROP must be a string");
14124
14125 graphics_handle gh = gh_mgr.lookup (h);
14126
14127 if (! gh.ok ())
14128 error ("dellistener: invalid graphics object (= %g)", h);
14129
14130 graphics_object go = gh_mgr.get_object (gh);
14131
14132 if (args.length () == 2)
14133 go.delete_property_listener (pname, octave_value (), GCB_POSTSET);
14134 else
14135 {
14136 if (args(2).is_string ()
14137 && args(2).string_value () == "persistent")
14138 {
14139 go.delete_property_listener (pname, octave_value (),
14140 GCB_PERSISTENT);
14141 go.delete_property_listener (pname, octave_value (),
14142 GCB_POSTSET);
14143 }
14144 else
14145 go.delete_property_listener (pname, args(2), GCB_POSTSET);
14146 }
14147
14148 return ovl ();
14149}
14150
14151DEFMETHOD (addproperty, interp, args, ,
14152 doc: /* -*- texinfo -*-
14153@deftypefn {} {} addproperty (@var{name}, @var{h}, @var{type})
14154@deftypefnx {} {} addproperty (@var{name}, @var{h}, @var{type}, @var{arg}, @dots{})
14155Create a new property named @var{name} in graphics object @var{h}.
14156
14157@var{type} determines the type of the property to create. @var{args}
14158usually contains the default value of the property, but additional
14159arguments might be given, depending on the type of the property.
14160
14161The supported property types are:
14162
14163@table @code
14164@item string
14165A string property. @var{arg} contains the default string value.
14166
14167@item any
14168An @nospell{un-typed} property. This kind of property can hold any octave
14169value. @var{args} contains the default value.
14170
14171@item radio
14172A string property with a limited set of accepted values. The first
14173argument must be a string with all accepted values separated by
14174a vertical bar ('|'). The default value can be marked by enclosing
14175it with a '@{' '@}' pair. The default value may also be given as
14176an optional second string argument.
14177
14178@item boolean
14179A boolean property. This property type is equivalent to a radio
14180property with "on|off" as accepted values. @var{arg} contains
14181the default property value.
14182
14183@item double
14184A scalar double property. @var{arg} contains the default value.
14185
14186@item handle
14187A handle property. This kind of property holds the handle of a
14188graphics object. @var{arg} contains the default handle value.
14189When no default value is given, the property is initialized to
14190the empty matrix.
14191
14192@item data
14193A data (matrix) property. @var{arg} contains the default data
14194value. When no default value is given, the data is initialized to
14195the empty matrix.
14196
14197@item color
14198A color property. @var{arg} contains the default color value.
14199When no default color is given, the property is set to black.
14200An optional second string argument may be given to specify an
14201additional set of accepted string values (like a radio property).
14202@end table
14203
14204@var{type} may also be the concatenation of a core object type and
14205a valid property name for that object type. The property created
14206then has the same characteristics as the referenced property (type,
14207possible values, hidden state@dots{}). This allows one to clone an
14208existing property into the graphics object @var{h}.
14209
14210Examples:
14211
14212@example
14213@group
14214addproperty ("my_property", gcf, "string", "a string value");
14215addproperty ("my_radio", gcf, "radio", "val_1|val_2|@{val_3@}");
14216addproperty ("my_style", gcf, "linelinestyle", "--");
14217@end group
14218@end example
14219
14220@seealso{addlistener, hggroup}
14221@end deftypefn */)
14222{
14223 gh_manager& gh_mgr = interp.get_gh_manager ();
14224
14225 octave::autolock guard (gh_mgr.graphics_lock ());
14226
14227 if (args.length () < 3)
14228 print_usage ();
14229
14230 std::string name = args(0).xstring_value ("addproperty: NAME must be a string");
14231
14232 double h = args(1).xdouble_value ("addproperty: invalid handle H");
14233
14234 graphics_handle gh = gh_mgr.lookup (h);
14235
14236 if (! gh.ok ())
14237 error ("addproperty: invalid graphics object (= %g)", h);
14238
14239 graphics_object go = gh_mgr.get_object (gh);
14240
14241 std::string type = args(2).xstring_value ("addproperty: TYPE must be a string");
14242
14243 if (go.get_properties ().has_property (name))
14244 error ("addproperty: a '%s' property already exists in the graphics object",
14245 name.c_str ());
14246
14247 property p = property::create (name, gh, type, args.splice (0, 3));
14248
14249 go.get_properties ().insert_property (name, p);
14250
14251 return ovl ();
14252}
14253
14255get_property_from_handle (double handle, const std::string& property,
14256 const std::string& func)
14257{
14258 gh_manager& gh_mgr = octave::__get_gh_manager__ ("get_property_from_handle");
14259
14260 octave::autolock guard (gh_mgr.graphics_lock ());
14261
14262 graphics_object go = gh_mgr.get_object (handle);
14263
14264 if (! go)
14265 error ("%s: invalid handle (= %g)", func.c_str (), handle);
14266
14267 return go.get (caseless_str (property));
14268}
14269
14270bool
14271set_property_in_handle (double handle, const std::string& property,
14272 const octave_value& arg, const std::string& func)
14273{
14274 gh_manager& gh_mgr = octave::__get_gh_manager__ ("set_property_in_handle");
14275
14276 octave::autolock guard (gh_mgr.graphics_lock ());
14277
14278 graphics_object go = gh_mgr.get_object (handle);
14279
14280 if (! go)
14281 error ("%s: invalid handle (= %g)", func.c_str (), handle);
14282
14283 go.set (caseless_str (property), arg);
14284
14285 return true;
14286}
14287
14288static bool
14290{
14291 octave_value_list args(2);
14292
14293 args(0) = ov1;
14294 args(1) = ov2;
14295
14296 octave_value_list result = octave::feval ("isequal", args, 1);
14297
14298 if (result.length () > 0)
14299 return result(0).bool_value ();
14300
14301 return false;
14302}
14303
14304static std::map<uint32_t, bool> waitfor_results;
14305
14306static void
14308{
14309 waitfor_results.erase (id);
14310}
14311
14312static void
14314 listener_mode mode = GCB_POSTSET)
14315{
14316 Cell c = listener.cell_value ();
14317
14318 if (c.numel () >= 4)
14319 {
14320 double h = c(2).double_value ();
14321
14322 caseless_str pname = c(3).string_value ();
14323
14324 gh_manager& gh_mgr
14325 = octave::__get_gh_manager__ ("do_cleanup_waitfor_listener");
14326
14327 octave::autolock guard (gh_mgr.graphics_lock ());
14328
14329 graphics_handle gh = gh_mgr.lookup (h);
14330
14331 if (gh.ok ())
14332 {
14333 graphics_object go = gh_mgr.get_object (gh);
14334
14335 if (go.get_properties ().has_property (pname))
14336 {
14337 go.get_properties ().delete_listener (pname, listener, mode);
14338
14339 if (mode == GCB_POSTSET)
14340 go.get_properties ().delete_listener (pname, listener,
14341 GCB_PERSISTENT);
14342 }
14343 }
14344 }
14345}
14346
14347static void
14349{ do_cleanup_waitfor_listener (listener, GCB_POSTSET); }
14350
14351static void
14353{ do_cleanup_waitfor_listener (listener, GCB_PREDELETE); }
14354
14356{
14357 if (args.length () > 3)
14358 {
14359 uint32_t id = args(2).uint32_scalar_value ().value ();
14360
14361 if (args.length () > 5)
14362 {
14363 double h = args(0).double_value ();
14364
14365 caseless_str pname = args(4).string_value ();
14366
14367 gh_manager& gh_mgr = octave::__get_gh_manager__ ("waitfor_listener");
14368
14369 octave::autolock guard (gh_mgr.graphics_lock ());
14370
14371 graphics_handle gh = gh_mgr.lookup (h);
14372
14373 if (gh.ok ())
14374 {
14375 graphics_object go = gh_mgr.get_object (gh);
14376 octave_value pvalue = go.get (pname);
14377
14378 if (compare_property_values (pvalue, args(5)))
14379 waitfor_results[id] = true;
14380 }
14381 }
14382 else
14383 waitfor_results[id] = true;
14384 }
14385
14386 return ovl ();
14387}
14388
14390{
14391 if (args.length () > 2)
14392 {
14393 uint32_t id = args(2).uint32_scalar_value ().value ();
14394
14395 waitfor_results[id] = true;
14396 }
14397
14398 return ovl ();
14399}
14400
14401DEFMETHOD (waitfor, interp, args, ,
14402 doc: /* -*- texinfo -*-
14403@deftypefn {} {} waitfor (@var{h})
14404@deftypefnx {} {} waitfor (@var{h}, @var{prop})
14405@deftypefnx {} {} waitfor (@var{h}, @var{prop}, @var{value})
14406@deftypefnx {} {} waitfor (@dots{}, "timeout", @var{timeout})
14407Suspend the execution of the current program until a condition is
14408satisfied on the graphics handle @var{h}.
14409
14410While the program is suspended graphics events are still processed normally,
14411allowing callbacks to modify the state of graphics objects. This function
14412is reentrant and can be called from a callback, while another @code{waitfor}
14413call is pending at the top-level.
14414
14415In the first form, program execution is suspended until the graphics object
14416@var{h} is destroyed. If the graphics handle is invalid or if @var{h} is
14417the root graphics handle and no property @var{prop} was provided, the function
14418returns immediately.
14419
14420In the second form, execution is suspended until the graphics object is
14421destroyed or the property named @var{prop} is modified. If the graphics
14422handle is invalid or the property does not exist, the function returns
14423immediately.
14424
14425In the third form, execution is suspended until the graphics object is
14426destroyed or the property named @var{prop} is set to @var{value}. The
14427function @code{isequal} is used to compare property values. If the graphics
14428handle is invalid, the property does not exist or the property is already
14429set to @var{value}, the function returns immediately.
14430
14431An optional timeout can be specified using the property @qcode{"timeout"}.
14432This timeout value is the number of seconds to wait for the condition to be
14433true. @var{timeout} must be at least 1. If a smaller value is specified, a
14434warning is issued and a value of 1 is used instead. If the timeout value is
14435not an integer, it is truncated towards 0.
14436
14437To define a condition on a property named @qcode{"timeout"}, use the string
14438@qcode{'@backslashchar{}timeout'} instead.
14439
14440In all cases, typing CTRL-C stops program execution immediately.
14441@seealso{waitforbuttonpress, isequal}
14442@end deftypefn */)
14443{
14444 if (args.length () == 0)
14445 print_usage ();
14446
14447 // return immediately if the graphics handle is invalid
14448 if (args(0).isempty ())
14449 return ovl ();
14450
14451 double h = args(0).xdouble_value ("waitfor: invalid handle value");
14452
14453 if (! ishghandle (h) || (h == 0 && args.length () == 1))
14454 return ovl ();
14455
14456 caseless_str pname;
14457
14458 octave::unwind_action cleanup_waitfor_id_action;
14459 octave::unwind_action cleanup_waitfor_postset_listener_action;
14460 octave::unwind_action cleanup_waitfor_predelete_listener_action;
14461
14462 static uint32_t id_counter = 0;
14463 uint32_t id = 0;
14464
14465 int max_arg_index = 0;
14466 int timeout_index = -1;
14467
14468 double timeout = 0;
14469
14470 gh_manager& gh_mgr = interp.get_gh_manager ();
14471
14472 if (args.length () > 1)
14473 {
14474 pname = args(1).xstring_value ("waitfor: PROP must be a string");
14475
14476 if (pname.empty ())
14477 error ("waitfor: PROP must be a non-empty string");
14478
14479 if (pname != "timeout")
14480 {
14481 if (pname.compare (R"(\timeout)"))
14482 pname = "timeout";
14483
14484 static octave_value wf_listener;
14485
14486 if (! wf_listener.is_defined ())
14487 wf_listener
14489 "waitfor_listener"));
14490
14491 max_arg_index++;
14492 if (args.length () > 2)
14493 {
14494 if (args(2).is_string ())
14495 {
14496 caseless_str s = args(2).string_value ();
14497
14498 if (s.compare ("timeout"))
14499 timeout_index = 2;
14500 else
14501 max_arg_index++;
14502 }
14503 else
14504 max_arg_index++;
14505 }
14506
14507 Cell listener (1, max_arg_index >= 2 ? 5 : 4);
14508
14509 id = id_counter++;
14510 cleanup_waitfor_id_action.set (cleanup_waitfor_id, id);
14511 waitfor_results[id] = false;
14512
14513 listener(0) = wf_listener;
14514 listener(1) = octave_uint32 (id);
14515 listener(2) = h;
14516 listener(3) = pname;
14517
14518 if (max_arg_index >= 2)
14519 listener(4) = args(2);
14520
14521 octave_value ov_listener (listener);
14522
14523 octave::autolock guard (gh_mgr.graphics_lock ());
14524
14525 graphics_handle gh = gh_mgr.lookup (h);
14526
14527 if (gh.ok ())
14528 {
14529 graphics_object go = gh_mgr.get_object (gh);
14530
14531 if (max_arg_index >= 2
14532 && compare_property_values (go.get (pname), args(2)))
14533 waitfor_results[id] = true;
14534 else
14535 {
14536 cleanup_waitfor_postset_listener_action.set
14537 (cleanup_waitfor_postset_listener, ov_listener);
14538
14539 go.add_property_listener (pname, ov_listener, GCB_POSTSET);
14540 go.add_property_listener (pname, ov_listener, GCB_PERSISTENT);
14541
14542 if (go.get_properties ().has_dynamic_property (pname))
14543 {
14544 static octave_value wf_del_listener;
14545
14546 if (! wf_del_listener.is_defined ())
14547 wf_del_listener
14550 "waitfor_del_listener"));
14551
14552 Cell del_listener (1, 4);
14553
14554 del_listener(0) = wf_del_listener;
14555 del_listener(1) = octave_uint32 (id);
14556 del_listener(2) = h;
14557 del_listener(3) = pname;
14558
14559 octave_value ov_del_listener (del_listener);
14560
14561 cleanup_waitfor_predelete_listener_action.set
14562 (cleanup_waitfor_predelete_listener, ov_del_listener);
14563
14564 go.add_property_listener (pname, ov_del_listener,
14565 GCB_PREDELETE);
14566 }
14567 }
14568 }
14569 }
14570 }
14571
14572 if (timeout_index < 0 && args.length () > (max_arg_index + 1))
14573 {
14574 caseless_str s = args(max_arg_index + 1).xstring_value ("waitfor: invalid parameter, expected 'timeout'");
14575
14576 if (! s.compare ("timeout"))
14577 error ("waitfor: invalid parameter '%s'", s.c_str ());
14578
14579 timeout_index = max_arg_index + 1;
14580 }
14581
14582 if (timeout_index >= 0)
14583 {
14584 if (args.length () <= (timeout_index + 1))
14585 error ("waitfor: missing TIMEOUT value");
14586
14587 timeout = args(timeout_index + 1).xscalar_value ("waitfor: TIMEOUT must be a scalar >= 1");
14588
14589 if (timeout < 1)
14590 {
14591 warning ("waitfor: TIMEOUT value must be >= 1, using 1 instead");
14592 timeout = 1;
14593 }
14594 }
14595
14596 // FIXME: There is still a "hole" in the following loop. The code
14597 // assumes that an object handle is unique, which is a fair
14598 // assumption, except for figures. If a figure is destroyed
14599 // then recreated with the same figure ID, within the same
14600 // run of event hooks, then the figure destruction won't be
14601 // caught and the loop will not stop. This is an unlikely
14602 // possibility in practice, though.
14603 //
14604 // Using deletefcn callback is also unreliable as it could be
14605 // modified during a callback execution and the waitfor loop
14606 // would not stop.
14607 //
14608 // The only "good" implementation would require object
14609 // listeners, similar to property listeners.
14610
14611 octave::sys::time start;
14612
14613 if (timeout > 0)
14614 start.stamp ();
14615
14616 while (true)
14617 {
14618 if (true)
14619 {
14620 octave::autolock guard (gh_mgr.graphics_lock ());
14621
14622 graphics_handle gh = gh_mgr.lookup (h);
14623
14624 if (gh.ok ())
14625 {
14626 if (! pname.empty () && waitfor_results[id])
14627 break;
14628 }
14629 else
14630 break;
14631 }
14632
14633 octave::sleep (0.1); // FIXME: really needed?
14634
14635 octave_quit ();
14636
14638
14639 if (timeout > 0)
14640 {
14642
14643 if (start + timeout < now)
14644 break;
14645 }
14646 }
14647
14648 return ovl ();
14649}
14650
14651DEFMETHOD (__zoom__, interp, args, ,
14652 doc: /* -*- texinfo -*-
14653@deftypefn {} {} __zoom__ (@var{axes}, @var{mode}, @var{factor})
14654@deftypefnx {} {} __zoom__ (@var{axes}, "out")
14655@deftypefnx {} {} __zoom__ (@var{axes}, "reset")
14656Undocumented internal function.
14657@end deftypefn */)
14658{
14659 int nargin = args.length ();
14660
14661 if (nargin != 2 && nargin != 3)
14662 print_usage ();
14663
14664 double h = args(0).double_value ();
14665
14666 gh_manager& gh_mgr = interp.get_gh_manager ();
14667
14668 octave::autolock guard (gh_mgr.graphics_lock ());
14669
14670 graphics_handle handle = gh_mgr.lookup (h);
14671
14672 if (! handle.ok ())
14673 error ("__zoom__: invalid handle");
14674
14675 graphics_object ax = gh_mgr.get_object (handle);
14676
14677 axes::properties& ax_props
14678 = dynamic_cast<axes::properties&> (ax.get_properties ());
14679
14680 if (nargin == 2)
14681 {
14682 std::string opt = args(1).string_value ();
14683
14684 if (opt == "out" || opt == "reset")
14685 {
14686 if (opt == "out")
14687 {
14688 ax_props.clear_zoom_stack ();
14689 Vdrawnow_requested = true;
14690 }
14691 else
14692 ax_props.clear_zoom_stack (false);
14693 }
14694 }
14695 else
14696 {
14697 std::string mode = args(1).string_value ();
14698 double factor = args(2).scalar_value ();
14699
14700 ax_props.zoom (mode, factor);
14701 Vdrawnow_requested = true;
14702 }
14703
14704 return ovl ();
14705}
14706
14707DEFMETHOD (__get_frame__, interp, args, ,
14708 doc: /* -*- texinfo -*-
14709@deftypefn {} {@var{cdata} =} __get_frame__ (@var{hfig})
14710Internal function.
14711
14712Return the pixel cdata of figure hfig in the form of a height-by-width-by-3
14713uint8 array.
14714@end deftypefn */)
14715{
14716 if (args.length () != 1)
14717 print_usage ();
14718
14719 double h = args(0).xdouble_value ("__get_frame__: HFIG is not a handle");
14720
14721 gh_manager& gh_mgr = interp.get_gh_manager ();
14722
14723 graphics_object go = gh_mgr.get_object (h);
14724
14725 if (! go || ! go.isa ("figure"))
14726 error ("__get_frame__: HFIG is not a figure");
14727
14728 // For Matlab compatibility, getframe must flush the event queue.
14729 gh_mgr.process_events ();
14730
14731 return ovl (go.get_toolkit ().get_pixels (go));
14732}
14733
14734DEFMETHOD (__get_position__, interp, args, ,
14735 doc: /* -*- texinfo -*-
14736@deftypefn {} {@var{pos} =} __get_position__ (@var{h}, @var{units})
14737Internal function.
14738
14739Return the position of the graphics object @var{h} in the specified
14740@var{units}.
14741@end deftypefn */)
14742{
14743 if (args.length () != 2)
14744 print_usage ();
14745
14746 double h
14747 = args(0).xdouble_value ("__get_position__: H must be a graphics handle");
14748
14749 std::string units
14750 = args(1).xstring_value ("__get_position__: UNITS must be a string");
14751
14752 gh_manager& gh_mgr = interp.get_gh_manager ();
14753
14754 graphics_object go = gh_mgr.get_object (h);
14755
14756 if (h == 0 || ! go)
14757 error ("__get_position__: H must be a handle to a valid graphics object");
14758
14759 graphics_object parent_go = gh_mgr.get_object (go.get_parent ());
14760 Matrix bbox = parent_go.get_properties ().get_boundingbox (true)
14761 .extract_n (0, 2, 1, 2);
14762
14763 Matrix pos = convert_position (go.get ("position").matrix_value (),
14764 go.get ("units").string_value (),
14765 units, bbox);
14766
14767 return ovl (pos);
14768}
14769
14770DEFUN (__get_system_fonts__, args, ,
14771 doc: /* -*- texinfo -*-
14772@deftypefn {} {@var{font_struct} =} __get_system_fonts__ ()
14773Internal function.
14774@end deftypefn */)
14775{
14776 if (args.length () != 0)
14777 print_usage ();
14778
14779 octave::text_renderer txt_renderer;
14780
14781 return ovl (txt_renderer.get_system_fonts ());
14782}
14783
14784OCTAVE_NAMESPACE_END
#define Inf
Definition: Faddeeva.cc:260
#define NaN
Definition: Faddeeva.cc:261
octave_idx_type lookup(const T *x, octave_idx_type n, T y)
charNDArray max(char d, const charNDArray &m)
Definition: chNDArray.cc:230
charNDArray min(char d, const charNDArray &m)
Definition: chNDArray.cc:207
T & xelem(octave_idx_type n)
Size of the specified dimension.
Definition: Array.h:504
octave_idx_type numel(void) const
Number of elements in the array.
Definition: Array.h:411
const dim_vector & dims(void) const
Return a const-reference so that dims ()(i) works efficiently.
Definition: Array.h:487
T & elem(octave_idx_type n)
Size of the specified dimension.
Definition: Array.h:534
octave_idx_type rows(void) const
Definition: Array.h:449
bool isempty(void) const
Size of the specified dimension.
Definition: Array.h:607
OCTARRAY_API void resize(const dim_vector &dv, const T &rfv)
Size of the specified dimension.
Definition: Array.cc:1010
octave_idx_type columns(void) const
Definition: Array.h:458
const T * data(void) const
Size of the specified dimension.
Definition: Array.h:616
OCTARRAY_API T * fortran_vec(void)
Size of the specified dimension.
Definition: Array.cc:1744
Array< T, Alloc > as_column(void) const
Return the array as a column vector.
Definition: Array.h:415
int ndims(void) const
Size of the specified dimension.
Definition: Array.h:627
OCTARRAY_API octave_idx_type lookup(const T &value, sortmode mode=UNSORTED) const
Do a binary lookup in a sorted array.
Definition: Array.cc:2138
Definition: Cell.h:43
OCTAVE_API double max(void) const
Definition: dColVector.cc:261
void resize(octave_idx_type n, const double &rfv=0)
Definition: dColVector.h:112
OCTAVE_API RowVector transpose(void) const
Definition: dColVector.cc:125
OCTAVE_API double min(void) const
Definition: dColVector.cc:245
OCTAVE_API ColumnVector extract_n(octave_idx_type r1, octave_idx_type n) const
Definition: dColVector.cc:166
Definition: EIG.h:41
ComplexColumnVector eigenvalues(void) const
Definition: EIG.h:121
MArray< T > reshape(const dim_vector &new_dims) const
Definition: MArray.h:87
Definition: dMatrix.h:42
OCTAVE_API ColumnVector row_max(void) const
Definition: dMatrix.cc:2479
OCTAVE_API RowVector row(octave_idx_type i) const
Definition: dMatrix.cc:416
OCTAVE_API Matrix extract_n(octave_idx_type r1, octave_idx_type c1, octave_idx_type nr, octave_idx_type nc) const
Definition: dMatrix.cc:407
OCTAVE_API ColumnVector row_min(void) const
Definition: dMatrix.cc:2424
Matrix transpose(void) const
Definition: dMatrix.h:140
void resize(octave_idx_type nr, octave_idx_type nc, double rfv=0)
Definition: dMatrix.h:158
OCTAVE_API bool any_element_is_inf_or_nan(void) const
Definition: dNDArray.cc:324
callback_event(const graphics_handle &h, const std::string &name, const octave_value &data=Matrix(), int busyaction=base_graphics_event::QUEUE)
Definition: graphics.cc:12027
std::string m_callback_name
Definition: graphics.cc:12058
void execute(void)
Definition: graphics.cc:12039
graphics_handle m_handle
Definition: graphics.cc:12057
callback_event(void)
Definition: graphics.cc:12051
callback_event(const graphics_handle &h, const octave_value &cb, const octave_value &data=Matrix(), int busyaction=base_graphics_event::QUEUE)
Definition: graphics.cc:12033
octave_value m_callback_data
Definition: graphics.cc:12060
octave_value m_callback
Definition: graphics.cc:12059
~callback_props(void)=default
callback_props(const callback_props &)=delete
bool empty(void) const
Definition: graphics.cc:1956
callback_props & operator=(const callback_props &)=delete
callback_props(void)
Definition: graphics.cc:1948
void erase(const callback_property *ptr)
Definition: graphics.cc:1963
std::set< intptr_t > m_set
Definition: graphics.cc:1975
bool contains(const callback_property *ptr) const
Definition: graphics.cc:1968
void insert(const callback_property *ptr)
Definition: graphics.cc:1958
bool compare(const std::string &s, std::size_t limit=std::string::npos) const
Definition: caseless-str.h:78
Vector representing the dimensions (size) of an Array.
Definition: dim-vector.h:94
void resize(int n, int fill_value=0)
Definition: dim-vector.h:272
static dim_vector alloc(int n)
Definition: dim-vector.h:202
octave_idx_type ndims(void) const
Number of dimensions.
Definition: dim-vector.h:257
graphics_event::event_fcn m_function
Definition: graphics.cc:12125
void * m_function_data
Definition: graphics.cc:12127
void execute(void)
Definition: graphics.cc:12118
function_event(graphics_event::event_fcn fcn, void *data=nullptr)
Definition: graphics.cc:12108
function_event(void)=delete
function_event(const function_event &)=delete
void execute(void)
Definition: graphics.cc:12072
mcode_event(void)
Definition: graphics.cc:12090
mcode_event(const graphics_handle &h, const std::string &cmd, int busyaction=base_graphics_event::QUEUE)
Definition: graphics.cc:12067
graphics_handle m_handle
Definition: graphics.cc:12095
std::string m_mcode
Definition: graphics.cc:12096
static void remove_event_hook(event_hook_fcn f)
Definition: cmd-edit.cc:1562
static void run_event_hooks(void)
Definition: cmd-edit.cc:1574
static void add_event_hook(event_hook_fcn f)
Definition: cmd-edit.cc:1554
std::string get_name(void) const
graphics_toolkit find_toolkit(const std::string &name) const
Definition: gtk-manager.h:67
void register_toolkit(const std::string &name)
Definition: gtk-manager.cc:74
Cell loaded_toolkits_list(void) const
Definition: gtk-manager.h:88
Cell available_toolkits_list(void) const
Definition: gtk-manager.h:77
graphics_toolkit get_toolkit(void) const
Definition: gtk-manager.cc:39
std::string default_toolkit(void) const
Definition: gtk-manager.h:115
bool is_dir(void) const
Definition: file-stat.cc:65
OCTAVE_API void stamp(void)
Definition: oct-time.cc:114
octave_map get_system_fonts(void)
static bool is_thread(void)
void set(F &&fcn, Args &&... args)
Definition: unwind-prot.h:191
octave_value as_octave_value(void) const
Definition: oct-handle.h:80
double value(void) const
Definition: oct-handle.h:78
bool ok(void) const
Definition: oct-handle.h:113
Cell getfield(const std::string &key) const
Definition: oct-map.cc:275
octave_idx_type nfields(void) const
Definition: oct-map.h:344
const Cell & contents(const_iterator p) const
Definition: oct-map.h:331
bool isfield(const std::string &name) const
Definition: oct-map.h:347
octave_idx_type numel(void) const
Definition: oct-map.h:389
string_vector keys(void) const
Definition: oct-map.h:356
static octave_map cat(int dim, octave_idx_type n, const octave_scalar_map *map_list)
Definition: oct-map.cc:690
void setfield(const std::string &key, const octave_value &val)
Definition: oct-map.cc:190
void assign(const std::string &k, const octave_value &val)
Definition: oct-map.h:238
octave_value getfield(const std::string &key) const
Definition: oct-map.cc:183
octave_idx_type length(void) const
Definition: ovl.h:113
octave_value_list splice(octave_idx_type offset, octave_idx_type len, const octave_value_list &lst=octave_value_list()) const
Definition: ovl.cc:139
bool is_function(void) const
Definition: ov.h:822
bool iscellstr(void) const
Definition: ov.h:652
bool iscell(void) const
Definition: ov.h:649
bool isreal(void) const
Definition: ov.h:783
OCTINTERP_API octave_function * function_value(bool silent=false) const
bool is_uint16_type(void) const
Definition: ov.h:766
bool is_bool_scalar(void) const
Definition: ov.h:667
bool is_int8_type(void) const
Definition: ov.h:751
octave_idx_type rows(void) const
Definition: ov.h:590
bool isnumeric(void) const
Definition: ov.h:795
octave_idx_type numel(void) const
Definition: ov.h:604
bool is_scalar_type(void) const
Definition: ov.h:789
bool is_string(void) const
Definition: ov.h:682
bool is_defined(void) const
Definition: ov.h:637
bool is_double_type(void) const
Definition: ov.h:740
Cell cell_value(void) const
bool is_function_handle(void) const
Definition: ov.h:813
std::string class_name(void) const
Definition: ov.h:1451
bool is_uint32_type(void) const
Definition: ov.h:769
octave_idx_type columns(void) const
Definition: ov.h:592
bool is_int64_type(void) const
Definition: ov.h:760
octave_value reshape(const dim_vector &dv) const
Definition: ov.h:616
std::string string_value(bool force=false) const
Definition: ov.h:1019
bool is_matrix_type(void) const
Definition: ov.h:792
bool is_int32_type(void) const
Definition: ov.h:757
bool is_uint64_type(void) const
Definition: ov.h:772
bool is_int16_type(void) const
Definition: ov.h:754
string_vector string_vector_value(bool pad=false) const
Definition: ov.h:1022
bool isempty(void) const
Definition: ov.h:646
NDArray array_value(bool frc_str_conv=false) const
Definition: ov.h:904
bool is_single_type(void) const
Definition: ov.h:743
bool is_real_scalar(void) const
Definition: ov.h:655
bool is_undefined(void) const
Definition: ov.h:640
OCTINTERP_API octave_idx_type length(void) const
bool is_uint8_type(void) const
Definition: ov.h:763
Matrix matrix_value(bool frc_str_conv=false) const
Definition: ov.h:898
bool iscomplex(void) const
Definition: ov.h:786
double double_value(bool frc_str_conv=false) const
Definition: ov.h:886
std::string type_name(void) const
Definition: ov.h:1449
bool islogical(void) const
Definition: ov.h:780
dim_vector dims(void) const
Definition: ov.h:586
OCTINTERP_API double xdouble_value(const char *fmt,...) const
bool m_notify_toolkit
Definition: graphics.cc:12202
void execute(void)
Definition: graphics.cc:12142
bool m_redraw_figure
Definition: graphics.cc:12203
std::string m_property_name
Definition: graphics.cc:12200
set_event(const graphics_handle &h, const std::string &name, const octave_value &value, bool do_notify_toolkit=true, bool redraw_figure=false)
Definition: graphics.cc:12134
set_event(void)
Definition: graphics.cc:12194
graphics_handle m_handle
Definition: graphics.cc:12199
octave_value m_property_value
Definition: graphics.cc:12201
string_vector & append(const std::string &s)
Definition: str-vec.cc:110
std::ostream & list_in_columns(std::ostream &, int width=0, const std::string &prefix="") const
Definition: str-vec.cc:201
std::string join(const std::string &sep="") const
Definition: str-vec.cc:137
octave_idx_type numel(void) const
Definition: str-vec.h:100
static octave_idx_type find(octave_idx_type i, octave_idx_type *pp)
Definition: colamd.cc:106
ColumnVector real(const ComplexColumnVector &a)
Definition: dColVector.cc:137
T eps(const T &x)
Definition: data.cc:4842
#define DECLARE_STATIC_FUNX(name, args_name, nargout_name)
Definition: defun-int.h:161
OCTINTERP_API void print_usage(void)
Definition: defun-int.h:72
#define DEFMETHOD(name, interp_name, args_name, nargout_name, doc)
Macro to define a builtin method.
Definition: defun.h:111
#define DEFUN(name, args_name, nargout_name, doc)
Macro to define a builtin function.
Definition: defun.h:56
void interpreter_try(unwind_protect &frame)
Definition: error.cc:2175
void warning(const char *fmt,...)
Definition: error.cc:1055
void warning_with_id(const char *id, const char *fmt,...)
Definition: error.cc:1070
void error(const char *fmt,...)
Definition: error.cc:980
octave_value_list set_warning_state(const std::string &id, const std::string &state)
Definition: error.cc:1812
void disable_warning(const std::string &id)
Definition: error.cc:1841
int warning_enabled(const std::string &id)
Definition: error.cc:1833
octave_handle graphics_handle
static double default_screendepth(void)
Definition: graphics.cc:225
static Matrix default_patch_vertices(void)
Definition: graphics.cc:406
static void xset(const graphics_handle &h, const caseless_str &pname, const octave_value &val)
Definition: graphics.cc:2982
static graphics_handle reparent(const octave_value &ov, const std::string &who, const std::string &pname, const graphics_handle &new_parent, bool adopt=true)
Definition: graphics.cc:3016
static Matrix default_surface_ydata(void)
Definition: graphics.cc:365
static Matrix default_light_position(void)
Definition: graphics.cc:585
static bool compare_property_values(const octave_value &ov1, const octave_value &ov2)
Definition: graphics.cc:14289
static Matrix default_patch_ydata(void)
Definition: graphics.cc:428
static double make_handle_fraction(void)
Definition: graphics.cc:2831
void cross_product(double x1, double y1, double z1, double x2, double y2, double z2, double &x, double &y, double &z)
Definition: graphics.cc:10039
Matrix unit_cube(void)
Definition: graphics.cc:5958
double dot(const ColumnVector &v1, const ColumnVector &v2)
Definition: graphics.cc:5934
static bool updating_zlabel_position
Definition: graphics.cc:6666
static Matrix default_image_cdata(void)
Definition: graphics.cc:337
static base_graphics_object * make_graphics_object_from_type(const caseless_str &type, const graphics_handle &h=graphics_handle(), const graphics_handle &p=graphics_handle())
Definition: graphics.cc:1210
void translate(Matrix &m, double x, double y, double z)
Definition: graphics.cc:5899
static std::set< double > updating_aspectratios
Definition: graphics.cc:6895
OCTAVE_EXPORT octave_value_list Fdrawnow(octave::interpreter &interp, const octave_value_list &args, int)
Definition: graphics.cc:13899
static caseless_str validate_property_name(const std::string &who, const std::string &what, const std::set< std::string > &pnames, const caseless_str &pname)
Definition: graphics.cc:87
static Matrix default_data(void)
Definition: graphics.cc:313
#define CONVERT_CDATA_1(ARRAY_T, VAL_FN, IS_REAL)
static graphics_handle make_graphics_handle(const std::string &go_name, const graphics_handle &parent, bool integer_figure_handle=false, bool call_createfcn=true, bool notify_toolkit=true)
Definition: graphics.cc:623
static bool updating_hggroup_limits
Definition: graphics.cc:10776
ColumnVector cross(const ColumnVector &v1, const ColumnVector &v2)
Definition: graphics.cc:5946
static void delete_graphics_objects(const NDArray vals, bool from_root=false)
Definition: graphics.cc:3102
std::vector< octave_idx_type > coplanar_partition(const Matrix &vert, const Matrix &idx, octave_idx_type nc, octave_idx_type jj)
Definition: graphics.cc:9810
static void finalize_r(const graphics_handle &h)
Definition: graphics.cc:2136
static Matrix default_figure_paperposition(void)
Definition: graphics.cc:525
static Matrix default_figure_papersize(void)
Definition: graphics.cc:514
graphics_handle gca(void)
Definition: graphics.cc:3059
static Matrix default_table_backgroundcolor(void)
Definition: graphics.cc:610
static octave_value convert_ticklabel_string(const octave_value &val)
Definition: graphics.cc:7185
void get_children_limits(double &min_val, double &max_val, double &min_pos, double &max_neg, const Matrix &kids, char limit_type)
Definition: graphics.cc:8392
#define FIX_LIMITS
static Matrix screen_size_pixels(void)
Definition: graphics.cc:929
double norm(const ColumnVector &v)
Definition: graphics.cc:5940
static void xreset_default_properties(graphics_handle h, property_list::pval_map_type factory_pval)
Definition: graphics.cc:3267
static bool updating_patch_data
Definition: graphics.cc:9714
void xform(ColumnVector &v, const Matrix &m)
Definition: graphics.cc:5905
static Matrix default_axes_position(void)
Definition: graphics.cc:438
static Matrix default_screensize(void)
Definition: graphics.cc:234
void normalize(ColumnVector &v)
Definition: graphics.cc:5927
static Matrix default_axes_view(void)
Definition: graphics.cc:464
static Matrix default_surface_xdata(void)
Definition: graphics.cc:353
static void close_figure(const graphics_handle &h)
Definition: graphics.cc:3112
Matrix xform_scale(double x, double y, double z)
Definition: graphics.cc:5867
static void normalized_aspectratios(Matrix &aspectratios, const Matrix &scalefactors, double xlength, double ylength, double zlength)
Definition: graphics.cc:6859
static std::map< caseless_str, graphics_object > dprop_obj_map
Definition: graphics.cc:2009
static Matrix default_control_position(void)
Definition: graphics.cc:548
static Matrix do_translate(double x0, double x1, const Matrix &lims, bool is_logscale)
Definition: graphics.cc:9055
static Matrix default_axes_tick(void)
Definition: graphics.cc:475
static bool updating_scatter_cdata
Definition: graphics.cc:10339
static void update_text_pos(graphics_handle h)
Definition: graphics.cc:2275
static callback_props executing_callbacks
Definition: graphics.cc:1982
static double device_pixel_ratio(graphics_handle h)
Definition: graphics.cc:942
static Matrix default_control_sliderstep(void)
Definition: graphics.cc:561
ColumnVector cam2xform(const Array< double > &m)
Definition: graphics.cc:5979
static void delete_graphics_object(const graphics_handle &h, bool from_root=false)
Definition: graphics.cc:3068
static OCTAVE_NORETURN void err_set_invalid(const std::string &pname)
Definition: graphics.cc:77
int calc_dimensions(const graphics_object &go)
Definition: graphics.cc:13417
static bool updating_ylabel_position
Definition: graphics.cc:6562
static bool ishghandle(const graphics_handle &h)
Definition: graphics.cc:3188
RowVector xform2cam(const ColumnVector &v)
Definition: graphics.cc:5989
static bool delete_executing
Definition: graphics.cc:3099
static void magform(double x, double &a, int &b)
Definition: graphics.cc:7529
static Matrix papersize_from_type(const caseless_str punits, const caseless_str ptype)
Definition: graphics.cc:4456
static std::map< uint32_t, bool > waitfor_results
Definition: graphics.cc:14304
static bool updating_axes_layout
Definition: graphics.cc:6208
static Matrix default_patch_xdata(void)
Definition: graphics.cc:418
static void check_limit_vals(double &min_val, double &max_val, double &min_pos, double &max_neg, const octave_value &data)
Definition: graphics.cc:7490
static bool lookup_object_name(const caseless_str &name, caseless_str &go_name, caseless_str &rest)
Definition: graphics.cc:1130
static Matrix default_lim(bool logscale=false)
Definition: graphics.cc:294
static void force_close_figure(const graphics_handle &h)
Definition: graphics.cc:3122
static Matrix default_table_position(void)
Definition: graphics.cc:597
static int process_graphics_events(void)
Definition: graphics.cc:12389
static double convert_font_size(double font_size, const caseless_str &from_units, const caseless_str &to_units, double parent_height=0)
Definition: graphics.cc:636
static Matrix viridis_colormap(void)
Definition: graphics.cc:141
static void get_array_limits(const Array< T > &m, double &emin, double &emax, double &eminp, double &emaxp)
Definition: graphics.cc:1101
#define GO_BODY(TYPE)
Definition: graphics.cc:13406
static void cleanup_waitfor_predelete_listener(const octave_value &listener)
Definition: graphics.cc:14352
static octave_value_list waitfor_listener(const octave_value_list &args, int)
Definition: graphics.cc:14355
static std::set< double > updating_axis_limits
Definition: graphics.cc:8477
bool set_property_in_handle(double handle, const std::string &property, const octave_value &arg, const std::string &func)
Definition: graphics.cc:14271
static void xset_gcbo(const graphics_handle &h)
Definition: graphics.cc:12250
static ColumnVector convert_label_position(const ColumnVector &p, const text::properties &props, const graphics_xform &xform, const Matrix &bbox)
Definition: graphics.cc:6430
void scale(Matrix &m, double x, double y, double z)
Definition: graphics.cc:5893
static octave_value convert_linestyleorder_string(const octave_value &val)
Definition: graphics.cc:7283
Matrix xform_translate(double x, double y, double z)
Definition: graphics.cc:5880
static std::string default_graphics_toolkit(void)
Definition: graphics.cc:539
static void initialize_r(const graphics_handle &h)
Definition: graphics.cc:2154
static void cleanup_waitfor_id(uint32_t id)
Definition: graphics.cc:14307
static std::string get_graphics_object_type(double val)
Definition: graphics.cc:13045
octave_value get_property_from_handle(double handle, const std::string &property, const std::string &func)
Definition: graphics.cc:14255
Matrix xform_matrix(void)
Definition: graphics.cc:5828
static double default_screenpixelsperinch(void)
Definition: graphics.cc:250
static bool updating_xlabel_position
Definition: graphics.cc:6458
ColumnVector xform_vector(void)
Definition: graphics.cc:5839
static Matrix default_surface_cdata(void)
Definition: graphics.cc:388
ColumnVector transform(const Matrix &m, double x, double y, double z)
Definition: graphics.cc:5861
graphics_handle gcf(void)
Definition: graphics.cc:3049
static void do_cleanup_waitfor_listener(const octave_value &listener, listener_mode mode=GCB_POSTSET)
Definition: graphics.cc:14313
static void xinitialize(const graphics_handle &h)
Definition: graphics.cc:3236
static OCTAVE_NAMESPACE_BEGIN octave_value xget(const graphics_handle &h, const caseless_str &name)
Definition: graphics.cc:3006
static void convert_cdata_2(bool is_scaled, bool is_real, double clim_0, double clim_1, const double *cmapv, double x, octave_idx_type lda, octave_idx_type nc, octave_idx_type i, double *av)
Definition: graphics.cc:957
static Matrix default_data_lim(void)
Definition: graphics.cc:324
#define CHECK_ARRAY_EQUAL(T, F, A)
static Matrix default_panel_position(void)
Definition: graphics.cc:572
static octave_value_list waitfor_del_listener(const octave_value_list &args, int)
Definition: graphics.cc:14389
bool is_coplanar(const Matrix &cov)
Definition: graphics.cc:9798
static Matrix do_zoom(double val, double factor, const Matrix &lims, bool is_logscale)
Definition: graphics.cc:8910
static bool updating_title_position
Definition: graphics.cc:6793
static bool is_handle_visible(const graphics_handle &h)
Definition: graphics.cc:12686
static Matrix default_colororder(void)
Definition: graphics.cc:259
static octave_value convert_cdata(const base_properties &props, const octave_value &cdata, bool is_scaled, int cdim)
Definition: graphics.cc:999
static void adopt(const graphics_handle &parent_h, const graphics_handle &h)
Definition: graphics.cc:3178
static Matrix default_axes_ticklength(void)
Definition: graphics.cc:490
double force_in_range(double x, double lower, double upper)
Definition: graphics.cc:8899
static Matrix default_surface_zdata(void)
Definition: graphics.cc:377
static void max_axes_scale(double &s, Matrix &limits, const Matrix &kids, double pbfactor, double dafactor, char limit_type, bool tight)
Definition: graphics.cc:6874
static void xcreatefcn(const graphics_handle &h)
Definition: graphics.cc:3226
static Matrix default_axes_outerposition(void)
Definition: graphics.cc:451
static Matrix convert_position(const Matrix &pos, const caseless_str &from_units, const caseless_str &to_units, const Matrix &parent_dim)
Definition: graphics.cc:694
static Matrix default_patch_faces(void)
Definition: graphics.cc:394
static octave_value make_graphics_object(const std::string &go_name, bool integer_figure_handle, const octave_value_list &args)
Definition: graphics.cc:13235
static Matrix convert_text_position(const Matrix &pos, const text::properties &props, const caseless_str &from_units, const caseless_str &to_units)
Definition: graphics.cc:837
static int toggle_warn(std::string id, bool on, int state=-1)
Definition: graphics.cc:3249
static bool isfigure(double val)
Definition: graphics.cc:2880
void convert_cdata_1(bool is_scaled, bool is_real, double clim_0, double clim_1, const double *cmapv, const T *cv, octave_idx_type lda, octave_idx_type nc, double *av)
Definition: graphics.cc:989
static Matrix default_figure_position(void)
Definition: graphics.cc:501
static void cleanup_waitfor_postset_listener(const octave_value &listener)
Definition: graphics.cc:14348
QString name
bool Vdrawnow_requested
Definition: input.cc:90
#define octave_NaN
Definition: lo-ieee.h:44
F77_RET_T const F77_DBLE const F77_DBLE F77_DBLE * d
F77_RET_T const F77_DBLE * x
F77_RET_T const F77_DBLE const F77_DBLE * f
class OCTAVE_API NDArray
Definition: mx-fwd.h:38
class OCTAVE_API Matrix
Definition: mx-fwd.h:31
void F(const TSRC *v, TRES *r, octave_idx_type m, octave_idx_type n)
Definition: mx-inlines.cc:757
T::properties & properties(graphics_object obj)
double fix(double x)
Definition: lo-mappers.h:118
Complex atan(const Complex &x)
Definition: lo-mappers.h:71
double signum(double x)
Definition: lo-mappers.h:229
T max(T x, T y)
Definition: lo-mappers.h:368
T x_nint(T x)
Definition: lo-mappers.h:269
bool isfinite(double x)
Definition: lo-mappers.h:192
bool isnan(bool)
Definition: lo-mappers.h:178
bool isinf(double x)
Definition: lo-mappers.h:203
double round(double x)
Definition: lo-mappers.h:136
std::complex< T > ceil(const std::complex< T > &x)
Definition: lo-mappers.h:103
std::complex< T > floor(const std::complex< T > &x)
Definition: lo-mappers.h:130
T min(T x, T y)
Definition: lo-mappers.h:361
std::string dirname(const std::string &path)
Definition: file-ops.cc:358
std::string dir_sep_chars(void)
Definition: file-ops.cc:247
static uint32_t state[624]
Definition: randmtzig.cc:192
OCTINTERP_API octave_value_list feval(const char *name, const octave_value_list &args=octave_value_list(), int nargout=0)
interpreter & __get_interpreter__(const std::string &who)
gh_manager & __get_gh_manager__(const std::string &who)
display_info & __get_display_info__(const std::string &who)
gtk_manager & __get_gtk_manager__(const std::string &who)
STL namespace.
octave_int< uint32_t > octave_uint32
octave_int< T > pow(const octave_int< T > &a, const octave_int< T > &b)
octave_int< T > xmin(const octave_int< T > &x, const octave_int< T > &y)
octave_int< T > xmax(const octave_int< T > &x, const octave_int< T > &y)
#define OCTAVE_LOCAL_BUFFER(T, buf, size)
Definition: oct-locbuf.h:44
void free(void *)
static void initialize(void)
static char default_im_data[]
return octave_value(v1.char_array_value() . concat(v2.char_array_value(), ra_idx),((a1.is_sq_string()||a2.is_sq_string()) ? '\'' :'"'))
const octave_char_matrix & v2
octave_value_list ovl(const OV_Args &... args)
Construct an octave_value_list with less typing.
Definition: ovl.h:211
void flush_stdout(void)
Definition: pager.cc:260
#define octave_stdout
Definition: pager.h:314
static T abs(T x)
Definition: pr-output.cc:1678
void sleep(double seconds, bool do_graphics_events)
Definition: utils.cc:1557
F77_RET_T len
Definition: xerbla.cc:61