GNU Octave 11.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
 
Loading...
Searching...
No Matches
graphics.cc
Go to the documentation of this file.
1////////////////////////////////////////////////////////////////////////
2//
3// Copyright (C) 2007-2026 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-sysdep.h"
49#include "oct-time.h"
50
51#include "builtin-defun-decls.h"
52#include "defun.h"
53#include "display.h"
54#include "error.h"
55#include "gh-manager.h"
56#include "graphics-utils.h"
57#include "graphics.h"
58#include "input.h"
59#include "interpreter-private.h"
60#include "interpreter.h"
61#include "ov.h"
62#include "ovl.h"
63#include "oct-map.h"
64#include "ov-fcn-handle.h"
65#include "pager.h"
66#include "text-engine.h"
67#include "text-renderer.h"
68#include "unwind-prot.h"
69#include "utils.h"
71
73
74OCTAVE_NORETURN static
75void
76err_set_invalid (const std::string& pname)
77{
78 error ("set: invalid value for %s property", pname.c_str ());
79}
80
81// Check to see that PNAME matches just one of PNAMES uniquely.
82// Return the full name of the match, or an empty caseless_str object
83// if there is no match, or the match is ambiguous.
84
85static caseless_str
86validate_property_name (const std::string& who, const std::string& what,
87 const std::set<std::string>& pnames,
88 const caseless_str& pname)
89{
90 std::size_t len = pname.length ();
91 std::set<std::string> matches;
92
93 // Find exact or partial matches to property name
94 for (const auto& propnm : pnames)
95 {
96 if (pname.compare (propnm, len))
97 {
98 if (len == propnm.length ())
99 return pname; // Exact match.
100
101 matches.insert (propnm);
102 }
103 }
104
105 std::size_t num_matches = matches.size ();
106
107 if (num_matches == 0)
108 error ("%s: unknown %s property %s",
109 who.c_str (), what.c_str (), pname.c_str ());
110 else if (num_matches > 1)
111 {
112 string_vector sv (matches);
113
114 std::ostringstream os;
115
116 sv.list_in_columns (os);
117
118 std::string match_list = os.str ();
119
120 error ("%s: ambiguous %s property name %s; possible matches:\n\n%s",
121 who.c_str (), what.c_str (), pname.c_str (), match_list.c_str ());
122 }
123 else // num_matches == 1
124 {
125 // Exact match was handled above.
126 std::string possible_match = *(matches.begin ());
127
128 warning_with_id ("Octave:abbreviated-property-match",
129 "%s: allowing %s to match %s property %s",
130 who.c_str (), pname.c_str (), what.c_str (),
131 possible_match.c_str ());
132
133 return possible_match;
134 }
135
136 return caseless_str ();
137}
138
139static Matrix
140viridis_colormap ()
141{
142 // The values below have been produced by "viridis ()(:)".
143 // It would be nice to be able to feval the viridis function but since there
144 // is a static property object that includes a colormap_property object, we
145 // need to initialize this before main() is even called, so calling an
146 // interpreted function is not possible.
147
148 const double cmapv[] =
149 {
150 0.26700401, 0.26851048, 0.26994384,
151 0.27130489, 0.27259384, 0.27380934,
152 0.27495242, 0.27602238, 0.27701840,
153 0.27794143, 0.27879067, 0.27956550,
154 0.28026658, 0.28089358, 0.28144581,
155 0.28192358, 0.28232739, 0.28265633,
156 0.28291049, 0.28309095, 0.28319704,
157 0.28322882, 0.28318684, 0.28307200,
158 0.28288389, 0.28262297, 0.28229037,
159 0.28188676, 0.28141228, 0.28086773,
160 0.28025468, 0.27957399, 0.27882618,
161 0.27801236, 0.27713437, 0.27619376,
162 0.27519116, 0.27412802, 0.27300596,
163 0.27182812, 0.27059473, 0.26930756,
164 0.26796846, 0.26657984, 0.26514450,
165 0.26366320, 0.26213801, 0.26057103,
166 0.25896451, 0.25732244, 0.25564519,
167 0.25393498, 0.25219404, 0.25042462,
168 0.24862899, 0.24681140, 0.24497208,
169 0.24311324, 0.24123708, 0.23934575,
170 0.23744138, 0.23552606, 0.23360277,
171 0.23167350, 0.22973926, 0.22780192,
172 0.22586330, 0.22392515, 0.22198915,
173 0.22005691, 0.21812995, 0.21620971,
174 0.21429757, 0.21239477, 0.21050310,
175 0.20862342, 0.20675628, 0.20490257,
176 0.20306309, 0.20123854, 0.19942950,
177 0.19763650, 0.19585993, 0.19410009,
178 0.19235719, 0.19063135, 0.18892259,
179 0.18723083, 0.18555593, 0.18389763,
180 0.18225561, 0.18062949, 0.17901879,
181 0.17742298, 0.17584148, 0.17427363,
182 0.17271876, 0.17117615, 0.16964573,
183 0.16812641, 0.16661710, 0.16511703,
184 0.16362543, 0.16214155, 0.16066467,
185 0.15919413, 0.15772933, 0.15626973,
186 0.15481488, 0.15336445, 0.15191820,
187 0.15047605, 0.14903918, 0.14760731,
188 0.14618026, 0.14475863, 0.14334327,
189 0.14193527, 0.14053599, 0.13914708,
190 0.13777048, 0.13640850, 0.13506561,
191 0.13374299, 0.13244401, 0.13117249,
192 0.12993270, 0.12872938, 0.12756771,
193 0.12645338, 0.12539383, 0.12439474,
194 0.12346281, 0.12260562, 0.12183122,
195 0.12114807, 0.12056501, 0.12009154,
196 0.11973756, 0.11951163, 0.11942341,
197 0.11948255, 0.11969858, 0.12008079,
198 0.12063824, 0.12137972, 0.12231244,
199 0.12344358, 0.12477953, 0.12632581,
200 0.12808703, 0.13006688, 0.13226797,
201 0.13469183, 0.13733921, 0.14020991,
202 0.14330291, 0.14661640, 0.15014782,
203 0.15389405, 0.15785146, 0.16201598,
204 0.16638320, 0.17094840, 0.17570671,
205 0.18065314, 0.18578266, 0.19109018,
206 0.19657063, 0.20221902, 0.20803045,
207 0.21400015, 0.22012381, 0.22639690,
208 0.23281498, 0.23937390, 0.24606968,
209 0.25289851, 0.25985676, 0.26694127,
210 0.27414922, 0.28147681, 0.28892102,
211 0.29647899, 0.30414796, 0.31192534,
212 0.31980860, 0.32779580, 0.33588539,
213 0.34407411, 0.35235985, 0.36074053,
214 0.36921420, 0.37777892, 0.38643282,
215 0.39517408, 0.40400101, 0.41291350,
216 0.42190813, 0.43098317, 0.44013691,
217 0.44936763, 0.45867362, 0.46805314,
218 0.47750446, 0.48702580, 0.49661536,
219 0.50627130, 0.51599182, 0.52577622,
220 0.53562110, 0.54552440, 0.55548397,
221 0.56549760, 0.57556297, 0.58567772,
222 0.59583934, 0.60604528, 0.61629283,
223 0.62657923, 0.63690157, 0.64725685,
224 0.65764197, 0.66805369, 0.67848868,
225 0.68894351, 0.69941463, 0.70989842,
226 0.72039115, 0.73088902, 0.74138803,
227 0.75188414, 0.76237342, 0.77285183,
228 0.78331535, 0.79375994, 0.80418159,
229 0.81457634, 0.82494028, 0.83526959,
230 0.84556056, 0.85580960, 0.86601325,
231 0.87616824, 0.88627146, 0.89632002,
232 0.90631121, 0.91624212, 0.92610579,
233 0.93590444, 0.94563626, 0.95529972,
234 0.96489353, 0.97441665, 0.98386829,
235 0.99324789, 0.00487433, 0.00960483,
236 0.01462494, 0.01994186, 0.02556309,
237 0.03149748, 0.03775181, 0.04416723,
238 0.05034437, 0.05632444, 0.06214536,
239 0.06783587, 0.07341724, 0.07890703,
240 0.08431970, 0.08966622, 0.09495545,
241 0.10019576, 0.10539345, 0.11055307,
242 0.11567966, 0.12077701, 0.12584799,
243 0.13089477, 0.13592005, 0.14092556,
244 0.14591233, 0.15088147, 0.15583425,
245 0.16077132, 0.16569272, 0.17059884,
246 0.17549020, 0.18036684, 0.18522836,
247 0.19007447, 0.19490540, 0.19972086,
248 0.20452049, 0.20930306, 0.21406899,
249 0.21881782, 0.22354911, 0.22826210,
250 0.23295593, 0.23763078, 0.24228619,
251 0.24692170, 0.25153685, 0.25613040,
252 0.26070284, 0.26525384, 0.26978306,
253 0.27429024, 0.27877509, 0.28323662,
254 0.28767547, 0.29209154, 0.29648471,
255 0.30085494, 0.30520222, 0.30952657,
256 0.31382773, 0.31810580, 0.32236127,
257 0.32659432, 0.33080515, 0.33499400,
258 0.33916114, 0.34330688, 0.34743154,
259 0.35153548, 0.35561907, 0.35968273,
260 0.36372671, 0.36775151, 0.37175775,
261 0.37574589, 0.37971644, 0.38366989,
262 0.38760678, 0.39152762, 0.39543297,
263 0.39932336, 0.40319934, 0.40706148,
264 0.41091033, 0.41474645, 0.41857040,
265 0.42238275, 0.42618405, 0.42997486,
266 0.43375572, 0.43752720, 0.44128981,
267 0.44504410, 0.44879060, 0.45252980,
268 0.45626209, 0.45998802, 0.46370813,
269 0.46742290, 0.47113278, 0.47483821,
270 0.47853961, 0.48223740, 0.48593197,
271 0.48962370, 0.49331293, 0.49700003,
272 0.50068529, 0.50436904, 0.50805136,
273 0.51173263, 0.51541316, 0.51909319,
274 0.52277292, 0.52645254, 0.53013219,
275 0.53381201, 0.53749213, 0.54117264,
276 0.54485335, 0.54853458, 0.55221637,
277 0.55589872, 0.55958162, 0.56326503,
278 0.56694891, 0.57063316, 0.57431754,
279 0.57800205, 0.58168661, 0.58537105,
280 0.58905521, 0.59273889, 0.59642187,
281 0.60010387, 0.60378459, 0.60746388,
282 0.61114146, 0.61481702, 0.61849025,
283 0.62216081, 0.62582833, 0.62949242,
284 0.63315277, 0.63680899, 0.64046069,
285 0.64410744, 0.64774881, 0.65138436,
286 0.65501363, 0.65863619, 0.66225157,
287 0.66585927, 0.66945881, 0.67304968,
288 0.67663139, 0.68020343, 0.68376525,
289 0.68731632, 0.69085611, 0.69438405,
290 0.69789960, 0.70140222, 0.70489133,
291 0.70836635, 0.71182668, 0.71527175,
292 0.71870095, 0.72211371, 0.72550945,
293 0.72888753, 0.73224735, 0.73558828,
294 0.73890972, 0.74221104, 0.74549162,
295 0.74875084, 0.75198807, 0.75520266,
296 0.75839399, 0.76156142, 0.76470433,
297 0.76782207, 0.77091403, 0.77397953,
298 0.77701790, 0.78002855, 0.78301086,
299 0.78596419, 0.78888793, 0.79178146,
300 0.79464415, 0.79747541, 0.80027461,
301 0.80304099, 0.80577412, 0.80847343,
302 0.81113836, 0.81376835, 0.81636288,
303 0.81892143, 0.82144351, 0.82392862,
304 0.82637633, 0.82878621, 0.83115784,
305 0.83349064, 0.83578452, 0.83803918,
306 0.84025437, 0.84242990, 0.84456561,
307 0.84666139, 0.84871722, 0.85073310,
308 0.85270912, 0.85464543, 0.85654226,
309 0.85839991, 0.86021878, 0.86199932,
310 0.86374211, 0.86544779, 0.86711711,
311 0.86875092, 0.87035015, 0.87191584,
312 0.87344918, 0.87495143, 0.87642392,
313 0.87786808, 0.87928545, 0.88067763,
314 0.88204632, 0.88339329, 0.88472036,
315 0.88602943, 0.88732243, 0.88860134,
316 0.88986815, 0.89112487, 0.89237353,
317 0.89361614, 0.89485467, 0.89609127,
318 0.89732977, 0.89857040, 0.89981500,
319 0.90106534, 0.90232311, 0.90358991,
320 0.90486726, 0.90615657, 0.32941519,
321 0.33542652, 0.34137895, 0.34726862,
322 0.35309303, 0.35885256, 0.36454323,
323 0.37016418, 0.37571452, 0.38119074,
324 0.38659204, 0.39191723, 0.39716349,
325 0.40232944, 0.40741404, 0.41241521,
326 0.41733086, 0.42216032, 0.42690202,
327 0.43155375, 0.43611482, 0.44058404,
328 0.44496000, 0.44924127, 0.45342734,
329 0.45751726, 0.46150995, 0.46540474,
330 0.46920128, 0.47289909, 0.47649762,
331 0.47999675, 0.48339654, 0.48669702,
332 0.48989831, 0.49300074, 0.49600488,
333 0.49891131, 0.50172076, 0.50443413,
334 0.50705243, 0.50957678, 0.51200840,
335 0.51434870, 0.51659930, 0.51876163,
336 0.52083736, 0.52282822, 0.52473609,
337 0.52656332, 0.52831152, 0.52998273,
338 0.53157905, 0.53310261, 0.53455561,
339 0.53594093, 0.53726018, 0.53851561,
340 0.53970946, 0.54084398, 0.54192140,
341 0.54294396, 0.54391424, 0.54483444,
342 0.54570633, 0.54653200, 0.54731353,
343 0.54805291, 0.54875211, 0.54941304,
344 0.55003755, 0.55062743, 0.55118440,
345 0.55171011, 0.55220646, 0.55267486,
346 0.55311653, 0.55353282, 0.55392505,
347 0.55429441, 0.55464205, 0.55496905,
348 0.55527637, 0.55556494, 0.55583559,
349 0.55608907, 0.55632606, 0.55654717,
350 0.55675292, 0.55694377, 0.55712010,
351 0.55728221, 0.55743035, 0.55756466,
352 0.55768526, 0.55779216, 0.55788532,
353 0.55796464, 0.55803034, 0.55808199,
354 0.55811913, 0.55814141, 0.55814842,
355 0.55813967, 0.55811466, 0.55807280,
356 0.55801347, 0.55793600, 0.55783967,
357 0.55772371, 0.55758733, 0.55742968,
358 0.55725050, 0.55704861, 0.55682271,
359 0.55657181, 0.55629491, 0.55599097,
360 0.55565893, 0.55529773, 0.55490625,
361 0.55448339, 0.55402906, 0.55354108,
362 0.55301828, 0.55245948, 0.55186354,
363 0.55122927, 0.55055551, 0.54984110,
364 0.54908564, 0.54828740, 0.54744498,
365 0.54655722, 0.54562298, 0.54464114,
366 0.54361058, 0.54253043, 0.54139999,
367 0.54021751, 0.53898192, 0.53769219,
368 0.53634733, 0.53494633, 0.53348834,
369 0.53197275, 0.53039808, 0.52876343,
370 0.52706792, 0.52531069, 0.52349092,
371 0.52160791, 0.51966086, 0.51764880,
372 0.51557101, 0.51342680, 0.51121549,
373 0.50893644, 0.50658890, 0.50417217,
374 0.50168574, 0.49912906, 0.49650163,
375 0.49380294, 0.49103252, 0.48818938,
376 0.48527326, 0.48228395, 0.47922108,
377 0.47608431, 0.47287330, 0.46958774,
378 0.46622638, 0.46278934, 0.45927675,
379 0.45568838, 0.45202405, 0.44828355,
380 0.44446673, 0.44057284, 0.43660090,
381 0.43255207, 0.42842626, 0.42422341,
382 0.41994346, 0.41558638, 0.41115215,
383 0.40664011, 0.40204917, 0.39738103,
384 0.39263579, 0.38781353, 0.38291438,
385 0.37793850, 0.37288606, 0.36775726,
386 0.36255223, 0.35726893, 0.35191009,
387 0.34647607, 0.34096730, 0.33538426,
388 0.32972749, 0.32399761, 0.31819529,
389 0.31232133, 0.30637661, 0.30036211,
390 0.29427888, 0.28812650, 0.28190832,
391 0.27562602, 0.26928147, 0.26287683,
392 0.25641457, 0.24989748, 0.24332878,
393 0.23671214, 0.23005179, 0.22335258,
394 0.21662012, 0.20986086, 0.20308229,
395 0.19629307, 0.18950326, 0.18272455,
396 0.17597055, 0.16925712, 0.16260273,
397 0.15602894, 0.14956101, 0.14322828,
398 0.13706449, 0.13110864, 0.12540538,
399 0.12000532, 0.11496505, 0.11034678,
400 0.10621724, 0.10264590, 0.09970219,
401 0.09745186, 0.09595277, 0.09525046,
402 0.09537439, 0.09633538, 0.09812496,
403 0.10071680, 0.10407067, 0.10813094,
404 0.11283773, 0.11812832, 0.12394051,
405 0.13021494, 0.13689671, 0.14393620,
406 };
407
408 // It would be nice if Matrix had a ctor allowing to do the
409 // following without a copy
410 Matrix cmap (256, 3, 0.0);
411 std::copy (cmapv, cmapv + (256*3), cmap.rwdata ());
412 return cmap;
413}
414
415/*
416## Test default colormap returns a 256-color viridis map
417%!test
418%! hf = figure ("visible", "off");
419%! unwind_protect
420%! hax = axes ("parent", hf);
421%! assert (get (hax, "colormap"), viridis (256));
422%! unwind_protect_cleanup
423%! delete (hf);
424%! end_unwind_protect
425*/
426
427static double
428default_screendepth ()
429{
430 octave::display_info& dpy_info = octave::__get_display_info__ ();
431
432 return dpy_info.depth ();
433}
434
435static Matrix
436default_screensize ()
437{
438 Matrix retval (1, 4);
439
440 octave::display_info& dpy_info = octave::__get_display_info__ ();
441
442 retval(0) = 1.0;
443 retval(1) = 1.0;
444 retval(2) = dpy_info.width ();
445 retval(3) = dpy_info.height ();
446
447 return retval;
448}
449
450static double
451default_screenpixelsperinch ()
452{
453 octave::display_info& dpy_info = octave::__get_display_info__ ();
454
455 return (dpy_info.x_dpi () + dpy_info.y_dpi ()) / 2;
456}
457
458static Matrix
459default_colororder ()
460{
461 Matrix retval (7, 3, 0.0);
462
463 retval(0, 1) = 0.447;
464 retval(0, 2) = 0.741;
465
466 retval(1, 0) = 0.850;
467 retval(1, 1) = 0.325;
468 retval(1, 2) = 0.098;
469
470 retval(2, 0) = 0.929;
471 retval(2, 1) = 0.694;
472 retval(2, 2) = 0.125;
473
474 retval(3, 0) = 0.494;
475 retval(3, 1) = 0.184;
476 retval(3, 2) = 0.556;
477
478 retval(4, 0) = 0.466;
479 retval(4, 1) = 0.674;
480 retval(4, 2) = 0.188;
481
482 retval(5, 0) = 0.301;
483 retval(5, 1) = 0.745;
484 retval(5, 2) = 0.933;
485
486 retval(6, 0) = 0.635;
487 retval(6, 1) = 0.078;
488 retval(6, 2) = 0.184;
489
490 return retval;
491}
492
493static Matrix
494default_lim (bool logscale = false)
495{
496 Matrix m (1, 2);
497
498 if (logscale)
499 {
500 m(0) = 0.1;
501 m(1) = 1.0;
502 }
503 else
504 {
505 m(0) = 0.0;
506 m(1) = 1.0;
507 }
508
509 return m;
510}
511
512static Matrix
513default_data ()
514{
515 Matrix retval (1, 2);
516
517 retval(0) = 0;
518 retval(1) = 1;
519
520 return retval;
521}
522
523static Matrix
524default_data_lim ()
525{
526 Matrix retval (1, 4);
527
528 retval(0) = 0;
529 retval(1) = 1;
530 retval(2) = 1; // minimum positive
531 retval(3) = -octave::numeric_limits<double>::Inf (); // maximum negative
532
533 return retval;
534}
535
536static Matrix
537default_image_cdata ()
538{
539 Matrix m (64, 64);
540
541 int i = 0;
542 for (int col = 0; col < 64; col++)
543 for (int row = 0; row < 64; row++)
544 {
545 m(col, row) = static_cast<double> (default_im_data[i]);
546 i++;
547 }
548
549 return m;
550}
551
552static Matrix
553default_surface_xdata ()
554{
555 Matrix m (3, 3);
556
557 for (int col = 0; col < 3; col++)
558 for (int row = 0; row < 3; row++)
559 m(row, col) = col+1;
560
561 return m;
562}
563
564static Matrix
565default_surface_ydata ()
566{
567 Matrix m (3, 3);
568
569 for (int row = 0; row < 3; row++)
570 for (int col = 0; col < 3; col++)
571 m(row, col) = row+1;
572
573 return m;
574}
575
576static Matrix
577default_surface_zdata ()
578{
579 Matrix m (3, 3, 0.0);
580
581 for (int row = 0; row < 3; row++)
582 m(row, row) = 1.0;
583
584 return m;
585}
586
587static Matrix
588default_surface_cdata ()
589{
590 return default_surface_zdata ();
591}
592
593static Matrix
594default_patch_faces ()
595{
596 Matrix m (1, 3);
597
598 m(0) = 1.0;
599 m(1) = 2.0;
600 m(2) = 3.0;
601
602 return m;
603}
604
605static Matrix
606default_patch_vertices ()
607{
608 Matrix m (3, 2, 0.0);
609
610 m(1) = 1.0;
611 m(3) = 1.0;
612 m(4) = 1.0;
613
614 return m;
615}
616
617static Matrix
618default_patch_xdata ()
619{
620 Matrix m (3, 1, 0.0);
621
622 m(1) = 1.0;
623
624 return m;
625}
626
627static Matrix
628default_patch_ydata ()
629{
630 Matrix m (3, 1, 1.0);
631
632 m(2) = 0.0;
633
634 return m;
635}
636
637static Matrix
638default_axes_position ()
639{
640 Matrix m (1, 4);
641
642 m(0) = 0.13;
643 m(1) = 0.11;
644 m(2) = 0.775;
645 m(3) = 0.815;
646
647 return m;
648}
649
650static Matrix
651default_axes_outerposition ()
652{
653 Matrix m (1, 4);
654
655 m(0) = 0.0;
656 m(1) = 0.0;
657 m(2) = 1.0;
658 m(3) = 1.0;
659
660 return m;
661}
662
663static Matrix
664default_axes_view ()
665{
666 Matrix m (1, 2);
667
668 m(0) = 0.0;
669 m(1) = 90.0;
670
671 return m;
672}
673
674static Matrix
675default_axes_tick ()
676{
677 Matrix m (1, 6);
678
679 m(0) = 0.0;
680 m(1) = 0.2;
681 m(2) = 0.4;
682 m(3) = 0.6;
683 m(4) = 0.8;
684 m(5) = 1.0;
685
686 return m;
687}
688
689static Matrix
690default_axes_ticklength ()
691{
692 Matrix m (1, 2);
693
694 m(0) = 0.01;
695 m(1) = 0.025;
696
697 return m;
698}
699
700static Matrix
701default_figure_position ()
702{
703 Matrix m (1, 4);
704
705 m(0) = 300;
706 m(1) = 200;
707 m(2) = 560;
708 m(3) = 420;
709
710 return m;
711}
712
713static Matrix
714default_figure_papersize ()
715{
716 Matrix m (1, 2);
717
718 m(0) = 8.5;
719 m(1) = 11.0;
720
721 return m;
722}
723
724static Matrix
725default_figure_paperposition ()
726{
727 Matrix m (1, 4);
728
729 // Update if default_figure_position or default_figure_papersize change
730 m(0) = 1.3421852580027660;
731 m(1) = 3.3191389435020748;
732 m(2) = 5.8156294839944680;
733 m(3) = 4.3617221129958503;
734
735 return m;
736}
737
738static std::string
739default_graphics_toolkit ()
740{
741 octave::gtk_manager& gtk_mgr = octave::__get_gtk_manager__ ();
742
743 return gtk_mgr.default_toolkit ();
744}
745
746static Matrix
747default_control_position ()
748{
749 Matrix retval (1, 4);
750
751 retval(0) = 0;
752 retval(1) = 0;
753 retval(2) = 80;
754 retval(3) = 30;
755
756 return retval;
757}
758
759static Matrix
760default_control_sliderstep ()
761{
762 Matrix retval (1, 2);
763
764 retval(0) = 0.01;
765 retval(1) = 0.1;
766
767 return retval;
768}
769
770static Matrix
771default_panel_position ()
772{
773 Matrix retval (1, 4);
774
775 retval(0) = 0;
776 retval(1) = 0;
777 retval(2) = 1;
778 retval(3) = 1;
779
780 return retval;
781}
782
783static Matrix
784default_light_position ()
785{
786 Matrix m (1, 3);
787
788 m(0) = 1.0;
789 m(1) = 0.0;
790 m(2) = 1.0;
791
792 return m;
793}
794
795static Matrix
796default_table_position ()
797{
798 Matrix retval (1, 4);
799
800 retval(0) = 20;
801 retval(1) = 20;
802 retval(2) = 300;
803 retval(3) = 300;
804
805 return retval;
806}
807
808static Matrix
809default_table_backgroundcolor ()
810{
811 Matrix retval (2, 3);
812 retval(0, 0) = 1;
813 retval(0, 1) = 1;
814 retval(0, 2) = 1;
815 retval(1, 0) = 0.94;
816 retval(1, 1) = 0.94;
817 retval(1, 2) = 0.94;
818 return retval;
819}
820
821static graphics_handle
822make_graphics_handle (const std::string& go_name,
823 const graphics_handle& parent,
824 bool integer_figure_handle = false,
825 bool call_createfcn = true,
826 bool notify_toolkit = true)
827{
828 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
829
830 return gh_mgr.make_graphics_handle (go_name, parent, integer_figure_handle,
831 call_createfcn, notify_toolkit);
832}
833
834static double
835convert_font_size (double font_size, const caseless_str& from_units,
836 const caseless_str& to_units, double parent_height = 0)
837{
838 // Simple case where from_units == to_units
839
840 if (from_units.compare (to_units))
841 return font_size;
842
843 // Converts the given fontsize using the following transformation:
844 // <old_font_size> => points => <new_font_size>
845
846 double points_size = 0;
847 double res = 0;
848
849 if (from_units.compare ("points"))
850 points_size = font_size;
851 else
852 {
853 res = xget (0, "screenpixelsperinch").double_value ();
854
855 if (from_units.compare ("pixels"))
856 points_size = font_size * 72.0 / res;
857 else if (from_units.compare ("inches"))
858 points_size = font_size * 72.0;
859 else if (from_units.compare ("centimeters"))
860 points_size = font_size * 72.0 / 2.54;
861 else if (from_units.compare ("normalized"))
862 points_size = font_size * parent_height * 72.0 / res;
863 }
864
865 double new_font_size = 0;
866
867 if (to_units.compare ("points"))
868 new_font_size = points_size;
869 else
870 {
871 if (res <= 0)
872 res = xget (0, "screenpixelsperinch").double_value ();
873
874 if (to_units.compare ("pixels"))
875 new_font_size = points_size * res / 72.0;
876 else if (to_units.compare ("inches"))
877 new_font_size = points_size / 72.0;
878 else if (to_units.compare ("centimeters"))
879 new_font_size = points_size * 2.54 / 72.0;
880 else if (to_units.compare ("normalized"))
881 {
882 // Avoid setting font size to (0/0) = NaN
883
884 if (parent_height > 0)
885 new_font_size = points_size * res / (parent_height * 72.0);
886 }
887 }
888
889 return new_font_size;
890}
891
892static Matrix
893convert_position (const Matrix& pos, const caseless_str& from_units,
894 const caseless_str& to_units, const Matrix& parent_dim)
895{
896 Matrix retval (1, pos.numel (), 0.0);
897 double res = 0;
898 bool is_rectangle = (pos.numel () == 4);
899 bool is_2D = (pos.numel () == 2);
900
901 if (from_units.compare ("pixels"))
902 retval = pos;
903 else if (from_units.compare ("normalized"))
904 {
905 retval(0) = pos(0) * parent_dim(0) + 1;
906 retval(1) = pos(1) * parent_dim(1) + 1;
907 if (is_rectangle)
908 {
909 retval(2) = pos(2) * parent_dim(0);
910 retval(3) = pos(3) * parent_dim(1);
911 }
912 else if (! is_2D)
913 retval(2) = 0;
914 }
915 else if (from_units.compare ("characters"))
916 {
917 if (res <= 0)
918 res = xget (0, "screenpixelsperinch").double_value ();
919
920 double f = 0.0;
921
922 // FIXME: this assumes the system font is Helvetica 10pt
923 // (for which "x" requires 6x12 pixels at 74.951 pixels/inch)
924 f = 12.0 * res / 74.951;
925
926 if (f > 0)
927 {
928 retval(0) = 0.5 * pos(0) * f;
929 retval(1) = pos(1) * f;
930 if (is_rectangle)
931 {
932 retval(2) = 0.5 * pos(2) * f;
933 retval(3) = pos(3) * f;
934 }
935 else if (! is_2D)
936 retval(2) = 0;
937 }
938 }
939 else
940 {
941 if (res <= 0)
942 res = xget (0, "screenpixelsperinch").double_value ();
943
944 double f = 0.0;
945
946 if (from_units.compare ("points"))
947 f = res / 72.0;
948 else if (from_units.compare ("inches"))
949 f = res;
950 else if (from_units.compare ("centimeters"))
951 f = res / 2.54;
952
953 if (f > 0)
954 {
955 retval(0) = pos(0) * f + 1;
956 retval(1) = pos(1) * f + 1;
957 if (is_rectangle)
958 {
959 retval(2) = pos(2) * f;
960 retval(3) = pos(3) * f;
961 }
962 else if (! is_2D)
963 retval(2) = 0;
964 }
965 }
966
967 if (! to_units.compare ("pixels"))
968 {
969 if (to_units.compare ("normalized"))
970 {
971 retval(0) = (retval(0) - 1) / parent_dim(0);
972 retval(1) = (retval(1) - 1) / parent_dim(1);
973 if (is_rectangle)
974 {
975 retval(2) /= parent_dim(0);
976 retval(3) /= parent_dim(1);
977 }
978 else if (! is_2D)
979 retval(2) = 0;
980 }
981 else if (to_units.compare ("characters"))
982 {
983 if (res <= 0)
984 res = xget (0, "screenpixelsperinch").double_value ();
985
986 double f = 0.0;
987
988 f = 12.0 * res / 74.951;
989
990 if (f > 0)
991 {
992 retval(0) = 2 * retval(0) / f;
993 retval(1) = retval(1) / f;
994 if (is_rectangle)
995 {
996 retval(2) = 2 * retval(2) / f;
997 retval(3) = retval(3) / f;
998 }
999 else if (! is_2D)
1000 retval(2) = 0;
1001 }
1002 }
1003 else
1004 {
1005 if (res <= 0)
1006 res = xget (0, "screenpixelsperinch").double_value ();
1007
1008 double f = 0.0;
1009
1010 if (to_units.compare ("points"))
1011 f = res / 72.0;
1012 else if (to_units.compare ("inches"))
1013 f = res;
1014 else if (to_units.compare ("centimeters"))
1015 f = res / 2.54;
1016
1017 if (f > 0)
1018 {
1019 retval(0) = (retval(0) - 1) / f;
1020 retval(1) = (retval(1) - 1) / f;
1021 if (is_rectangle)
1022 {
1023 retval(2) /= f;
1024 retval(3) /= f;
1025 }
1026 else if (! is_2D)
1027 retval(2) = 0;
1028 }
1029 }
1030 }
1031
1032 return retval;
1033}
1034
1035static Matrix
1036convert_text_position (const Matrix& pos, const text::properties& props,
1037 const caseless_str& from_units,
1038 const caseless_str& to_units)
1039{
1040 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
1041
1042 graphics_object go = gh_mgr.get_object (props.get___myhandle__ ());
1043
1044 graphics_object ax = go.get_ancestor ("axes");
1045
1046 Matrix retval;
1047
1048 if (ax.valid_object ())
1049 {
1050 const axes::properties& ax_props
1051 = dynamic_cast<const axes::properties&> (ax.get_properties ());
1052 graphics_xform ax_xform = ax_props.get_transform ();
1053 bool is_rectangle = (pos.numel () == 4);
1054 Matrix ax_bbox = ax_props.get_boundingbox (true),
1055 ax_size = ax_bbox.extract_n (0, 2, 1, 2);
1056
1057 if (from_units.compare ("data"))
1058 {
1059 if (is_rectangle)
1060 {
1061 ColumnVector v1 = ax_xform.transform (pos(0), pos(1), 0),
1062 v2 = ax_xform.transform (pos(0) + pos(2),
1063 pos(1) + pos(3), 0);
1064
1065 retval.resize (1, 4);
1066
1067 retval(0) = v1(0) - ax_bbox(0) + 1;
1068 retval(1) = ax_bbox(1) + ax_bbox(3) - v1(1) + 1;
1069 retval(2) = v2(0) - v1(0);
1070 retval(3) = v1(1) - v2(1);
1071 }
1072 else
1073 {
1074 ColumnVector v = ax_xform.transform (pos(0), pos(1), pos(2));
1075
1076 retval.resize (1, 3);
1077
1078 retval(0) = v(0) - ax_bbox(0) + 1;
1079 retval(1) = ax_bbox(1) + ax_bbox(3) - v(1) + 1;
1080 retval(2) = 0;
1081 }
1082 }
1083 else
1084 retval = convert_position (pos, from_units, "pixels", ax_size);
1085
1086 if (! to_units.compare ("pixels"))
1087 {
1088 if (to_units.compare ("data"))
1089 {
1090 if (is_rectangle)
1091 {
1092 ColumnVector v1, v2;
1093 v1 = ax_xform.untransform (retval(0) + ax_bbox(0) - 1,
1094 ax_bbox(1) + ax_bbox(3) - retval(1) + 1);
1095 v2 = ax_xform.untransform (retval(0) + retval(2) + ax_bbox(0) - 1,
1096 ax_bbox(1) + ax_bbox(3) - (retval(1) + retval(3)) + 1);
1097
1098 retval.resize (1, 4);
1099
1100 retval(0) = v1(0);
1101 retval(1) = v1(1);
1102 retval(2) = v2(0) - v1(0);
1103 retval(3) = v2(1) - v1(1);
1104 }
1105 else
1106 {
1107 ColumnVector v;
1108 v = ax_xform.untransform (retval(0) + ax_bbox(0) - 1,
1109 ax_bbox(1) + ax_bbox(3) - retval(1) + 1);
1110
1111 retval.resize (1, 3);
1112
1113 retval(0) = v(0);
1114 retval(1) = v(1);
1115 retval(2) = v(2);
1116 }
1117 }
1118 else
1119 retval = convert_position (retval, "pixels", to_units, ax_size);
1120 }
1121 }
1122
1123 return retval;
1124}
1125
1126// This function always returns the screensize in pixels
1127static Matrix
1128screen_size_pixels ()
1129{
1130 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
1131
1132 graphics_object obj = gh_mgr.get_object (0);
1133
1134 Matrix sz = obj.get ("screensize").matrix_value ();
1135
1136 return convert_position (sz, obj.get ("units").string_value (), "pixels",
1137 sz.extract_n (0, 2, 1, 2)).extract_n (0, 2, 1, 2);
1138}
1139
1140static double
1141device_pixel_ratio (graphics_handle h)
1142{
1143 double retval = 1.0;
1144
1145 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
1146
1147 graphics_object fig = gh_mgr.get_object (h).get_ancestor ("figure");
1148
1149 if (fig.valid_object ())
1150 retval = fig.get ("__device_pixel_ratio__").double_value ();
1151
1152 return retval;
1153}
1154
1155static void
1156convert_cdata_2 (bool is_scaled, bool is_real, double clim_0, double clim_1,
1157 const double *cmapv, double x, octave_idx_type lda,
1158 octave_idx_type nc, octave_idx_type i, double *av)
1159{
1160 if (is_scaled)
1161 x = octave::math::fix (nc * (x - clim_0) / (clim_1 - clim_0));
1162 else if (is_real)
1163 x = octave::math::fix (x - 1);
1164
1165 if (octave::math::isnan (x))
1166 {
1167 av[i] = x;
1168 av[i+lda] = x;
1169 av[i+2*lda] = x;
1170 }
1171 else
1172 {
1173 if (x < 0)
1174 x = 0;
1175 else if (x >= nc)
1176 x = (nc - 1);
1177
1178 octave_idx_type idx = static_cast<octave_idx_type> (x);
1179
1180 av[i] = cmapv[idx];
1181 av[i+lda] = cmapv[idx+nc];
1182 av[i+2*lda] = cmapv[idx+2*nc];
1183 }
1184}
1185
1186template <typename T>
1187void
1188convert_cdata_1 (bool is_scaled, bool is_real, double clim_0, double clim_1,
1189 const double *cmapv, const T *cv, octave_idx_type lda,
1190 octave_idx_type nc, double *av)
1191{
1192 for (octave_idx_type i = 0; i < lda; i++)
1193 convert_cdata_2 (is_scaled, is_real,
1194 clim_0, clim_1, cmapv, cv[i], lda, nc, i, av);
1195}
1196
1197static octave_value
1198convert_cdata (const base_properties& props, const octave_value& cdata,
1199 bool is_scaled, int cdim)
1200{
1201 dim_vector dv (cdata.dims ());
1202
1203 // TrueColor data doesn't require conversion
1204 if (dv.ndims () == cdim && dv(cdim-1) == 3)
1205 return cdata;
1206
1207 Matrix cmap (1, 3, 0.0);
1208 Matrix clim (1, 2, 0.0);
1209
1210 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
1211
1212 graphics_object go = gh_mgr.get_object (props.get___myhandle__ ());
1213 graphics_object ax = go.get_ancestor ("axes");
1214
1215 if (ax.valid_object ())
1216 {
1217 Matrix _cmap = ax.get (caseless_str ("colormap")).matrix_value ();
1218
1219 cmap = _cmap;
1220
1221 if (is_scaled)
1222 {
1223 Matrix _clim = ax.get (caseless_str ("clim")).matrix_value ();
1224
1225 clim = _clim;
1226 }
1227 }
1228
1229 dv.resize (cdim);
1230 dv(cdim-1) = 3;
1231
1232 NDArray a (dv);
1233
1234 octave_idx_type lda = a.numel () / static_cast<octave_idx_type> (3);
1235 octave_idx_type nc = cmap.rows ();
1236
1237 double *av = a.rwdata ();
1238 const double *cmapv = cmap.data ();
1239
1240 double clim_0 = clim(0);
1241 double clim_1 = clim(1);
1242
1243 // FIXME: There is a lot of processing time spent just on data conversion
1244 // both here in graphics.cc and again in gl-render.cc. There must
1245 // be room for improvement! Here a macro expands to a templated
1246 // function which in turn calls another function (covert_cdata_2).
1247 // And in gl-render.cc (opengl_renderer::draw_image), only GLfloat
1248 // is supported anyways so there is another double for loop across
1249 // height and width to convert all of the input data to GLfloat.
1250
1251#define CONVERT_CDATA_1(ARRAY_T, VAL_FN, IS_REAL) \
1252 do \
1253 { \
1254 ARRAY_T tmp = cdata. VAL_FN ## array_value (); \
1255 \
1256 convert_cdata_1 (is_scaled, IS_REAL, clim_0, clim_1, cmapv, \
1257 tmp.data (), lda, nc, av); \
1258 } \
1259 while (0)
1260
1261 if (cdata.is_int8_type ())
1262 CONVERT_CDATA_1 (int8NDArray, int8_, false);
1263 else if (cdata.is_int16_type ())
1264 CONVERT_CDATA_1 (int16NDArray, int16_, false);
1265 else if (cdata.is_int32_type ())
1266 CONVERT_CDATA_1 (int32NDArray, int32_, false);
1267 else if (cdata.is_int64_type ())
1268 CONVERT_CDATA_1 (int64NDArray, int64_, false);
1269 else if (cdata.is_uint8_type ())
1270 CONVERT_CDATA_1 (uint8NDArray, uint8_, false);
1271 else if (cdata.is_uint16_type ())
1272 CONVERT_CDATA_1 (uint16NDArray, uint16_, false);
1273 else if (cdata.is_uint32_type ())
1274 CONVERT_CDATA_1 (uint32NDArray, uint32_, false);
1275 else if (cdata.is_uint64_type ())
1276 CONVERT_CDATA_1 (uint64NDArray, uint64_, false);
1277 else if (cdata.is_double_type ())
1278 CONVERT_CDATA_1 (NDArray,, true);
1279 else if (cdata.is_single_type ())
1280 CONVERT_CDATA_1 (FloatNDArray, float_, true);
1281 else if (cdata.islogical ())
1282 CONVERT_CDATA_1 (boolNDArray, bool_, false);
1283 else
1284 {
1285 // Don't throw an error; leads to an incomplete FLTK object (bug #46933).
1286 warning ("unsupported type for cdata (= %s). "
1287 "Valid types are int8, int16, int32, int64, uint8, uint16, "
1288 "uint32, uint64, double, single, and bool.",
1289 cdata.type_name ().c_str ());
1290 a = NDArray (dv, 0); // return 0 instead
1291 }
1292
1293#undef CONVERT_CDATA_1
1294
1295 return octave_value (a);
1296}
1297
1298template <typename T>
1299static void
1300get_array_limits (const Array<T>& m, double& emin, double& emax,
1301 double& eminp, double& emaxp)
1302{
1303 const T *data = m.data ();
1304 octave_idx_type n = m.numel ();
1305
1306 for (octave_idx_type i = 0; i < n; i++)
1307 {
1308 double e = double (data[i]);
1309
1310 // Don't need to test for NaN here as NaN>x and NaN<x is always false
1311 if (! octave::math::isinf (e))
1312 {
1313 if (e < emin)
1314 emin = e;
1315
1316 if (e > emax)
1317 emax = e;
1318
1319 if (e > 0 && e < eminp)
1320 eminp = e;
1321
1322 if (e < 0 && e > emaxp)
1323 emaxp = e;
1324 }
1325 }
1326}
1327
1328static bool
1329lookup_object_name (const caseless_str& name, caseless_str& go_name,
1330 caseless_str& rest)
1331{
1332 int len = name.length ();
1333 int offset = 0;
1334 bool result = false;
1335
1336 if (len >= 4)
1337 {
1338 caseless_str pfx = name.substr (0, 4);
1339
1340 if (pfx.compare ("axes") || pfx.compare ("line")
1341 || pfx.compare ("text"))
1342 offset = 4;
1343 else if (len >= 5)
1344 {
1345 pfx = name.substr (0, 5);
1346
1347 if (pfx.compare ("image") || pfx.compare ("patch"))
1348 offset = 5;
1349 else if (len >= 6)
1350 {
1351 pfx = name.substr (0, 6);
1352
1353 if (pfx.compare ("figure") || pfx.compare ("uimenu"))
1354 offset = 6;
1355 else if (len >= 7)
1356 {
1357 pfx = name.substr (0, 7);
1358
1359 if (pfx.compare ("surface") || pfx.compare ("scatter")
1360 || pfx.compare ("hggroup") || pfx.compare ("uipanel")
1361 || pfx.compare ("uitable"))
1362 offset = 7;
1363 else if (len >= 9)
1364 {
1365 pfx = name.substr (0, 9);
1366
1367 if (pfx.compare ("uicontrol")
1368 || pfx.compare ("uitoolbar"))
1369 offset = 9;
1370 else if (len >= 10)
1371 {
1372 pfx = name.substr (0, 10);
1373
1374 if (pfx.compare ("uipushtool"))
1375 offset = 10;
1376 else if (len >= 12)
1377 {
1378 pfx = name.substr (0, 12);
1379
1380 if (pfx.compare ("uitoggletool"))
1381 offset = 12;
1382 else if (len >= 13)
1383 {
1384 pfx = name.substr (0, 13);
1385
1386 if (pfx.compare ("uicontextmenu")
1387 || pfx.compare ("uibuttongroup"))
1388 offset = 13;
1389 }
1390 }
1391 }
1392 }
1393 }
1394 }
1395 }
1396
1397 if (offset > 0)
1398 {
1399 go_name = pfx;
1400 rest = name.substr (offset);
1401 result = true;
1402 }
1403 }
1404
1405 return result;
1406}
1407
1408base_graphics_object *
1410 const graphics_handle& h,
1411 const graphics_handle& p)
1412{
1413 base_graphics_object *go = nullptr;
1414
1415 if (type.compare ("figure"))
1416 go = new figure (h, p);
1417 else if (type.compare ("axes"))
1418 go = new axes (h, p);
1419 else if (type.compare ("line"))
1420 go = new line (h, p);
1421 else if (type.compare ("text"))
1422 go = new text (h, p);
1423 else if (type.compare ("image"))
1424 go = new image (h, p);
1425 else if (type.compare ("light"))
1426 go = new light (h, p);
1427 else if (type.compare ("patch"))
1428 go = new patch (h, p);
1429 else if (type.compare ("scatter"))
1430 go = new scatter (h, p);
1431 else if (type.compare ("surface"))
1432 go = new surface (h, p);
1433 else if (type.compare ("hggroup"))
1434 go = new hggroup (h, p);
1435 else if (type.compare ("uimenu"))
1436 go = new uimenu (h, p);
1437 else if (type.compare ("uicontrol"))
1438 go = new uicontrol (h, p);
1439 else if (type.compare ("uipanel"))
1440 go = new uipanel (h, p);
1441 else if (type.compare ("uibuttongroup"))
1442 go = new uibuttongroup (h, p);
1443 else if (type.compare ("uicontextmenu"))
1444 go = new uicontextmenu (h, p);
1445 else if (type.compare ("uitable"))
1446 go = new uitable (h, p);
1447 else if (type.compare ("uitoolbar"))
1448 go = new uitoolbar (h, p);
1449 else if (type.compare ("uipushtool"))
1450 go = new uipushtool (h, p);
1451 else if (type.compare ("uitoggletool"))
1452 go = new uitoggletool (h, p);
1453 return go;
1454}
1455
1456// ---------------------------------------------------------------------
1457
1458bool
1459base_property::set (const octave_value& v, bool do_run, bool do_notify_toolkit)
1460{
1461 if (do_set (v))
1462 {
1463 // Notify graphics toolkit.
1464 if (m_id >= 0 && do_notify_toolkit)
1465 {
1466 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
1467
1468 graphics_object go = gh_mgr.get_object (m_parent);
1469 if (go)
1470 go.update (m_id);
1471 }
1472
1473 // run listeners
1474 if (do_run)
1475 run_listeners (GCB_POSTSET);
1476
1477 return true;
1478 }
1479
1480 return false;
1481}
1482
1483void
1484base_property::run_listeners (listener_mode mode)
1485{
1486 const octave_value_list& l = m_listeners[mode];
1487
1488 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
1489
1490 for (int i = 0; i < l.length (); i++)
1491 gh_mgr.execute_listener (m_parent, l(i));
1492}
1493
1494radio_values::radio_values (const std::string& opt_string)
1495 : m_default_val (), m_possible_vals ()
1496{
1497 std::size_t beg = 0;
1498 std::size_t len = opt_string.length ();
1499 bool done = len == 0;
1500
1501 while (! done)
1502 {
1503 std::size_t end = opt_string.find ('|', beg);
1504
1505 if (end == std::string::npos)
1506 {
1507 end = len;
1508 done = true;
1509 }
1510
1511 std::string t = opt_string.substr (beg, end-beg);
1512
1513 // Special case for '|' symbol itself
1514 if (t.empty () && opt_string[beg] == '|')
1515 {
1516 t = '|';
1517 end++;
1518 }
1519
1520 // Might want more error checking on parsing default value...
1521 if (t[0] == '{')
1522 {
1523 t = t.substr (1, t.length () - 2);
1524 m_default_val = t;
1525 }
1526 else if (beg == 0) // ensure default value
1527 m_default_val = t;
1528
1529 m_possible_vals.insert (t);
1530
1531 beg = end + 1;
1532 }
1533}
1534
1535std::string
1536radio_values::values_as_string () const
1537{
1538 std::string retval;
1539
1540 for (const auto& val : m_possible_vals)
1541 {
1542 if (retval.empty ())
1543 {
1544 if (val == default_value ())
1545 retval = '{' + val + '}';
1546 else
1547 retval = val;
1548 }
1549 else
1550 {
1551 if (val == default_value ())
1552 retval += " | {" + val + '}';
1553 else
1554 retval += " | " + val;
1555 }
1556 }
1557
1558 if (! retval.empty ())
1559 retval = "[ " + retval + " ]";
1560
1561 return retval;
1562}
1563
1564Cell
1565radio_values::values_as_cell () const
1566{
1567 octave_idx_type i = 0;
1568 Cell retval (nelem (), 1);
1569
1570 for (const auto& val : m_possible_vals)
1571 retval(i++) = std::string (val);
1572
1573 return retval;
1574}
1575
1576bool
1577color_values::str2rgb (const std::string& str_arg)
1578{
1579 bool retval = true;
1580
1581 double tmp_rgb[3] = {0, 0, 0};
1582
1583 std::string str = str_arg;
1584 unsigned int len = str.length ();
1585
1586 std::transform (str.begin (), str.end (), str.begin (), tolower);
1587
1588 // "blue" must precede black for Matlab compatibility
1589 if (str.compare (0, len, "blue", 0, len) == 0)
1590 tmp_rgb[2] = 1;
1591 else if (str.compare (0, len, "black", 0, len) == 0
1592 || str.compare (0, len, "k", 0, len) == 0)
1593 tmp_rgb[0] = tmp_rgb[1] = tmp_rgb[2] = 0;
1594 else if (str.compare (0, len, "red", 0, len) == 0)
1595 tmp_rgb[0] = 1;
1596 else if (str.compare (0, len, "green", 0, len) == 0)
1597 tmp_rgb[1] = 1;
1598 else if (str.compare (0, len, "yellow", 0, len) == 0)
1599 tmp_rgb[0] = tmp_rgb[1] = 1;
1600 else if (str.compare (0, len, "magenta", 0, len) == 0)
1601 tmp_rgb[0] = tmp_rgb[2] = 1;
1602 else if (str.compare (0, len, "cyan", 0, len) == 0)
1603 tmp_rgb[1] = tmp_rgb[2] = 1;
1604 else if (str.compare (0, len, "white", 0, len) == 0
1605 || str.compare (0, len, "w", 0, len) == 0)
1606 tmp_rgb[0] = tmp_rgb[1] = tmp_rgb[2] = 1;
1607 else if (str[0] == '#' && len == 7)
1608 {
1609 try
1610 {
1611 tmp_rgb[0] = static_cast<double> (stoi (str.substr (1, 2), nullptr, 16))
1612 / 255.0;
1613 tmp_rgb[1] = static_cast<double> (stoi (str.substr (3, 2), nullptr, 16))
1614 / 255.0;
1615 tmp_rgb[2] = static_cast<double> (stoi (str.substr (5, 2), nullptr, 16))
1616 / 255.0;
1617 }
1618 catch (const octave::execution_exception&)
1619 {
1620 retval = false;
1621 }
1622 catch (const std::invalid_argument&)
1623 {
1624 retval = false;
1625 }
1626
1627 }
1628 else if (str[0] == '#' && len == 4)
1629 {
1630 try
1631 {
1632 tmp_rgb[0] = static_cast<double> (stoi (str.substr (1, 1), nullptr, 16))
1633 / 15.0;
1634 tmp_rgb[1] = static_cast<double> (stoi (str.substr (2, 1), nullptr, 16))
1635 / 15.0;
1636 tmp_rgb[2] = static_cast<double> (stoi (str.substr (3, 1), nullptr, 16))
1637 / 15.0;
1638 }
1639 catch (const octave::execution_exception&)
1640 {
1641 retval = false;
1642 }
1643 catch (const std::invalid_argument&)
1644 {
1645 retval = false;
1646 }
1647 }
1648 else
1649 retval = false;
1650
1651 if (retval)
1652 {
1653 for (int i = 0; i < 3; i++)
1654 m_rgb(i) = tmp_rgb[i];
1655 }
1656
1657 return retval;
1658}
1659
1660bool
1661color_property::do_set (const octave_value& val)
1662{
1663 if (val.is_string ())
1664 {
1665 std::string s = val.string_value ();
1666
1667 if (s.empty ())
1668 error (R"(invalid value for color property "%s")",
1669 get_name ().c_str ());
1670
1671 std::string match;
1672
1673 if (m_radio_val.contains (s, match))
1674 {
1675 if (m_current_type != radio_t || match != m_current_val)
1676 {
1677 if (s.length () != match.length ())
1678 warning_with_id ("Octave:abbreviated-property-match",
1679 "%s: allowing %s to match %s value %s",
1680 "set", s.c_str (), get_name ().c_str (),
1681 match.c_str ());
1682 m_current_val = match;
1683 m_current_type = radio_t;
1684 return true;
1685 }
1686 }
1687 else
1688 {
1689 try
1690 {
1691 color_values col (s);
1692
1693 if (m_current_type != color_t || col != m_color_val)
1694 {
1695 m_color_val = col;
1696 m_current_type = color_t;
1697 return true;
1698 }
1699 }
1700 catch (octave::execution_exception& ee)
1701 {
1702 error (ee, R"(invalid value for color property "%s" (value = %s))",
1703 get_name ().c_str (), s.c_str ());
1704 }
1705 }
1706 }
1707 else if (val.isnumeric ())
1708 {
1709 Matrix m = val.matrix_value ();
1710
1711 if (m.numel () != 3)
1712 error (R"(invalid value for color property "%s")",
1713 get_name ().c_str ());
1714
1715 color_values col (m(0), m(1), m(2));
1716
1717 if (m_current_type != color_t || col != m_color_val)
1718 {
1719 m_color_val = col;
1720 m_current_type = color_t;
1721 return true;
1722 }
1723 }
1724 else
1725 error (R"(invalid value for color property "%s")",
1726 get_name ().c_str ());
1727
1728 return false;
1729}
1730
1731bool
1732double_radio_property::do_set (const octave_value& val)
1733{
1734 if (val.is_string ())
1735 {
1736 std::string s = val.string_value ();
1737 std::string match;
1738
1739 if (s.empty () || ! m_radio_val.contains (s, match))
1740 error (R"(invalid value for double_radio property "%s")",
1741 get_name ().c_str ());
1742
1743 if (m_current_type != radio_t || match != m_current_val)
1744 {
1745 if (s.length () != match.length ())
1746 warning_with_id ("Octave:abbreviated-property-match",
1747 "%s: allowing %s to match %s value %s",
1748 "set", s.c_str (), get_name ().c_str (),
1749 match.c_str ());
1750 m_current_val = match;
1751 m_current_type = radio_t;
1752 return true;
1753 }
1754 }
1755 else if (val.is_scalar_type () && val.isreal ())
1756 {
1757 double new_dval = val.double_value ();
1758
1759 if (m_current_type != double_t || new_dval != m_dval)
1760 {
1761 m_dval = new_dval;
1762 m_current_type = double_t;
1763 return true;
1764 }
1765 }
1766 else
1767 error (R"(invalid value for double_radio property "%s")",
1768 get_name ().c_str ());
1769
1770 return false;
1771}
1772
1773bool
1774array_property::validate (const octave_value& v)
1775{
1776 bool xok = false;
1777
1778 // check value type
1779 if (m_type_constraints.size () > 0)
1780 {
1781 if (m_type_constraints.find (v.class_name ()) != m_type_constraints.end ())
1782 xok = true;
1783
1784 // check if complex is allowed (it's also of class "double", so
1785 // checking that alone is not enough to ensure real type)
1786 if (m_type_constraints.find ("real") != m_type_constraints.end ()
1787 && v.iscomplex ())
1788 xok = false;
1789 }
1790 else
1791 xok = v.isnumeric () || v.is_bool_scalar ();
1792
1793 if (xok && m_size_constraints.size () > 0)
1794 {
1795 const dim_vector& vdims = v.dims ();
1796 int vlen = vdims.ndims ();
1797
1798 xok = false;
1799
1800 // check dimensional size constraints until a match is found
1801 for (auto it = m_size_constraints.cbegin ();
1802 ! xok && it != m_size_constraints.cend ();
1803 ++it)
1804 {
1805 dim_vector itdims = (*it);
1806
1807 if (itdims.ndims () == vlen)
1808 {
1809 xok = true;
1810
1811 for (int i = 0; xok && i < vlen; i++)
1812 {
1813 if (itdims(i) > 0)
1814 {
1815 if (itdims(i) != vdims(i))
1816 xok = false;
1817 }
1818 else if (itdims(i) == 0)
1819 {
1820 if (! v.isempty ())
1821 xok = false;
1822 break;
1823 }
1824 }
1825 }
1826 }
1827 }
1828
1829 if (xok)
1830 {
1831 NDArray v_mat = v.array_value ();
1832 // Check min and max
1833 if (! octave::math::isnan (m_minval.first))
1834 {
1835 for (octave_idx_type i = 0; i < v_mat.numel (); i++)
1836 if (m_minval.second && m_minval.first > v_mat(i))
1837 error (R"(set: "%s" must be greater than or equal to %g)",
1838 get_name ().c_str (), m_minval.first);
1839 else if (! m_minval.second && m_minval.first >= v_mat(i))
1840 error (R"(set: "%s" must be greater than %g)",
1841 get_name ().c_str (), m_minval.first);
1842 }
1843
1844 if (! octave::math::isnan (m_maxval.first))
1845 {
1846 for (octave_idx_type i = 0; i < v_mat.numel (); i++)
1847 if (m_maxval.second && m_maxval.first < v_mat(i))
1848 error (R"(set: "%s" must be less than or equal to %g)",
1849 get_name ().c_str (), m_maxval.first);
1850 else if (! m_maxval.second && m_maxval.first <= v_mat(i))
1851 error (R"(set: "%s" must be less than %g)",
1852 get_name ().c_str (), m_maxval.first);
1853 }
1854
1855 if (m_finite_constraint == NO_CHECK) { /* do nothing */ }
1856 else if (m_finite_constraint == FINITE)
1857 {
1858 for (octave_idx_type i = 0; i < v_mat.numel (); i++)
1859 if (! octave::math::isfinite (v_mat(i)))
1860 error (R"(set: "%s" must be finite)", get_name ().c_str ());
1861 }
1862 else if (m_finite_constraint == NOT_NAN)
1863 {
1864 for (octave_idx_type i = 0; i < v_mat.numel (); i++)
1865 if (octave::math::isnan (v_mat(i)))
1866 error (R"(set: "%s" must not be NaN)", get_name ().c_str ());
1867 }
1868 else if (m_finite_constraint == NOT_INF)
1869 {
1870 for (octave_idx_type i = 0; i < v_mat.numel (); i++)
1871 if (octave::math::isinf (v_mat(i)))
1872 error (R"(set: "%s" must not be infinite)", get_name ().c_str ());
1873 }
1874
1875 }
1876
1877 return xok;
1878}
1879
1880bool
1881array_property::is_equal (const octave_value& v) const
1882{
1883 if (m_data.type_name () == v.type_name ())
1884 {
1885 if (m_data.dims () == v.dims ())
1886 {
1887
1888#define CHECK_ARRAY_EQUAL(T, F, A) \
1889 { \
1890 if (m_data.numel () == 1) \
1891 return m_data.F ## scalar_value () == \
1892 v.F ## scalar_value (); \
1893 else \
1894 { \
1895 /* Keep copy of array_value to allow */ \
1896 /* sparse/bool arrays that are converted, to */ \
1897 /* not be deallocated early */ \
1898 const A m1 = m_data.F ## array_value (); \
1899 const T *d1 = m1.data (); \
1900 const A m2 = v.F ## array_value (); \
1901 const T *d2 = m2.data (); \
1902 \
1903 bool flag = true; \
1904 \
1905 for (int i = 0; flag && i < m_data.numel (); i++) \
1906 if (d1[i] != d2[i]) \
1907 flag = false; \
1908 \
1909 return flag; \
1910 } \
1911 }
1912
1913 if (m_data.is_double_type () || m_data.islogical ())
1914 CHECK_ARRAY_EQUAL (double,, NDArray)
1915 else if (m_data.is_single_type ())
1916 CHECK_ARRAY_EQUAL (float, float_, FloatNDArray)
1917 else if (m_data.is_int8_type ())
1919 else if (m_data.is_int16_type ())
1921 else if (m_data.is_int32_type ())
1923 else if (m_data.is_int64_type ())
1925 else if (m_data.is_uint8_type ())
1927 else if (m_data.is_uint16_type ())
1929 else if (m_data.is_uint32_type ())
1931 else if (m_data.is_uint64_type ())
1933 }
1934 }
1935
1936 return false;
1937}
1938
1939void
1940array_property::get_data_limits ()
1941{
1942 m_min_val = m_min_pos = octave::numeric_limits<double>::Inf ();
1943 m_max_val = m_max_neg = -octave::numeric_limits<double>::Inf ();
1944
1945 if (! m_data.isempty ())
1946 {
1947 if (m_data.isinteger ())
1948 {
1949 if (m_data.is_int8_type ())
1950 get_array_limits (m_data.int8_array_value (),
1951 m_min_val, m_max_val, m_min_pos, m_max_neg);
1952 else if (m_data.is_uint8_type ())
1953 get_array_limits (m_data.uint8_array_value (),
1954 m_min_val, m_max_val, m_min_pos, m_max_neg);
1955 else if (m_data.is_int16_type ())
1956 get_array_limits (m_data.int16_array_value (),
1957 m_min_val, m_max_val, m_min_pos, m_max_neg);
1958 else if (m_data.is_uint16_type ())
1959 get_array_limits (m_data.uint16_array_value (),
1960 m_min_val, m_max_val, m_min_pos, m_max_neg);
1961 else if (m_data.is_int32_type ())
1962 get_array_limits (m_data.int32_array_value (),
1963 m_min_val, m_max_val, m_min_pos, m_max_neg);
1964 else if (m_data.is_uint32_type ())
1965 get_array_limits (m_data.uint32_array_value (),
1966 m_min_val, m_max_val, m_min_pos, m_max_neg);
1967 else if (m_data.is_int64_type ())
1968 get_array_limits (m_data.int64_array_value (),
1969 m_min_val, m_max_val, m_min_pos, m_max_neg);
1970 else if (m_data.is_uint64_type ())
1971 get_array_limits (m_data.uint64_array_value (),
1972 m_min_val, m_max_val, m_min_pos, m_max_neg);
1973 }
1974 else
1975 get_array_limits (m_data.array_value (),
1976 m_min_val, m_max_val, m_min_pos, m_max_neg);
1977 }
1978}
1979
1980bool
1981handle_property::do_set (const octave_value& v)
1982{
1983 // Users may want to use empty matrix to reset a handle property
1984 if (v.isempty ())
1985 {
1986 if (! get ().isempty ())
1987 {
1988 m_current_val = graphics_handle ();
1989 return true;
1990 }
1991 else
1992 return false;
1993 }
1994
1995 double dv = v.xdouble_value (R"(set: invalid graphics handle for property "%s")",
1996 get_name ().c_str ());
1997
1998 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
1999
2000 graphics_handle gh = gh_mgr.lookup (dv);
2001
2002 // Check the object type if necessary
2003 bool type_ok = true;
2004 if (gh.ok () && ! m_type_constraints.empty ())
2005 {
2006 type_ok = false;
2007 graphics_object obj = gh_mgr.get_object (gh);
2008
2009 for (const auto& type : m_type_constraints)
2010 if (obj.isa (type))
2011 {
2012 type_ok = true;
2013 break;
2014 }
2015 }
2016
2017 if (! octave::math::isnan (gh.value ()) && ! (gh.ok () && type_ok))
2018 {
2019 if (type_ok)
2020 error (R"(set: invalid graphics handle (= %g) for property "%s")",
2021 dv, get_name ().c_str ());
2022 else
2023 error (R"(set: invalid graphics object type for property "%s")",
2024 get_name ().c_str ());
2025 }
2026
2027 if (m_current_val != gh)
2028 {
2029 m_current_val = gh;
2030 return true;
2031 }
2032
2033 return false;
2034}
2035
2036/*
2037## Test validation of contextmenu property
2038%!test
2039%! hf = figure ("visible", "off");
2040%! unwind_protect
2041%! hax = axes ("parent", hf);
2042%! hpa = patch ("parent", hax);
2043%! try
2044%! set (hax, "contextmenu", hpa);
2045%! catch
2046%! err = lasterr ();
2047%! end_try_catch
2048%! assert (err, 'set: invalid graphics object type for property "contextmenu"');
2049%! unwind_protect_cleanup
2050%! delete (hf);
2051%! end_unwind_protect
2052*/
2053
2054Matrix
2055children_property::do_get_children (bool return_hidden) const
2056{
2057 Matrix retval (m_children_list.size (), 1);
2058 octave_idx_type k = 0;
2059
2060 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
2061
2062 graphics_object go = gh_mgr.get_object (0);
2063
2064 root_figure::properties& props
2065 = dynamic_cast<root_figure::properties&> (go.get_properties ());
2066
2067 if (! props.is_showhiddenhandles ())
2068 {
2069 for (const auto& hchild : m_children_list)
2070 {
2071 graphics_handle kid = hchild;
2072
2073 if (gh_mgr.is_handle_visible (kid))
2074 {
2075 if (! return_hidden)
2076 retval(k++) = hchild;
2077 }
2078 else if (return_hidden)
2079 retval(k++) = hchild;
2080 }
2081
2082 retval.resize (k, 1);
2083 }
2084 else
2085 {
2086 for (const auto& hchild : m_children_list)
2087 retval(k++) = hchild;
2088 }
2089
2090 // Return a 0x0 empty array, not 0x1.
2091 if (retval.isempty ())
2092 retval.resize (0,0);
2093
2094 return retval;
2095}
2096
2097void
2098children_property::do_delete_children (bool clear, bool from_root)
2099{
2100 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
2101
2102 if (from_root)
2103 {
2104 for (graphics_handle hchild : m_children_list)
2105 {
2106 graphics_object go = gh_mgr.get_object (hchild);
2107 if (go.valid_object ()
2108 && ! go.get_properties ().is_beingdeleted ())
2109 gh_mgr.free (hchild, from_root);
2110 }
2111 m_children_list.clear ();
2112 }
2113 else
2114 while (! m_children_list.empty ())
2115 {
2116 // gh_mgr.free removes hchild from children_list
2117 graphics_handle hchild = m_children_list.front ();
2118 graphics_object go = gh_mgr.get_object (hchild);
2119 if (go.valid_object ()
2120 && ! go.get_properties ().is_beingdeleted ())
2121 gh_mgr.free (hchild, from_root);
2122 }
2123
2124 // FIXME: children_list should be clear anyway at this point.
2125 if (clear)
2126 m_children_list.clear ();
2127}
2128
2129bool
2130callback_property::validate (const octave_value& v) const
2131{
2132 // case 1: empty matrix
2133 // case 2: function handle
2134 // case 3: string corresponding to known function name
2135 // case 4: string that can be eval()'ed
2136 // case 5: cell array with first element being a function handle
2137
2138 if (v.isempty ())
2139 return true;
2140 else if (v.is_function_handle ())
2141 return true;
2142 else if (v.is_string ())
2143 // complete validation will be done at execution-time
2144 return true;
2145 else if (v.iscell () && (v.rows () == 1 || v.columns () == 1)
2146 && v.cell_value ()(0).is_function_handle ())
2147 return true;
2148
2149 return false;
2150}
2151
2152class callback_props
2153{
2154public:
2155
2156 callback_props () : m_set () { }
2157
2158 OCTAVE_DISABLE_COPY_MOVE (callback_props)
2159
2160 ~callback_props () = default;
2161
2162 bool empty () const { return m_set.empty (); }
2163
2164 void insert (const callback_property *ptr)
2165 {
2166 m_set.insert (reinterpret_cast<intptr_t> (ptr));
2167 }
2168
2169 void erase (const callback_property *ptr)
2170 {
2171 m_set.erase (reinterpret_cast<intptr_t> (ptr));
2172 }
2173
2174 bool contains (const callback_property *ptr) const
2175 {
2176 return m_set.find (reinterpret_cast<intptr_t> (ptr)) != m_set.end ();
2177 }
2178
2179private:
2180
2181 std::set<intptr_t> m_set;
2182};
2183
2184// Elements of this set are pointers to currently executing
2185// callback_property objects. Used to determine handle visibility
2186// inside callback functions.
2187
2188static callback_props executing_callbacks;
2189
2190void
2191callback_property::execute (const octave_value& data) const
2192{
2193 // We are executing a callback function, so allow handles that have
2194 // their handlevisibility property set to "callback" to be visible.
2195
2196 octave::unwind_action executing_callbacks_cleanup
2197 ([this] () { executing_callbacks.erase (this); });
2198
2199 if (! executing_callbacks.contains (this))
2200 {
2201 executing_callbacks.insert (this);
2202
2203 if (m_callback.is_defined () && ! m_callback.isempty ())
2204 {
2205 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
2206
2207 gh_mgr.execute_callback (get_parent (), m_callback, data);
2208 }
2209 }
2210}
2211
2212// Used to cache dummy graphics objects from which dynamic properties can be
2213// cloned.
2214static std::map<caseless_str, graphics_object> dprop_obj_map;
2215
2216property
2217property::create (const std::string& name, const graphics_handle& h,
2218 const caseless_str& type, const octave_value_list& args)
2219{
2220 property retval;
2221
2222 if (type.compare ("string"))
2223 {
2224 std::string sv = (args.length () > 0 ? args(0).string_value () : "");
2225
2226 retval = property (new string_property (name, h, sv));
2227 }
2228 else if (type.compare ("any"))
2229 {
2230 octave_value ov = (args.length () > 0 ? args(0)
2231 : octave_value (Matrix ()));
2232
2233 retval = property (new any_property (name, h, ov));
2234 }
2235 else if (type.compare ("radio"))
2236 {
2237 if (args.length () < 1)
2238 error ("addproperty: missing possible values for radio property");
2239
2240 std::string sv = args(0).xstring_value ("addproperty: argument for radio property must be a string");
2241
2242 retval = property (new radio_property (name, h, sv));
2243
2244 if (args.length () > 1)
2245 retval.set (args(1));
2246 }
2247 else if (type.compare ("double"))
2248 {
2249 double dv = (args.length () > 0 ? args(0).double_value () : 0.0);
2250
2251 retval = property (new double_property (name, h, dv));
2252 }
2253 else if (type.compare ("handle"))
2254 {
2255 double hv = (args.length () > 0 ? args(0).double_value ()
2256 : octave::numeric_limits<double>::NaN ());
2257
2258 graphics_handle gh (hv);
2259
2260 retval = property (new handle_property (name, h, gh));
2261 }
2262 else if (type.compare ("boolean"))
2263 {
2264 retval = property (new bool_property (name, h, false));
2265
2266 if (args.length () > 0)
2267 retval.set (args(0));
2268 }
2269 else if (type.compare ("data"))
2270 {
2271 retval = property (new array_property (name, h, Matrix ()));
2272
2273 if (args.length () > 0)
2274 {
2275 retval.set (args(0));
2276 // FIXME: additional argument could define constraints,
2277 // but is this really useful?
2278 }
2279 }
2280 else if (type.compare ("color"))
2281 {
2282 color_values cv (0, 0, 0);
2283 radio_values rv;
2284
2285 if (args.length () > 1)
2286 rv = radio_values (args(1).string_value ());
2287
2288 retval = property (new color_property (name, h, cv, rv));
2289
2290 if (args.length () > 0 && ! args(0).isempty ())
2291 retval.set (args(0));
2292 else
2293 retval.set (rv.default_value ());
2294 }
2295 else
2296 {
2297 caseless_str go_name, go_rest;
2298
2299 if (! lookup_object_name (type, go_name, go_rest))
2300 error ("addproperty: unsupported type for dynamic property (= %s)",
2301 type.c_str ());
2302
2303 graphics_object go;
2304
2305 std::map<caseless_str, graphics_object>::const_iterator it
2306 = dprop_obj_map.find (go_name);
2307
2308 if (it == dprop_obj_map.end ())
2309 {
2310 base_graphics_object *bgo = make_graphics_object_from_type (go_name);
2311
2312 if (bgo)
2313 {
2314 go = graphics_object (bgo);
2315
2316 dprop_obj_map[go_name] = go;
2317 }
2318 }
2319 else
2320 go = it->second;
2321
2322 if (! go.valid_object ())
2323 error ("addproperty: invalid object type (= %s)",
2324 go_name.c_str ());
2325
2326 property prop = go.get_properties ().get_property (go_rest);
2327
2328 retval = prop.clone ();
2329
2330 retval.set_parent (h);
2331 retval.set_name (name);
2332
2333 if (args.length () > 0)
2334 retval.set (args(0));
2335 }
2336
2337 return retval;
2338}
2339
2340static void
2341finalize_r (const graphics_handle& h)
2342{
2343 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
2344
2345 graphics_object go = gh_mgr.get_object (h);
2346
2347 if (go)
2348 {
2349 Matrix children = go.get_properties ().get_all_children ();
2350
2351 for (int k = 0; k < children.numel (); k++)
2352 finalize_r (children(k));
2353
2354 go.finalize ();
2355 }
2356}
2357
2358static void
2359initialize_r (const graphics_handle& h)
2360{
2361 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
2362
2363 graphics_object go = gh_mgr.get_object (h);
2364
2365 if (go)
2366 {
2367 Matrix children = go.get_properties ().get_all_children ();
2368
2369 go.initialize ();
2370
2371 for (int k = 0; k < children.numel (); k++)
2372 initialize_r (children(k));
2373 }
2374}
2375
2376void
2377figure::properties::set_toolkit (const octave::graphics_toolkit& b)
2378{
2379 if (m_toolkit)
2380 finalize_r (get___myhandle__ ());
2381
2382 m_toolkit = b;
2383 m___graphics_toolkit__ = b.get_name ();
2384 m___plot_stream__ = Matrix ();
2385
2386 if (m_toolkit)
2387 initialize_r (get___myhandle__ ());
2388
2389 mark_modified ();
2390}
2391
2392void
2393figure::properties::set___mouse_mode__ (const octave_value& val_arg)
2394{
2395 std::string direction = "in";
2396
2397 octave_value val = val_arg;
2398
2399 if (val.is_string ())
2400 {
2401 std::string modestr = val.string_value ();
2402
2403 if (modestr == "zoom in")
2404 {
2405 val = modestr = "zoom";
2406 direction = "in";
2407 }
2408 else if (modestr == "zoom out")
2409 {
2410 val = modestr = "zoom";
2411 direction = "out";
2412 }
2413
2414 if (m___mouse_mode__.set (val, true))
2415 {
2416 std::string mode = m___mouse_mode__.current_value ();
2417
2418 octave_scalar_map pm = get___pan_mode__ ().scalar_map_value ();
2419 pm.setfield ("Enable", mode == "pan" ? "on" : "off");
2420 set___pan_mode__ (pm);
2421
2422 octave_scalar_map rm = get___rotate_mode__ ().scalar_map_value ();
2423 rm.setfield ("Enable", mode == "rotate" ? "on" : "off");
2424 set___rotate_mode__ (rm);
2425
2426 octave_scalar_map zm = get___zoom_mode__ ().scalar_map_value ();
2427 zm.setfield ("Enable", mode == "zoom" ? "on" : "off");
2428 zm.setfield ("Direction", direction);
2429 set___zoom_mode__ (zm);
2430
2431 mark_modified ();
2432 }
2433 else if (modestr == "zoom")
2434 {
2435 octave_scalar_map zm = get___zoom_mode__ ().scalar_map_value ();
2436 std::string curr_direction
2437 = zm.getfield ("Direction").string_value ();
2438
2439 if (direction != curr_direction)
2440 {
2441 zm.setfield ("Direction", direction);
2442 set___zoom_mode__ (zm);
2443
2444 mark_modified ();
2445 }
2446 }
2447 }
2448}
2449
2450void
2451figure::properties::update_handlevisibility ()
2452{
2453 if (! is_handle_visible ())
2454 {
2455 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
2456
2457 octave_value cf = gh_mgr.get_object (0).get ("currentfigure");
2458
2459 if (! cf.isempty () && cf.double_value () == m___myhandle__)
2460 {
2461 octave::autolock guard (gh_mgr.graphics_lock ());
2462
2463 octave_value kids = gh_mgr.get_object (0).get ("children");
2464
2465 if (kids.isempty ())
2466 gh_mgr.get_object (0).set ("currentfigure", Matrix ());
2467 else
2468 {
2469 NDArray kidsarray = kids.array_value ();
2470 gh_mgr.get_object (0).set ("currentfigure", kidsarray(0));
2471 }
2472 }
2473 }
2474
2475 base_properties::update_handlevisibility ();
2476}
2477
2478static void
2479update_text_pos (graphics_handle h)
2480{
2481 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
2482
2483 graphics_object go = gh_mgr.get_object (h);
2484
2485 if (go.isa ("text"))
2486 {
2487 text::properties& tp
2488 = dynamic_cast<text::properties&> (go.get_properties ());
2489 tp.update_font ();
2490 tp.update_text_extent ();
2491 }
2492 else if (go.isa ("figure") || go.isa ("uipanel") || go.isa ("axes")
2493 || go.isa ("hggroup"))
2494 {
2495 Matrix ch = go.get_properties ().get_all_children ();
2496 for (octave_idx_type ii = 0; ii < ch.numel (); ii++)
2497 update_text_pos (graphics_handle (ch(ii)));
2498
2499 if (go.isa ("axes"))
2500 {
2501 axes::properties& ap
2502 = dynamic_cast<axes::properties&> (go.get_properties ());
2503 ap.update_font ();
2504 ap.sync_positions ();
2505 }
2506 }
2507}
2508
2509void
2510figure::properties::update___device_pixel_ratio__ ()
2511{
2512 update_text_pos (get___myhandle__ ());
2513}
2514
2515// ---------------------------------------------------------------------
2516
2517void
2518property_list::set (const caseless_str& name, const octave_value& val)
2519{
2520 std::size_t offset = 0;
2521
2522 std::size_t len = name.length ();
2523
2524 if (len > 4)
2525 {
2526 caseless_str pfx = name.substr (0, 4);
2527
2528 if (pfx.compare ("axes") || pfx.compare ("line")
2529 || pfx.compare ("text"))
2530 offset = 4;
2531 else if (len > 5)
2532 {
2533 pfx = name.substr (0, 5);
2534
2535 if (pfx.compare ("image") || pfx.compare ("patch"))
2536 offset = 5;
2537 else if (len > 6)
2538 {
2539 pfx = name.substr (0, 6);
2540
2541 if (pfx.compare ("figure") || pfx.compare ("uimenu"))
2542 offset = 6;
2543 else if (len > 7)
2544 {
2545 pfx = name.substr (0, 7);
2546
2547 if (pfx.compare ("surface") || pfx.compare ("scatter")
2548 || pfx.compare ("hggroup")|| pfx.compare ("uipanel")
2549 || pfx.compare ("uitable"))
2550 offset = 7;
2551 else if (len > 9)
2552 {
2553 pfx = name.substr (0, 9);
2554
2555 if (pfx.compare ("uicontrol")
2556 || pfx.compare ("uitoolbar"))
2557 offset = 9;
2558 else if (len > 10)
2559 {
2560 pfx = name.substr (0, 10);
2561
2562 if (pfx.compare ("uipushtool"))
2563 offset = 10;
2564 else if (len > 12)
2565 {
2566 pfx = name.substr (0, 12);
2567
2568 if (pfx.compare ("uitoogletool"))
2569 offset = 12;
2570 else if (len > 13)
2571 {
2572 pfx = name.substr (0, 13);
2573
2574 if (pfx.compare ("uicontextmenu")
2575 || pfx.compare ("uibuttongroup"))
2576 offset = 13;
2577 }
2578 }
2579 }
2580 }
2581 }
2582 }
2583 }
2584
2585 if (offset > 0)
2586 {
2587 // FIXME: should we validate property names and values here?
2588
2589 std::string pname = name.substr (offset);
2590
2591 std::transform (pfx.begin (), pfx.end (), pfx.begin (), tolower);
2592 std::transform (pname.begin (), pname.end (), pname.begin (),
2593 tolower);
2594
2595 bool has_property = false;
2596 if (pfx == "axes")
2597 has_property = axes::properties::has_core_property (pname);
2598 else if (pfx == "figure")
2599 has_property = figure::properties::has_core_property (pname);
2600 else if (pfx == "line")
2601 has_property = line::properties::has_core_property (pname);
2602 else if (pfx == "text")
2603 has_property = text::properties::has_core_property (pname);
2604 else if (pfx == "image")
2605 has_property = image::properties::has_core_property (pname);
2606 else if (pfx == "patch")
2607 has_property = patch::properties::has_core_property (pname);
2608 else if (pfx == "scatter")
2609 has_property = scatter::properties::has_core_property (pname);
2610 else if (pfx == "surface")
2611 has_property = surface::properties::has_core_property (pname);
2612 else if (pfx == "hggroup")
2613 has_property = hggroup::properties::has_core_property (pname);
2614 else if (pfx == "uimenu")
2615 has_property = uimenu::properties::has_core_property (pname);
2616 else if (pfx == "uicontrol")
2617 has_property = uicontrol::properties::has_core_property (pname);
2618 else if (pfx == "uibuttongroup")
2619 has_property = uibuttongroup::properties::has_core_property (pname);
2620 else if (pfx == "uipanel")
2621 has_property = uipanel::properties::has_core_property (pname);
2622 else if (pfx == "uicontextmenu")
2623 has_property = uicontextmenu::properties::has_core_property (pname);
2624 else if (pfx == "uitable")
2625 has_property = uitable::properties::has_core_property (pname);
2626 else if (pfx == "uitoolbar")
2627 has_property = uitoolbar::properties::has_core_property (pname);
2628 else if (pfx == "uipushtool")
2629 has_property = uipushtool::properties::has_core_property (pname);
2630
2631 if (! has_property)
2632 error ("invalid %s property '%s'", pfx.c_str (), pname.c_str ());
2633
2634 bool remove = false;
2635 if (val.is_string ())
2636 {
2637 std::string sval = val.string_value ();
2638
2639 remove = (sval == "remove");
2640 }
2641
2642 pval_map_type& pval_map = m_plist_map[pfx];
2643
2644 if (remove)
2645 {
2646 auto p = pval_map.find (pname);
2647
2648 if (p != pval_map.end ())
2649 pval_map.erase (p);
2650 }
2651 else
2652 pval_map[pname] = val;
2653 }
2654 }
2655
2656 if (offset == 0)
2657 error ("invalid default property specification");
2658}
2659
2661property_list::lookup (const caseless_str& name) const
2662{
2663 octave_value retval;
2664
2665 std::size_t offset = 0;
2666
2667 std::size_t len = name.length ();
2668
2669 if (len > 4)
2670 {
2671 caseless_str pfx = name.substr (0, 4);
2672
2673 if (pfx.compare ("axes") || pfx.compare ("line")
2674 || pfx.compare ("text"))
2675 offset = 4;
2676 else if (len > 5)
2677 {
2678 pfx = name.substr (0, 5);
2679
2680 if (pfx.compare ("image") || pfx.compare ("patch"))
2681 offset = 5;
2682 else if (len > 6)
2683 {
2684 pfx = name.substr (0, 6);
2685
2686 if (pfx.compare ("figure") || pfx.compare ("uimenu"))
2687 offset = 6;
2688 else if (len > 7)
2689 {
2690 pfx = name.substr (0, 7);
2691
2692 if (pfx.compare ("surface") || pfx.compare ("scatter")
2693 || pfx.compare ("hggroup") || pfx.compare ("uipanel")
2694 || pfx.compare ("uitable"))
2695 offset = 7;
2696 else if (len > 9)
2697 {
2698 pfx = name.substr (0, 9);
2699
2700 if (pfx.compare ("uicontrol")
2701 || pfx.compare ("uitoolbar"))
2702 offset = 9;
2703 else if (len > 10)
2704 {
2705 pfx = name.substr (0, 10);
2706
2707 if (pfx.compare ("uipushtool"))
2708 offset = 10;
2709 else if (len > 12)
2710 {
2711 pfx = name.substr (0, 12);
2712
2713 if (pfx.compare ("uitoggletool"))
2714 offset = 12;
2715 else if (len > 13)
2716 {
2717 pfx = name.substr (0, 13);
2718
2719 if (pfx.compare ("uicontextmenu")
2720 || pfx.compare ("uibuttongroup"))
2721 offset = 13;
2722 }
2723 }
2724 }
2725 }
2726 }
2727 }
2728 }
2729
2730 if (offset > 0)
2731 {
2732 std::string pname = name.substr (offset);
2733
2734 std::transform (pfx.begin (), pfx.end (), pfx.begin (), tolower);
2735 std::transform (pname.begin (), pname.end (), pname.begin (),
2736 tolower);
2737
2738 plist_map_const_iterator p = find (pfx);
2739
2740 if (p != end ())
2741 {
2742 const pval_map_type& pval_map = p->second;
2743
2744 pval_map_const_iterator q = pval_map.find (pname);
2745
2746 if (q != pval_map.end ())
2747 retval = q->second;
2748 }
2749 }
2750 }
2751
2752 return retval;
2753}
2754
2756property_list::as_struct (const std::string& prefix_arg) const
2757{
2759
2760 for (const auto& p : *this)
2761 {
2762 std::string prefix = prefix_arg + p.first;
2763
2764 for (const auto& prop_val : p.second)
2765 m.assign (prefix + prop_val.first, prop_val.second);
2766 }
2767
2768 return m;
2769}
2770
2771// Set property given as either cs-list of name/value pairs or a struct.
2772
2773void
2774graphics_object::set (const octave_value_list& args)
2775{
2776 int nargin = args.length ();
2777
2778 if (nargin == 0)
2779 error ("graphics_object::set: Nothing to set");
2780
2781 for (int i = 0; i < nargin; )
2782 {
2783 if (args(i).isstruct ())
2784 {
2785 set (args(i).map_value ());
2786 i++;
2787 }
2788 else if (i < nargin - 1)
2789 {
2790 caseless_str pname = args(i).xstring_value ("set: argument %d must be a property name", i);
2791 octave_value val = args(i+1);
2792 set_value_or_default (pname, val);
2793 i += 2;
2794 }
2795 else
2796 error ("set: invalid number of arguments");
2797 }
2798}
2799
2800/*
2801## test set with name, value pairs
2802%!test
2803%! hf = figure ("visible", "off");
2804%! h = plot (1:10, 10:-1:1);
2805%! set (h, "linewidth", 10, "marker", "x");
2806%! lw = get (h, "linewidth");
2807%! mk = get (h, "marker");
2808%! close (hf);
2809%! assert (lw, 10);
2810%! assert (mk, "x");
2811*/
2812
2813// Set properties given in two cell arrays containing names and values.
2814void
2815graphics_object::set (const Array<std::string>& pnames,
2816 const Cell& values, octave_idx_type row)
2817{
2818 if (pnames.numel () != values.columns ())
2819 error ("set: number of names must match number of value columns "
2820 "(%" OCTAVE_IDX_TYPE_FORMAT " != %" OCTAVE_IDX_TYPE_FORMAT ")",
2821 pnames.numel (), values.columns ());
2822
2823 octave_idx_type k = pnames.columns ();
2824
2825 for (octave_idx_type column = 0; column < k; column++)
2826 {
2827 caseless_str pname = pnames(column);
2828 octave_value val = values(row, column);
2829
2830 set_value_or_default (pname, val);
2831 }
2832}
2833
2834/*
2835## test set with cell array arguments
2836%!test
2837%! hf = figure ("visible", "off");
2838%! h = plot (1:10, 10:-1:1);
2839%! set (h, {"linewidth", "marker"}, {10, "x"});
2840%! lw = get (h, "linewidth");
2841%! mk = get (h, "marker");
2842%! close (hf);
2843%! assert (lw, 10);
2844%! assert (mk, "x");
2845
2846## test set with multiple handles and cell array arguments
2847%!test
2848%! hf = figure ("visible", "off");
2849%! unwind_protect
2850%! h = plot (1:10, 10:-1:1, 1:10, 1:10);
2851%! set (h, {"linewidth", "marker"}, {10, "x"; 5, "o"});
2852%! assert (get (h, "linewidth"), {10; 5});
2853%! assert (get (h, "marker"), {"x"; "o"});
2854%! set (h, {"linewidth", "marker"}, {10, "x"});
2855%! assert (get (h, "linewidth"), {10; 10});
2856%! assert (get (h, "marker"), {"x"; "x"});
2857%! unwind_protect_cleanup
2858%! close (hf);
2859%! end_unwind_protect
2860
2861%!error <set: number of graphics handles must match number of value rows>
2862%! hf = figure ("visible", "off");
2863%! unwind_protect
2864%! h = plot (1:10, 10:-1:1, 1:10, 1:10);
2865%! set (h, {"linewidth", "marker"}, {10, "x"; 5, "o"; 7, "."});
2866%! unwind_protect_cleanup
2867%! close (hf);
2868%! end_unwind_protect
2869
2870%!error <set: number of names must match number of value columns>
2871%! hf = figure ("visible", "off");
2872%! unwind_protect
2873%! h = plot (1:10, 10:-1:1, 1:10, 1:10);
2874%! set (h, {"linewidth"}, {10, "x"; 5, "o"});
2875%! unwind_protect_cleanup
2876%! close (hf);
2877%! end_unwind_protect
2878*/
2879
2880// Set properties given in a struct array
2881void
2882graphics_object::set (const octave_map& m)
2883{
2884 for (octave_idx_type p = 0; p < m.nfields (); p++)
2885 {
2886 // FIXME: Would it be better to extract all the keys at once rather than
2887 // repeatedly call keys() inside a for loop?
2888 caseless_str pname = m.keys ()[p];
2889
2890 octave_value val = octave_value (m.contents (pname).elem (m.numel () - 1));
2891
2892 set_value_or_default (pname, val);
2893 }
2894}
2895
2896/*
2897## test set with struct arguments
2898%!test
2899%! hf = figure ("visible", "off");
2900%! unwind_protect
2901%! h = plot (1:10, 10:-1:1);
2902%! set (h, struct ("linewidth", 10, "marker", "x"));
2903%! assert (get (h, "linewidth"), 10);
2904%! assert (get (h, "marker"), "x");
2905%! h = plot (1:10, 10:-1:1, 1:10, 1:10);
2906%! set (h, struct ("linewidth", {5, 10}));
2907%! assert (get (h, "linewidth"), {10; 10});
2908%! unwind_protect_cleanup
2909%! close (hf);
2910%! end_unwind_protect
2911
2912## test ordering
2913%!test
2914%! markchanged = @(h, foobar, name) set (h, "userdata", [get(h,"userdata"); {name}]);
2915%! hf = figure ("visible", "off");
2916%! unwind_protect
2917%! h = line ();
2918%! set (h, "userdata", {});
2919%! addlistener (h, "color", {markchanged, "color"});
2920%! addlistener (h, "linewidth", {markchanged, "linewidth"});
2921%! ## "linewidth" first
2922%! props.linewidth = 2;
2923%! props.color = "r";
2924%! set (h, props);
2925%! assert (get (h, "userdata"), fieldnames (props));
2926%! clear props;
2927%! clf ();
2928%! h = line ();
2929%! set (h, "userdata", {});
2930%! addlistener (h, "color", {markchanged, "color"});
2931%! addlistener (h, "linewidth", {markchanged, "linewidth"});
2932%! ## "color" first
2933%! props.color = "r";
2934%! props.linewidth = 2;
2935%! set (h, props);
2936%! assert (get (h, "userdata"), fieldnames (props));
2937%! unwind_protect_cleanup
2938%! close (hf);
2939%! end_unwind_protect
2940*/
2941
2942// Set a property to a value or to its (factory) default value.
2943
2944void
2945graphics_object::set_value_or_default (const caseless_str& pname,
2946 const octave_value& val)
2947{
2948 if (val.is_string () && val.rows () == 1)
2949 {
2950 std::string sval = val.string_value ();
2951
2952 octave_value default_val;
2953
2954 if (sval == "default")
2955 {
2956 default_val = get_default (pname);
2957
2958 m_rep->set (pname, default_val);
2959 }
2960 else if (sval == "factory")
2961 {
2962 default_val = get_factory_default (pname);
2963
2964 m_rep->set (pname, default_val);
2965 }
2966 else
2967 {
2968 // Matlab specifically uses "\default" to escape string setting
2969 if (sval == R"(\default)")
2970 m_rep->set (pname, "default");
2971 else if (sval == R"(\factory)")
2972 m_rep->set (pname, "factory");
2973 else
2974 m_rep->set (pname, val);
2975 }
2976 }
2977 else
2978 m_rep->set (pname, val);
2979}
2980
2981/*
2982## test setting of default values
2983%!test
2984%! old_lw = get (0, "defaultlinelinewidth");
2985%! unwind_protect
2986%! hf = figure ("visible", "off");
2987%! h = plot (1:10, 10:-1:1);
2988%! set (0, "defaultlinelinewidth", 20);
2989%! set (h, "linewidth", "default");
2990%! assert (get (h, "linewidth"), 20);
2991%! set (h, "linewidth", "factory");
2992%! assert (get (h, "linewidth"), 0.5);
2993%! unwind_protect_cleanup
2994%! close (hf);
2995%! set (0, "defaultlinelinewidth", old_lw);
2996%! end_unwind_protect
2997*/
2998
2999// This function is NOT equivalent to the scripting language function gcf.
3002{
3003 octave_value val = xget (0, "currentfigure");
3004
3005 return val.isempty () ? octave::numeric_limits<double>::NaN ()
3006 : val.double_value ();
3007}
3008
3009// This function is NOT equivalent to the scripting language function gca.
3012{
3013 octave_value val = xget (gcf (), "currentaxes");
3014
3015 return val.isempty () ? octave::numeric_limits<double>::NaN ()
3016 : val.double_value ();
3017}
3018
3019static void
3020adopt (const graphics_handle& parent_h, const graphics_handle& h)
3021{
3022 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3023
3024 graphics_object parent_go = gh_mgr.get_object (parent_h);
3025
3026 parent_go.adopt (h);
3027}
3028
3029static bool
3030ishghandle (const graphics_handle& h)
3031{
3032 return h.ok ();
3033}
3034
3035static bool
3036ishghandle (double val)
3037{
3038 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3039
3040 graphics_handle h = gh_mgr.lookup (val);
3041
3042 return h.ok ();
3043}
3044
3045static octave_value
3046ishghandle (const octave_value& val)
3047{
3048 octave_value retval = false;
3049
3050 if (val.is_real_scalar () && ishghandle (val.double_value ()))
3051 retval = true;
3052 else if (val.isnumeric () && val.isreal ())
3053 {
3054 const NDArray handles = val.array_value ();
3055
3056 boolNDArray result (handles.dims ());
3057
3058 for (octave_idx_type i = 0; i < handles.numel (); i++)
3059 result.xelem (i) = ishghandle (handles(i));
3060
3061 retval = result;
3062 }
3063
3064 return retval;
3065}
3066
3067static void
3068xcreatefcn (const graphics_handle& h)
3069{
3070 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3071
3072 graphics_object go = gh_mgr.get_object (h);
3073
3074 go.get_properties ().execute_createfcn ();
3075}
3076
3077static void
3078xinitialize (const graphics_handle& h)
3079{
3080 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3081
3082 graphics_object go = gh_mgr.get_object (h);
3083
3084 if (go)
3085 go.initialize ();
3086}
3087
3088// ---------------------------------------------------------------------
3089
3090static int
3091toggle_warn (std::string id, bool on, int state = -1)
3092{
3093 if (! on)
3094 {
3095 state = octave::warning_enabled (id);
3096 octave::disable_warning (id);
3097 }
3098 else
3099 {
3100 if (state == 1)
3101 octave::set_warning_state (id, "on");
3102 else if (state == 2)
3103 octave::set_warning_state (id, "error");
3104 }
3105 return state;
3106}
3107
3108static void
3109xreset_default_properties (graphics_handle h,
3110 property_list::pval_map_type factory_pval)
3111{
3112 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3113
3114 graphics_object go = gh_mgr.get_object (h);
3115
3116 // Replace factory defaults by user defined ones
3117 std::string go_name = go.get_properties ().graphics_object_name ();
3118 property_list::pval_map_type pval;
3119 go.build_user_defaults_map (pval, go_name);
3120
3121 for (const auto& p : pval)
3122 factory_pval[p.first] = p.second;
3123
3124 // Save warning state of "Octave:deprecated-property"
3125 int state = toggle_warn ("Octave:deprecated-property", false);
3126
3127 // Reset defaults
3128 for (const auto& p : factory_pval)
3129 {
3130 std::string pname = p.first;
3131
3132 // Don't reset internal properties and handle_properties
3133 if (! go.has_readonly_property (pname)
3134 && pname.find ("__") != 0 && pname.find ("current") != 0
3135 && pname != "uicontextmenu" && pname != "parent")
3136 {
3137 // Store *mode prop/val in order to set them last
3138 if (pname.find ("mode") == (pname.length () - 4))
3139 pval[pname] = p.second;
3140 else
3141 go.set (pname, p.second);
3142 }
3143 }
3144
3145 // set *mode properties
3146 for (const auto& p : pval)
3147 go.set (p.first, p.second);
3148
3149 toggle_warn ("Octave:deprecated-property", true, state);
3150}
3151
3152// ---------------------------------------------------------------------
3153
3154void
3155base_properties::set_from_list (base_graphics_object& bgo,
3156 property_list& defaults)
3157{
3158 std::string go_name = graphics_object_name ();
3159
3160 property_list::plist_map_const_iterator plist = defaults.find (go_name);
3161
3162 if (plist != defaults.end ())
3163 {
3164 const property_list::pval_map_type pval_map = plist->second;
3165
3166 for (const auto& prop_val : pval_map)
3167 {
3168 std::string pname = prop_val.first;
3169
3170 try
3171 {
3172 bgo.set (pname, prop_val.second);
3173 }
3174 catch (octave::execution_exception& ee)
3175 {
3176 error (ee, "error setting default property %s", pname.c_str ());
3177 }
3178 }
3179 }
3180}
3181
3182/*
3183## test defaults are set in the order they were stored
3184%!test
3185%! set (0, "defaultfigureunits", "normalized");
3186%! set(0, "defaultfigureposition", [0.7 0 0.3 0.3]);
3187%! hf = figure ("visible", "off");
3188%! tol = 20 * eps;
3189%! unwind_protect
3190%! assert (get (hf, "position"), [0.7 0 0.3 0.3], tol);
3191%! unwind_protect_cleanup
3192%! close (hf);
3193%! set (0, "defaultfigureunits", "remove");
3194%! set (0, "defaultfigureposition", "remove");
3195%! end_unwind_protect
3196*/
3197
3199base_properties::get_dynamic (const caseless_str& pname) const
3200{
3201 std::map<caseless_str, property, cmp_caseless_str>::const_iterator it
3202 = m_all_props.find (pname);
3203
3204 if (it == m_all_props.end ())
3205 error (R"(get: unknown property "%s")", pname.c_str ());
3206
3207 return it->second.get ();
3208}
3209
3211base_properties::get_dynamic (bool all) const
3212{
3214
3215 for (const auto& it : m_all_props)
3216 if (all || ! it.second.is_hidden ())
3217 m.assign (it.second.get_name (), it.second.get ());
3218
3219 return m;
3220}
3221
3222std::set<std::string>
3223base_properties::dynamic_property_names () const
3224{
3225 return m_dynamic_properties;
3226}
3227
3228bool
3229base_properties::has_dynamic_property (const std::string& pname) const
3230{
3231 const std::set<std::string>& dynprops = dynamic_property_names ();
3232
3233 if (dynprops.find (pname) != dynprops.end ())
3234 return true;
3235 else
3236 return m_all_props.find (pname) != m_all_props.end ();
3237}
3238
3239void
3240base_properties::set_dynamic (const caseless_str& pname,
3241 const octave_value& val)
3242{
3243 auto it = m_all_props.find (pname);
3244
3245 if (it == m_all_props.end ())
3246 error (R"(set: unknown property "%s")", pname.c_str ());
3247
3248 it->second.set (val);
3249
3250 m_dynamic_properties.insert (pname);
3251
3252 mark_modified ();
3253}
3254
3255property
3256base_properties::get_property_dynamic (const caseless_str& pname) const
3257{
3258 std::map<caseless_str, property, cmp_caseless_str>::const_iterator it
3259 = m_all_props.find (pname);
3260
3261 if (it == m_all_props.end ())
3262 error (R"(get_property: unknown property "%s")", pname.c_str ());
3263
3264 return it->second;
3265}
3266
3267void
3268base_properties::set_parent (const octave_value& val)
3269{
3270 double hp = val.xdouble_value ("set: parent must be a graphics handle");
3271 if (hp == m___myhandle__)
3272 error ("set: can not set object parent to be object itself");
3273
3274 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3275
3276 graphics_handle new_parent = gh_mgr.lookup (hp);
3277 if (! new_parent.ok ())
3278 error ("set: invalid graphics handle (= %g) for parent", hp);
3279
3280 // Remove child from current parent
3281 graphics_object old_parent_go;
3282 old_parent_go = gh_mgr.get_object (get_parent ());
3283
3284 if (old_parent_go.get_handle () != hp)
3285 old_parent_go.remove_child (m___myhandle__);
3286 else
3287 return; // Do nothing more
3288
3289 // Check new parent's parent is not this child to avoid recursion
3290 graphics_object new_parent_go;
3291 new_parent_go = gh_mgr.get_object (new_parent);
3292 if (new_parent_go.get_parent () == m___myhandle__)
3293 {
3294 // new parent's parent gets child's original parent
3295 new_parent_go.get_properties ().set_parent (get_parent ().as_octave_value ());
3296 }
3297
3298 // Set parent property to new_parent and do adoption
3299 m_parent = new_parent.as_octave_value ();
3300 octave::adopt (m_parent.handle_value (), m___myhandle__);
3301}
3302
3303/*
3304%!test
3305%! hf = figure ("visible", "off");
3306%! unwind_protect
3307%! hax = gca ();
3308%! set (hax, "parent", gcf ());
3309%! assert (gca (), hax);
3310%! unwind_protect_cleanup
3311%! close (hf);
3312%! end_unwind_protect
3313*/
3314
3315void
3316base_properties::mark_modified ()
3317{
3318 // Mark existing object as modified
3319 m___modified__ = "on";
3320
3321 // Attempt to mark parent object as modified if it exists
3322
3323 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3324
3325 graphics_object parent_go = gh_mgr.get_object (get_parent ());
3326
3327 if (parent_go)
3328 parent_go.mark_modified ();
3329}
3330
3331void
3332base_properties::override_defaults (base_graphics_object& obj)
3333{
3334 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3335
3336 graphics_object parent_go = gh_mgr.get_object (get_parent ());
3337
3338 if (parent_go)
3339 parent_go.override_defaults (obj);
3340}
3341
3342void
3343base_properties::update_axis_limits (const std::string& axis_type) const
3344{
3345 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3346
3347 graphics_object go = gh_mgr.get_object (m___myhandle__);
3348
3349 if (go)
3350 go.update_axis_limits (axis_type);
3351}
3352
3353void
3354base_properties::update_axis_limits (const std::string& axis_type,
3355 const graphics_handle& h) const
3356{
3357 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3358
3359 graphics_object go = gh_mgr.get_object (m___myhandle__);
3360
3361 if (go)
3362 go.update_axis_limits (axis_type, h);
3363}
3364
3365void
3366base_properties::update_contextmenu () const
3367{
3368 if (m_contextmenu.get ().isempty ())
3369 return;
3370
3371 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3372
3373 graphics_object go = gh_mgr.get_object (m_contextmenu.get ());
3374
3375 if (go && go.isa ("uicontextmenu"))
3376 {
3377 uicontextmenu::properties& props
3378 = reinterpret_cast<uicontextmenu::properties&> (go.get_properties ());
3379 props.add_dependent_obj (m___myhandle__);
3380 }
3381}
3382
3383bool
3384base_properties::is_handle_visible () const
3385{
3386 return (m_handlevisibility.is ("on")
3387 || (! executing_callbacks.empty () && ! m_handlevisibility.is ("off")));
3388}
3389
3390octave::graphics_toolkit
3391base_properties::get_toolkit () const
3392{
3393 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3394
3395 graphics_object go = gh_mgr.get_object (get_parent ());
3396
3397 if (go)
3398 return go.get_toolkit ();
3399 else
3400 return octave::graphics_toolkit ();
3401}
3402
3403void
3404base_properties::update_boundingbox ()
3405{
3406 Matrix kids = get_children ();
3407
3408 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3409
3410 for (int i = 0; i < kids.numel (); i++)
3411 {
3412 graphics_object go = gh_mgr.get_object (kids(i));
3413
3414 if (go.valid_object ())
3415 go.get_properties ().update_boundingbox ();
3416 }
3417}
3418
3419void
3420base_properties::update_autopos (const std::string& elem_type)
3421{
3422 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3423
3424 graphics_object parent_go = gh_mgr.get_object (get_parent ());
3425
3426 if (parent_go.valid_object ())
3427 parent_go.get_properties ().update_autopos (elem_type);
3428}
3429
3430void
3431base_properties::update_handlevisibility ()
3432{
3433 if (is_handle_visible ())
3434 return;
3435
3436 // This object should not be the figure "currentobject"
3437
3438 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3439
3440 graphics_object go (gh_mgr.get_object (get___myhandle__ ()));
3441
3442 graphics_object fig (go.get_ancestor ("figure"));
3443
3444 if (fig.valid_object ())
3445 {
3446 octave_value co = fig.get ("currentobject");
3447 if (! co.isempty () && co.double_value () == m___myhandle__)
3448 {
3449 octave::autolock guard (gh_mgr.graphics_lock ());
3450
3451 auto& fig_props = dynamic_cast<figure::properties&> (fig.get_properties ());
3452 fig_props.set_currentobject (Matrix ());
3453 }
3454 }
3455}
3456
3457/*
3458## test current figure and current axes have visible handles
3459%!test
3460%! hf1 = figure ("visible", "off");
3461%! hf2 = figure ("visible", "off");
3462%! hax1 = axes ();
3463%! hax2 = axes ();
3464%! unwind_protect
3465%! assert (get (0, "currentfigure"), hf2);
3466%! assert (get (hf2, "currentaxes"), hax2);
3467%! set (hf2, "handlevisibility", "off");
3468%! assert (get (0, "currentfigure"), hf1);
3469%! set (hax2, "handlevisibility", "off");
3470%! assert (get (hf2, "currentaxes"), hax1);
3471%! assert (get (hf2, "currentobject"), []);
3472%! unwind_protect_cleanup
3473%! close ([hf1, hf2]);
3474%! end_unwind_protect
3475*/
3476
3477/*
3478## test current callback object have visible handle
3479%!test
3480%! hf = figure ("handlevisibility", "off", "visible", "off");
3481%! hax = axes ("parent", hf, "handlevisibility", "off");
3482%! unwind_protect
3483%! fcn = @(h, ~) setappdata (h, "testdata", gcbo ());
3484%! addlistener (hf, "color", fcn);
3485%! addlistener (hax, "color", fcn);
3486%! set (hf, "color", "b");
3487%! set (hax, "color", "b");
3488%! assert (getappdata (hf, "testdata"), hf);
3489%! assert (getappdata (hax, "testdata"), hax);
3490%! unwind_protect_cleanup
3491%! close (hf);
3492%! end_unwind_protect
3493*/
3494
3495void
3496base_properties::add_listener (const caseless_str& pname,
3497 const octave_value& val,
3498 listener_mode mode)
3499{
3500 property p = get_property (pname);
3501
3502 if (p.ok ())
3503 p.add_listener (val, mode);
3504}
3505
3506void
3507base_properties::delete_listener (const caseless_str& pname,
3508 const octave_value& val,
3509 listener_mode mode)
3510{
3511 property p = get_property (pname);
3512
3513 if (p.ok ())
3514 p.delete_listener (val, mode);
3515}
3516
3517void
3518base_properties::get_children_of_type (const caseless_str& chtype,
3519 bool get_invisible,
3520 bool traverse,
3521 std::list<graphics_object>& children_list) const
3522{
3523 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3524
3525 Matrix ch = get_children ();
3526
3527 for (octave_idx_type i = 0; i < ch.numel (); i++)
3528 {
3529 graphics_handle hkid = gh_mgr.lookup (ch(i));
3530
3531 if (hkid.ok ())
3532 {
3533 graphics_object go = gh_mgr.get_object (hkid);
3534 if ( get_invisible || go.get_properties ().is_visible () )
3535 {
3536 if (go.isa (chtype))
3537 children_list.push_back (go);
3538 else if (traverse && go.isa ("hggroup"))
3539 go.get_properties ().get_children_of_type (chtype,
3540 get_invisible,
3541 traverse,
3542 children_list);
3543 }
3544 }
3545 }
3546}
3547
3548// ---------------------------------------------------------------------
3549
3550void
3551base_graphics_object::update_axis_limits (const std::string& axis_type)
3552{
3553 if (! valid_object ())
3554 error ("base_graphics_object::update_axis_limits: invalid graphics object");
3555
3556 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3557
3558 graphics_object parent_go = gh_mgr.get_object (get_parent ());
3559
3560 if (parent_go)
3561 parent_go.update_axis_limits (axis_type);
3562}
3563
3564void
3565base_graphics_object::update_axis_limits (const std::string& axis_type,
3566 const graphics_handle& h)
3567{
3568 if (! valid_object ())
3569 error ("base_graphics_object::update_axis_limits: invalid graphics object");
3570
3571 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3572
3573 graphics_object parent_go = gh_mgr.get_object (get_parent ());
3574
3575 if (parent_go)
3576 parent_go.update_axis_limits (axis_type, h);
3577}
3578
3579void
3580base_graphics_object::remove_all_listeners ()
3581{
3582 int state = toggle_warn ("Octave:deprecated-property", false);
3583 octave_map m = get (true).map_value ();
3584 toggle_warn ("Octave:deprecated-property", true, state);
3585
3586 for (const auto& pm : m)
3587 {
3588 // FIXME: there has to be a better way. I think we want to
3589 // ask whether it is OK to delete the listener for the given
3590 // property. How can we know in advance that it will be OK?
3591
3592 octave::unwind_protect frame;
3593
3594 octave::interpreter_try (frame);
3595
3596 try
3597 {
3598 property p = get_properties ().get_property (pm.first);
3599
3600 if (p.ok ())
3601 p.delete_listener ();
3602 }
3603 catch (const octave::execution_exception&)
3604 {
3605 octave::interpreter& interp = octave::__get_interpreter__ ();
3606
3607 interp.recover_from_exception ();
3608 }
3609 }
3610}
3611
3612void
3613base_graphics_object::build_user_defaults_map (property_list::pval_map_type& def,
3614 const std::string go_name) const
3615{
3616 property_list local_defaults = get_defaults_list ();
3617 const auto it = local_defaults.find (go_name);
3618
3619 if (it != local_defaults.end ())
3620 {
3621 property_list::pval_map_type pval_lst = it->second;
3622 for (const auto& prop_val : pval_lst)
3623 {
3624 std::string pname = prop_val.first;
3625 if (def.find (pname) == def.end ())
3626 def[pname] = prop_val.second;
3627 }
3628 }
3629
3630 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3631
3632 graphics_object parent_go = gh_mgr.get_object (get_parent ());
3633
3634 if (parent_go)
3635 parent_go.build_user_defaults_map (def, go_name);
3636}
3637
3638void
3639base_graphics_object::reset_default_properties ()
3640{
3641 if (valid_object ())
3642 {
3643 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3644
3645 property_list::pval_map_type factory_pval
3646 = gh_mgr.get_object (0).get_factory_defaults_list ().find (type ())->second;
3647
3648 remove_all_listeners ();
3649 xreset_default_properties (get_handle (), factory_pval);
3650 }
3651}
3652
3653std::string
3654base_graphics_object::values_as_string ()
3655{
3656 if (! valid_object ())
3657 error ("base_graphics_object::values_as_string: invalid graphics object");
3658
3659 std::string retval;
3660 octave_map m = get ().map_value ();
3661
3662 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3663
3664 graphics_object go = gh_mgr.get_object (get_handle ());
3665
3666 for (const auto& pm : m)
3667 {
3668 const auto& pname = pm.first;
3669 if (pname != "children" && ! go.has_readonly_property (pname))
3670 {
3671 property p = get_properties ().get_property (pname);
3672
3673 if (p.ok () && ! p.is_hidden ())
3674 {
3675 retval += "\n\t" + std::string (pname) + ": ";
3676 if (p.is_radio ())
3677 retval += p.values_as_string ();
3678 }
3679 }
3680 }
3681
3682 if (! retval.empty ())
3683 retval += '\n';
3684
3685 return retval;
3686}
3687
3688std::string
3689base_graphics_object::value_as_string (const std::string& prop)
3690{
3691 std::string retval;
3692
3693 if (! valid_object ())
3694 error ("base_graphics_object::value_as_string: invalid graphics object");
3695
3696 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3697
3698 graphics_object go = gh_mgr.get_object (get_handle ());
3699
3700 if (prop != "children" && ! go.has_readonly_property (prop))
3701 {
3702 property p = get_properties ().get_property (prop);
3703
3704 if (p.ok () && ! p.is_hidden ())
3705 {
3706 if (p.is_radio ())
3707 retval += p.values_as_string ();
3708 }
3709 }
3710
3711 if (! retval.empty ())
3712 retval += '\n';
3713
3714 return retval;
3715}
3716
3718base_graphics_object::values_as_struct ()
3719{
3720 octave_scalar_map retval;
3721
3722 if (! valid_object ())
3723 error ("base_graphics_object::values_as_struct: invalid graphics object");
3724
3725 octave_scalar_map m = get ().scalar_map_value ();
3726
3727 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3728
3729 graphics_object go = gh_mgr.get_object (get_handle ());
3730
3731 for (const auto& pm : m)
3732 {
3733 const auto& pname = pm.first;
3734 if (pname != "children" && ! go.has_readonly_property (pname))
3735 {
3736 property p = get_properties ().get_property (pname);
3737
3738 if (p.ok () && ! p.is_hidden ())
3739 {
3740 if (p.is_radio ())
3741 retval.assign (p.get_name (), p.values_as_cell ());
3742 else
3743 retval.assign (p.get_name (), Cell ());
3744 }
3745 }
3746 }
3747
3748 return retval;
3749}
3750
3751/*
3752%!test
3753%! hfig = figure ("visible", "off");
3754%! unwind_protect
3755%! hax = axes ();
3756%! ret = set (hax, "tightinset");
3757%! assert (isempty (ret));
3758%! ret = set (hax, "type");
3759%! assert (isempty (ret));
3760%! ret = set (hfig, "tag");
3761%! assert (isempty (ret));
3762%! ret = set (0, "commandwindowsize");
3763%! assert (isempty (ret));
3764%! ret = set (0);
3765%! assert (! isfield (ret, "commandwindowsize"));
3766%! unwind_protect_cleanup
3767%! close (hfig);
3768%! end_unwind_protect
3769*/
3770
3771graphics_object
3772graphics_object::get_ancestor (const std::string& obj_type) const
3773{
3774 if (valid_object ())
3775 {
3776 if (isa (obj_type))
3777 return *this;
3778 else
3779 {
3780 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3781
3782 return gh_mgr.get_object (get_parent ()).get_ancestor (obj_type);
3783 }
3784 }
3785 else
3786 return graphics_object ();
3787}
3788
3789// ---------------------------------------------------------------------
3790
3791#include "graphics-props.cc"
3792
3793// ---------------------------------------------------------------------
3794
3795void
3796root_figure::properties::set_callbackobject (const octave_value& v)
3797{
3798 graphics_handle val (v);
3799
3800 if (octave::math::isnan (val.value ()))
3801 m_callbackobject = graphics_handle ();
3802 else if (ishghandle (val))
3803 m_callbackobject = val;
3804 else
3805 err_set_invalid ("callbackobject");
3806}
3807
3808void
3809root_figure::properties::set_currentfigure (const octave_value& v)
3810{
3811 graphics_handle val (v);
3812
3813 if (octave::math::isnan (val.value ()) || ishghandle (val))
3814 {
3815 m_currentfigure = val;
3816
3817 if (val.ok ())
3818 {
3819 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3820
3821 gh_mgr.push_figure (val);
3822 }
3823 }
3824 else
3825 err_set_invalid ("currentfigure");
3826}
3827
3828void
3829figure::properties::set_integerhandle (const octave_value& val)
3830{
3831 if (m_integerhandle.set (val, true))
3832 {
3833 bool int_fig_handle = m_integerhandle.is_on ();
3834
3835 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3836
3837 graphics_object this_go = gh_mgr.get_object (m___myhandle__);
3838
3839 graphics_handle old_myhandle = m___myhandle__;
3840
3841 m___myhandle__ = gh_mgr.get_handle (int_fig_handle);
3842
3843 gh_mgr.renumber_figure (old_myhandle, m___myhandle__);
3844
3845 graphics_object parent_go = gh_mgr.get_object (get_parent ());
3846
3847 base_properties& props = parent_go.get_properties ();
3848
3849 props.renumber_child (old_myhandle, m___myhandle__);
3850
3851 Matrix kids = get_children ();
3852
3853 for (octave_idx_type i = 0; i < kids.numel (); i++)
3854 {
3855 graphics_object kid = gh_mgr.get_object (kids(i));
3856
3857 kid.get_properties ().renumber_parent (m___myhandle__);
3858 }
3859
3860 graphics_handle cf = gh_mgr.current_figure ();
3861
3862 if (m___myhandle__ == cf)
3863 xset (0, "currentfigure", m___myhandle__.value ());
3864
3865 this_go.update (m_integerhandle.get_id ());
3866
3867 mark_modified ();
3868 }
3869}
3870
3871// FIXME: This should update monitorpositions and pointerlocation, but as these
3872// properties aren't yet used, it doesn't matter that they aren't set either.
3873void
3874root_figure::properties::update_units ()
3875{
3876 std::string xunits = get_units ();
3877
3878 Matrix scrn_sz = default_screensize ();
3879
3880 double dpi = get_screenpixelsperinch ();
3881
3882 if (xunits == "pixels")
3883 {
3884 // Most common case (default).
3885 // Don't need to convert anything, but short-circuit if/else tree.
3886 }
3887 else if (xunits == "normalized")
3888 {
3889 scrn_sz = Matrix (1, 4, 1.0);
3890 scrn_sz(0) = 0;
3891 scrn_sz(1) = 0;
3892 }
3893 else if (xunits == "inches")
3894 {
3895 scrn_sz(0) = 0;
3896 scrn_sz(1) = 0;
3897 scrn_sz(2) /= dpi;
3898 scrn_sz(3) /= dpi;
3899 }
3900 else if (xunits == "centimeters")
3901 {
3902 scrn_sz(0) = 0;
3903 scrn_sz(1) = 0;
3904 scrn_sz(2) *= 2.54 / dpi;
3905 scrn_sz(3) *= 2.54 / dpi;
3906 }
3907 else if (xunits == "points")
3908 {
3909 scrn_sz(0) = 0;
3910 scrn_sz(1) = 0;
3911 scrn_sz(2) *= 72 / dpi;
3912 scrn_sz(3) *= 72 / dpi;
3913 }
3914 else if (xunits == "characters")
3915 {
3916 scrn_sz(0) = 0;
3917 scrn_sz(1) = 0;
3918 // FIXME: this assumes the system font is Helvetica 10pt
3919 // (for which "x" requires 6x12 pixels at 74.951 pixels/inch)
3920 scrn_sz(2) *= 74.951 / 12.0 / dpi;
3921 scrn_sz(3) *= 74.951 / 12.0 / dpi;
3922 }
3923
3924 set_screensize (scrn_sz);
3925}
3926
3927Matrix
3928root_figure::properties::get_boundingbox (bool, const Matrix&) const
3929{
3930 Matrix screen_size = screen_size_pixels ();
3931 Matrix pos = Matrix (1, 4, 0.0);
3932
3933 pos(2) = screen_size(0);
3934 pos(3) = screen_size(1);
3935
3936 return pos;
3937}
3938
3939/*
3940%!test
3941%! old_units = get (0, "units");
3942%! unwind_protect
3943%! set (0, "units", "pixels");
3944%! sz = get (0, "screensize") - [1, 1, 0, 0];
3945%! dpi = get (0, "screenpixelsperinch");
3946%! set (0, "units", "inches");
3947%! assert (get (0, "screensize"), sz / dpi, 0.5 / dpi);
3948%! set (0, "units", "centimeters");
3949%! assert (get (0, "screensize"), sz / dpi * 2.54, 0.5 / dpi * 2.54);
3950%! set (0, "units", "points");
3951%! assert (get (0, "screensize"), sz / dpi * 72, 0.5 / dpi * 72);
3952%! set (0, "units", "normalized");
3953%! assert (get (0, "screensize"), [0.0, 0.0, 1.0, 1.0]);
3954%! set (0, "units", "pixels");
3955%! assert (get (0, "screensize"), sz + [1, 1, 0, 0]);
3956%! set (0, "units", "characters");
3957%! assert (get (0, "screensize"),
3958%! sz / dpi * (74.951 / 12.0), 0.5 / dpi * (74.951 / 12.0));
3959%! unwind_protect_cleanup
3960%! set (0, "units", old_units);
3961%! end_unwind_protect
3962*/
3963
3964void
3965root_figure::properties::remove_child (const graphics_handle& h, bool)
3966{
3967 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3968
3969 gh_mgr.pop_figure (h);
3970
3971 graphics_handle cf = gh_mgr.current_figure ();
3972
3973 xset (0, "currentfigure", cf.value ());
3974
3975 base_properties::remove_child (h, true);
3976}
3977
3978void
3979root_figure::reset_default_properties ()
3980{
3981 // empty list of local defaults
3982 m_default_properties = property_list ();
3983
3984 remove_all_listeners ();
3985 xreset_default_properties (get_handle (),
3986 m_properties.factory_defaults ());
3987}
3988
3989// ---------------------------------------------------------------------
3990
3991void
3992figure::properties::set_currentaxes (const octave_value& val)
3993{
3994 graphics_handle hax (val);
3995
3996 if (octave::math::isnan (hax.value ()) || ishghandle (hax))
3997 m_currentaxes = hax;
3998 else
3999 err_set_invalid ("currentaxes");
4000}
4001
4002void
4003figure::properties::remove_child (const graphics_handle& h, bool from_root)
4004{
4005 base_properties::remove_child (h, from_root);
4006
4007 if (h == m_currentaxes.handle_value ())
4008 {
4009 graphics_handle new_currentaxes;
4010
4011 Matrix kids = get_children ();
4012
4013 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
4014
4015 for (octave_idx_type i = 0; i < kids.numel (); i++)
4016 {
4017 graphics_handle kid = kids(i);
4018
4019 graphics_object go = gh_mgr.get_object (kid);
4020
4021 if (go.isa ("axes"))
4022 {
4023 new_currentaxes = kid;
4024 break;
4025 }
4026 }
4027
4028 m_currentaxes = new_currentaxes;
4029 }
4030}
4031
4033figure::properties::get_number () const
4034{
4035 if (m_integerhandle.is_on ())
4036 return m___myhandle__.value ();
4037 else
4038 return Matrix ();
4039}
4040
4041octave::graphics_toolkit
4042figure::properties::get_toolkit () const
4043{
4044 return m_toolkit;
4045}
4046
4047void
4048figure::properties::set___graphics_toolkit__ (const octave_value& val)
4049{
4050 if (! val.is_string ())
4051 error ("set___graphics_toolkit__: toolkit must be a string");
4052
4053 std::string nm = val.string_value ();
4054
4055 octave::gtk_manager& gtk_mgr = octave::__get_gtk_manager__ ();
4056
4057 octave::graphics_toolkit b = gtk_mgr.find_toolkit (nm);
4058
4059 if (b.get_name () != nm)
4060 error ("set___graphics_toolkit__: invalid graphics toolkit");
4061
4062 if (nm != get___graphics_toolkit__ ())
4063 {
4064 set_toolkit (b);
4065 mark_modified ();
4066 }
4067}
4068
4069void
4070figure::properties::adopt (const graphics_handle& h)
4071{
4072 base_properties::adopt (h);
4073
4074 if (! get_currentaxes ().ok ())
4075 {
4076 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
4077
4078 graphics_object go = gh_mgr.get_object (h);
4079
4080 if (go.type () == "axes")
4081 set_currentaxes (h.as_octave_value ());
4082 }
4083}
4084
4085/*
4086%!test
4087%! hf1 = figure ("visible", "off");
4088%! ax1 = subplot (1,2,1);
4089%! ax2 = subplot (1,2,2);
4090%! hf2 = figure ("visible", "off");
4091%! unwind_protect
4092%! set (ax2, "parent", hf2);
4093%! assert (get (hf2, "currentaxes"), ax2);
4094%! assert (get (hf1, "currentaxes"), ax1);
4095%! set (ax1, "parent", hf2);
4096%! assert (get (hf2, "currentaxes"), ax2);
4097%! unwind_protect_cleanup
4098%! close (hf1);
4099%! close (hf2);
4100%! end_unwind_protect
4101*/
4102
4103void
4104figure::properties::set_visible (const octave_value& val)
4105{
4106 std::string sval = val.string_value ();
4107
4108 if (sval == "on")
4109 xset (0, "currentfigure", m___myhandle__.value ());
4110
4111 m_visible = val;
4112}
4113
4114Matrix
4115figure::properties::get_boundingbox (bool internal, const Matrix&) const
4116{
4117#if defined (__APPLE__) && defined (__MACH__)
4118 // On macOS with Retina display, it looks like we'd need to divide the
4119 // "__device_pixel_ratio__" (reported by Qt) by the "backingScaleFactor":
4120 // https://developer.apple.com/documentation/appkit/nswindow/1419459-backingscalefactor
4121 // It might also be that we'd need to multiply the screensize (in pixels) by
4122 // the "backingScaleFactor" instead.
4123 // Afaict, that factor is only available via an Objective-C-API.
4124 // FIXME: Check how to get that from C++.
4125 // As a workaround, assume that we are either on a display prior to Retina
4126 // scaling where "__device_pixel_ratio__" and "backingScaleFactor" are both
4127 // 1, or we are on a Retina display where both are probably 2. The latter
4128 // might not always be the case.
4129 double dpr = 1.0;
4130#else
4131 double dpr = get___device_pixel_ratio__ ();
4132#endif
4133 Matrix screen_size = screen_size_pixels ();
4134 Matrix pos = (internal ?
4135 get_position ().matrix_value () :
4136 get_outerposition ().matrix_value ());
4137
4138 pos = convert_position (pos, get_units (), "pixels", screen_size);
4139
4140 pos(0)--;
4141 pos(1)--;
4142 pos(1) = screen_size(1) / dpr - pos(1) - pos(3);
4143
4144 return pos;
4145}
4146
4147Matrix
4148figure::properties::bbox2position (const Matrix& bb) const
4149{
4150#if defined (__APPLE__) && defined (__MACH__)
4151 // FIXME: See comment in figure::properties::get_boundingbox.
4152 double dpr = 1.0;
4153#else
4154 double dpr = get___device_pixel_ratio__ ();
4155#endif
4156 Matrix screen_size = screen_size_pixels ();
4157 Matrix pos = bb;
4158
4159 pos(1) = screen_size(1) - (pos(1) + pos(3)) * dpr;
4160 pos(1)++;
4161 pos(0)++;
4162 pos = convert_position (pos, "pixels", get_units (), screen_size);
4163 return pos;
4164}
4165
4166void
4167figure::properties::set_boundingbox (const Matrix& bb, bool internal,
4168 bool do_notify_toolkit)
4169{
4170 Matrix pos = bbox2position (bb);
4171
4172 if (internal)
4173 set_position (pos, do_notify_toolkit);
4174 else
4175 set_outerposition (pos, do_notify_toolkit);
4176}
4177
4178Matrix
4179figure::properties::map_from_boundingbox (double x, double y) const
4180{
4181 Matrix bb = get_boundingbox (true);
4182 Matrix pos (1, 2, 0.0);
4183
4184 pos(0) = x;
4185 pos(1) = y;
4186
4187 pos(1) = bb(3) - pos(1);
4188 pos(0)++;
4189 pos = convert_position (pos, "pixels", get_units (),
4190 bb.extract_n (0, 2, 1, 2));
4191
4192 return pos;
4193}
4194
4195Matrix
4196figure::properties::map_to_boundingbox (double x, double y) const
4197{
4198 Matrix bb = get_boundingbox (true);
4199 Matrix pos (1, 2, 0.0);
4200
4201 pos(0) = x;
4202 pos(1) = y;
4203
4204 pos = convert_position (pos, get_units (), "pixels",
4205 bb.extract_n (0, 2, 1, 2));
4206 pos(0)--;
4207 pos(1) = bb(3) - pos(1);
4208
4209 return pos;
4210}
4211
4212void
4213figure::properties::set_position (const octave_value& v,
4214 bool do_notify_toolkit)
4215{
4216 Matrix old_bb, new_bb;
4217 bool modified = false;
4218
4219 old_bb = get_boundingbox (true);
4220 modified = m_position.set (v, false, do_notify_toolkit);
4221 new_bb = get_boundingbox (true);
4222
4223 if (old_bb != new_bb)
4224 {
4225 if (old_bb(2) != new_bb(2) || old_bb(3) != new_bb(3))
4226 {
4227 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
4228
4229 if (! get_resizefcn ().isempty ())
4230 gh_mgr.post_callback (m___myhandle__, "resizefcn");
4231
4232 if (! get_sizechangedfcn ().isempty ())
4233 gh_mgr.post_callback (m___myhandle__, "sizechangedfcn");
4234
4235 update_boundingbox ();
4236 }
4237 }
4238
4239 if (modified)
4240 {
4241 m_position.run_listeners (GCB_POSTSET);
4242 mark_modified ();
4243 }
4244
4245 if (m_paperpositionmode.is ("auto"))
4246 m_paperposition.set (get_auto_paperposition ());
4247}
4248
4249void
4250figure::properties::set_outerposition (const octave_value& v,
4251 bool do_notify_toolkit)
4252{
4253 if (m_outerposition.set (v, true, do_notify_toolkit))
4254 mark_modified ();
4255}
4256
4257void
4258figure::properties::set_paperunits (const octave_value& val)
4259{
4260 caseless_str punits = val.string_value ();
4261 caseless_str ptype = get_papertype ();
4262
4263 if (punits.compare ("normalized") && ptype.compare ("<custom>"))
4264 error ("set: can't set paperunits to normalized when papertype is custom");
4265
4266 caseless_str old_paperunits = get_paperunits ();
4267 if (m_paperunits.set (val, true))
4268 {
4269 update_paperunits (old_paperunits);
4270 mark_modified ();
4271 }
4272}
4273
4274void
4275figure::properties::set_papertype (const octave_value& val)
4276{
4277 caseless_str ptype = val.string_value ();
4278 caseless_str punits = get_paperunits ();
4279
4280 if (punits.compare ("normalized") && ptype.compare ("<custom>"))
4281 error ("set: can't set paperunits to normalized when papertype is custom");
4282
4283 if (m_papertype.set (val, true))
4284 {
4285 update_papertype ();
4286 mark_modified ();
4287 }
4288}
4289
4290static Matrix
4291papersize_from_type (const caseless_str punits, const caseless_str ptype)
4292{
4293 Matrix retval (1, 2, 1.0);
4294
4295 if (! punits.compare ("normalized"))
4296 {
4297 double in2units;
4298 double mm2units;
4299
4300 if (punits.compare ("inches"))
4301 {
4302 in2units = 1.0;
4303 mm2units = 1 / 25.4;
4304 }
4305 else if (punits.compare ("centimeters"))
4306 {
4307 in2units = 2.54;
4308 mm2units = 1 / 10.0;
4309 }
4310 else // points
4311 {
4312 in2units = 72.0;
4313 mm2units = 72.0 / 25.4;
4314 }
4315
4316 if (ptype.compare ("usletter"))
4317 {
4318 retval(0) = 8.5 * in2units;
4319 retval(1) = 11.0 * in2units;
4320 }
4321 else if (ptype.compare ("uslegal"))
4322 {
4323 retval(0) = 8.5 * in2units;
4324 retval(1) = 14.0 * in2units;
4325 }
4326 else if (ptype.compare ("tabloid"))
4327 {
4328 retval(0) = 11.0 * in2units;
4329 retval(1) = 17.0 * in2units;
4330 }
4331 else if (ptype.compare ("a0"))
4332 {
4333 retval(0) = 841.0 * mm2units;
4334 retval(1) = 1189.0 * mm2units;
4335 }
4336 else if (ptype.compare ("a1"))
4337 {
4338 retval(0) = 594.0 * mm2units;
4339 retval(1) = 841.0 * mm2units;
4340 }
4341 else if (ptype.compare ("a2"))
4342 {
4343 retval(0) = 420.0 * mm2units;
4344 retval(1) = 594.0 * mm2units;
4345 }
4346 else if (ptype.compare ("a3"))
4347 {
4348 retval(0) = 297.0 * mm2units;
4349 retval(1) = 420.0 * mm2units;
4350 }
4351 else if (ptype.compare ("a4"))
4352 {
4353 retval(0) = 210.0 * mm2units;
4354 retval(1) = 297.0 * mm2units;
4355 }
4356 else if (ptype.compare ("a5"))
4357 {
4358 retval(0) = 148.0 * mm2units;
4359 retval(1) = 210.0 * mm2units;
4360 }
4361 else if (ptype.compare ("b0"))
4362 {
4363 retval(0) = 1029.0 * mm2units;
4364 retval(1) = 1456.0 * mm2units;
4365 }
4366 else if (ptype.compare ("b1"))
4367 {
4368 retval(0) = 728.0 * mm2units;
4369 retval(1) = 1028.0 * mm2units;
4370 }
4371 else if (ptype.compare ("b2"))
4372 {
4373 retval(0) = 514.0 * mm2units;
4374 retval(1) = 728.0 * mm2units;
4375 }
4376 else if (ptype.compare ("b3"))
4377 {
4378 retval(0) = 364.0 * mm2units;
4379 retval(1) = 514.0 * mm2units;
4380 }
4381 else if (ptype.compare ("b4"))
4382 {
4383 retval(0) = 257.0 * mm2units;
4384 retval(1) = 364.0 * mm2units;
4385 }
4386 else if (ptype.compare ("b5"))
4387 {
4388 retval(0) = 182.0 * mm2units;
4389 retval(1) = 257.0 * mm2units;
4390 }
4391 else if (ptype.compare ("arch-a"))
4392 {
4393 retval(0) = 9.0 * in2units;
4394 retval(1) = 12.0 * in2units;
4395 }
4396 else if (ptype.compare ("arch-b"))
4397 {
4398 retval(0) = 12.0 * in2units;
4399 retval(1) = 18.0 * in2units;
4400 }
4401 else if (ptype.compare ("arch-c"))
4402 {
4403 retval(0) = 18.0 * in2units;
4404 retval(1) = 24.0 * in2units;
4405 }
4406 else if (ptype.compare ("arch-d"))
4407 {
4408 retval(0) = 24.0 * in2units;
4409 retval(1) = 36.0 * in2units;
4410 }
4411 else if (ptype.compare ("arch-e"))
4412 {
4413 retval(0) = 36.0 * in2units;
4414 retval(1) = 48.0 * in2units;
4415 }
4416 else if (ptype.compare ("a"))
4417 {
4418 retval(0) = 8.5 * in2units;
4419 retval(1) = 11.0 * in2units;
4420 }
4421 else if (ptype.compare ("b"))
4422 {
4423 retval(0) = 11.0 * in2units;
4424 retval(1) = 17.0 * in2units;
4425 }
4426 else if (ptype.compare ("c"))
4427 {
4428 retval(0) = 17.0 * in2units;
4429 retval(1) = 22.0 * in2units;
4430 }
4431 else if (ptype.compare ("d"))
4432 {
4433 retval(0) = 22.0 * in2units;
4434 retval(1) = 34.0 * in2units;
4435 }
4436 else if (ptype.compare ("e"))
4437 {
4438 retval(0) = 34.0 * in2units;
4439 retval(1) = 43.0 * in2units;
4440 }
4441 }
4442
4443 return retval;
4444}
4445
4446Matrix
4447figure::properties::get_auto_paperposition ()
4448{
4449 Matrix pos = get_position ().matrix_value ();
4450 Matrix sz;
4451
4452 caseless_str funits = get_units ();
4453 caseless_str punits = get_paperunits ();
4454
4455 // Convert position from figure units to paperunits
4456 if (funits == "normalized" || punits == "normalized")
4457 {
4458 sz = screen_size_pixels ();
4459 pos = convert_position (pos, funits, "inches", sz);
4460
4461 if (punits == "normalized")
4462 sz = papersize_from_type ("points", get_papertype ());
4463
4464 pos = convert_position (pos, "inches", punits, sz);
4465 }
4466 else
4467 pos = convert_position (pos, funits, punits, sz);
4468
4469 // Center the figure on the page
4470 sz = get_papersize ().matrix_value ();
4471
4472 pos(0) = sz(0)/2 - pos(2)/2;
4473 pos(1) = sz(1)/2 - pos(3)/2;
4474
4475 return pos;
4476}
4477
4478/*
4479%!test
4480%! hf = figure ("visible", "off", "paperpositionmode", "auto");
4481%! in_pos = [0 0 4 5];
4482%! tol = 20 * eps ();
4483%! unwind_protect
4484%! ## paperpositionmode "auto" converts figure size to paper units
4485%! set (hf, "units", "inches");
4486%! set (hf, "position", in_pos);
4487%! set (hf, "paperunits", "centimeters");
4488%! psz = get (hf, "papersize");
4489%! fsz = in_pos(3:4) * 2.54;
4490%! pos = [(psz/2 - fsz/2) fsz];
4491%! set (hf, "paperpositionmode", "auto");
4492%! assert (get (hf, "paperposition"), pos, tol);
4493%! unwind_protect_cleanup
4494%! close (hf);
4495%! end_unwind_protect
4496
4497%!test
4498%! hf = figure ("visible", "off", "paperpositionmode", "auto");
4499%! in_pos = [0 0 4 5];
4500%! tol = 20 * eps ();
4501%! unwind_protect
4502%! ## likewise with normalized units
4503%! set (hf, "units", "inches");
4504%! set (hf, "position", in_pos);
4505%! psz = get (hf, "papersize");
4506%! set (hf, "paperunits", "normalized");
4507%! fsz = in_pos(3:4) ./ psz;
4508%! pos = [([0.5 0.5] - fsz/2) fsz];
4509%! assert (get (hf, "paperposition"), pos, tol);
4510%! unwind_protect_cleanup
4511%! close (hf);
4512%! end_unwind_protect
4513
4514%!test
4515%! hf = figure ("visible", "off", "paperpositionmode", "auto");
4516%! in_pos = [0 0 4 5];
4517%! tol = 20 * eps ();
4518%! unwind_protect
4519%! ## changing papertype updates paperposition
4520%! set (hf, "units", "inches");
4521%! set (hf, "position", in_pos);
4522%! set (hf, "papertype", "a4");
4523%! psz = get (hf, "papersize");
4524%! fsz = in_pos(3:4);
4525%! pos = [(psz/2 - fsz/2) fsz];
4526%! assert (get (hf, "paperposition"), pos, tol);
4527%! unwind_protect_cleanup
4528%! close (hf);
4529%! end_unwind_protect
4530
4531%!test
4532%! hf = figure ("visible", "off", "paperpositionmode", "auto");
4533%! in_pos = [0 0 4 5];
4534%! tol = 20 * eps ();
4535%! unwind_protect
4536%! ## lanscape updates paperposition
4537%! set (hf, "units", "inches");
4538%! set (hf, "position", in_pos);
4539%! set (hf, "paperorientation", "landscape");
4540%! psz = get (hf, "papersize");
4541%! fsz = in_pos(3:4);
4542%! pos = [(psz/2 - fsz/2) fsz];
4543%! assert (get (hf, "paperposition"), pos, tol);
4544%! unwind_protect_cleanup
4545%! close (hf);
4546%! end_unwind_protect
4547
4548%!test
4549%! hf = figure ("visible", "off", "paperpositionmode", "auto");
4550%! in_pos = [0 0 4 5];
4551%! unwind_protect
4552%! ## back to manual mode
4553%! set (hf, "paperposition", in_pos * 1.1);
4554%! assert (get (hf, "paperpositionmode"), "manual");
4555%! assert (get (hf, "paperposition"), in_pos * 1.1);
4556%! unwind_protect_cleanup
4557%! close (hf);
4558%! end_unwind_protect
4559*/
4560
4561void
4562figure::properties::update_paperunits (const caseless_str& old_paperunits)
4563{
4564 Matrix pos = get_paperposition ().matrix_value ();
4565 Matrix sz = get_papersize ().matrix_value ();
4566
4567 pos(0) /= sz(0);
4568 pos(1) /= sz(1);
4569 pos(2) /= sz(0);
4570 pos(3) /= sz(1);
4571
4572 std::string porient = get_paperorientation ();
4573 caseless_str punits = get_paperunits ();
4574 caseless_str ptype = get_papertype ();
4575
4576 if (ptype.compare ("<custom>"))
4577 {
4578 if (old_paperunits.compare ("centimeters"))
4579 {
4580 sz(0) /= 2.54;
4581 sz(1) /= 2.54;
4582 }
4583 else if (old_paperunits.compare ("points"))
4584 {
4585 sz(0) /= 72.0;
4586 sz(1) /= 72.0;
4587 }
4588
4589 if (punits.compare ("centimeters"))
4590 {
4591 sz(0) *= 2.54;
4592 sz(1) *= 2.54;
4593 }
4594 else if (punits.compare ("points"))
4595 {
4596 sz(0) *= 72.0;
4597 sz(1) *= 72.0;
4598 }
4599 }
4600 else
4601 {
4602 sz = papersize_from_type (punits, ptype);
4603 if (porient == "landscape")
4604 std::swap (sz(0), sz(1));
4605 }
4606
4607 pos(0) *= sz(0);
4608 pos(1) *= sz(1);
4609 pos(2) *= sz(0);
4610 pos(3) *= sz(1);
4611
4612 m_papersize.set (octave_value (sz));
4613 m_paperposition.set (octave_value (pos));
4614}
4615
4616void
4617figure::properties::update_papertype ()
4618{
4619 std::string typ = get_papertype ();
4620 if (typ != "<custom>")
4621 {
4622 Matrix sz = papersize_from_type (get_paperunits (), typ);
4623 if (get_paperorientation () == "landscape")
4624 std::swap (sz(0), sz(1));
4625 // Call papersize.set rather than set_papersize to avoid loops
4626 // between update_papersize and update_papertype.
4627 m_papersize.set (octave_value (sz));
4628 }
4629
4630 if (m_paperpositionmode.is ("auto"))
4631 m_paperposition.set (get_auto_paperposition ());
4632}
4633
4634void
4635figure::properties::update_papersize ()
4636{
4637 Matrix sz = get_papersize ().matrix_value ();
4638 if (sz(0) > sz(1))
4639 {
4640 std::swap (sz(0), sz(1));
4641 m_papersize.set (octave_value (sz));
4642 m_paperorientation.set (octave_value ("landscape"));
4643 }
4644 else
4645 {
4646 m_paperorientation.set ("portrait");
4647 }
4648
4649 std::string punits = get_paperunits ();
4650 if (punits == "centimeters")
4651 {
4652 sz(0) /= 2.54;
4653 sz(1) /= 2.54;
4654 }
4655 else if (punits == "points")
4656 {
4657 sz(0) /= 72.0;
4658 sz(1) /= 72.0;
4659 }
4660 if (punits == "normalized")
4661 {
4662 if (get_papertype () == "<custom>")
4663 error ("set: can't set the papertype to <custom> when the paperunits is normalized");
4664 }
4665 else
4666 {
4667 // FIXME: The papersizes info is also in papersize_from_type().
4668 // Both should be rewritten to avoid the duplication.
4669 // Don't Repeat Yourself (DRY) principle.
4670 std::string ptype = "<custom>";
4671 const double mm2in = 1.0 / 25.4;
4672 const double tol = 0.01;
4673
4674 if (std::abs (sz(0) - 8.5) + std::abs (sz(1) - 11.0) < tol)
4675 ptype = "usletter";
4676 else if (std::abs (sz(0) - 8.5) + std::abs (sz(1) - 14.0) < tol)
4677 ptype = "uslegal";
4678 else if (std::abs (sz(0) - 11.0) + std::abs (sz(1) - 17.0) < tol)
4679 ptype = "tabloid";
4680 else if (std::abs (sz(0) - 841.0 * mm2in)
4681 + std::abs (sz(1) - 1198.0 * mm2in) < tol)
4682 ptype = "a0";
4683 else if (std::abs (sz(0) - 594.0 * mm2in)
4684 + std::abs (sz(1) - 841.0 * mm2in) < tol)
4685 ptype = "a1";
4686 else if (std::abs (sz(0) - 420.0 * mm2in)
4687 + std::abs (sz(1) - 594.0 * mm2in) < tol)
4688 ptype = "a2";
4689 else if (std::abs (sz(0) - 297.0 * mm2in)
4690 + std::abs (sz(1) - 420.0 * mm2in) < tol)
4691 ptype = "a3";
4692 else if (std::abs (sz(0) - 210.0 * mm2in)
4693 + std::abs (sz(1) - 297.0 * mm2in) < tol)
4694 ptype = "a4";
4695 else if (std::abs (sz(0) - 148.0 * mm2in)
4696 + std::abs (sz(1) - 210.0 * mm2in) < tol)
4697 ptype = "a5";
4698 else if (std::abs (sz(0) - 1029.0 * mm2in)
4699 + std::abs (sz(1) - 1456.0 * mm2in) < tol)
4700 ptype = "b0";
4701 else if (std::abs (sz(0) - 728.0 * mm2in)
4702 + std::abs (sz(1) - 1028.0 * mm2in) < tol)
4703 ptype = "b1";
4704 else if (std::abs (sz(0) - 514.0 * mm2in)
4705 + std::abs (sz(1) - 728.0 * mm2in) < tol)
4706 ptype = "b2";
4707 else if (std::abs (sz(0) - 364.0 * mm2in)
4708 + std::abs (sz(1) - 514.0 * mm2in) < tol)
4709 ptype = "b3";
4710 else if (std::abs (sz(0) - 257.0 * mm2in)
4711 + std::abs (sz(1) - 364.0 * mm2in) < tol)
4712 ptype = "b4";
4713 else if (std::abs (sz(0) - 182.0 * mm2in)
4714 + std::abs (sz(1) - 257.0 * mm2in) < tol)
4715 ptype = "b5";
4716 else if (std::abs (sz(0) - 9.0)
4717 + std::abs (sz(1) - 12.0) < tol)
4718 ptype = "arch-a";
4719 else if (std::abs (sz(0) - 12.0)
4720 + std::abs (sz(1) - 18.0) < tol)
4721 ptype = "arch-b";
4722 else if (std::abs (sz(0) - 18.0)
4723 + std::abs (sz(1) - 24.0) < tol)
4724 ptype = "arch-c";
4725 else if (std::abs (sz(0) - 24.0)
4726 + std::abs (sz(1) - 36.0) < tol)
4727 ptype = "arch-d";
4728 else if (std::abs (sz(0) - 36.0)
4729 + std::abs (sz(1) - 48.0) < tol)
4730 ptype = "arch-e";
4731 else if (std::abs (sz(0) - 8.5)
4732 + std::abs (sz(1) - 11.0) < tol)
4733 ptype = "a";
4734 else if (std::abs (sz(0) - 11.0)
4735 + std::abs (sz(1) - 17.0) < tol)
4736 ptype = "b";
4737 else if (std::abs (sz(0) - 17.0)
4738 + std::abs (sz(1) - 22.0) < tol)
4739 ptype = "c";
4740 else if (std::abs (sz(0) - 22.0)
4741 + std::abs (sz(1) - 34.0) < tol)
4742 ptype = "d";
4743 else if (std::abs (sz(0) - 34.0)
4744 + std::abs (sz(1) - 43.0) < tol)
4745 ptype = "e";
4746 // Call papertype.set rather than set_papertype to avoid loops between
4747 // update_papersize and update_papertype
4748 m_papertype.set (ptype);
4749 }
4750 if (punits == "centimeters")
4751 {
4752 sz(0) *= 2.54;
4753 sz(1) *= 2.54;
4754 }
4755 else if (punits == "points")
4756 {
4757 sz(0) *= 72.0;
4758 sz(1) *= 72.0;
4759 }
4760 if (get_paperorientation () == "landscape")
4761 {
4762 std::swap (sz(0), sz(1));
4763 m_papersize.set (octave_value (sz));
4764 }
4765
4766 if (m_paperpositionmode.is ("auto"))
4767 m_paperposition.set (get_auto_paperposition ());
4768}
4769
4770/*
4771%!test
4772%! hf = figure ("visible", "off");
4773%! unwind_protect
4774%! set (hf, "paperunits", "inches");
4775%! set (hf, "papersize", [5, 4]);
4776%! set (hf, "paperunits", "points");
4777%! assert (get (hf, "papersize"), [5, 4] * 72, 1);
4778%! papersize = get (hf, "papersize");
4779%! set (hf, "papersize", papersize + 1);
4780%! set (hf, "papersize", papersize);
4781%! assert (get (hf, "papersize"), [5, 4] * 72, 1);
4782%! unwind_protect_cleanup
4783%! close (hf);
4784%! end_unwind_protect
4785
4786%!test
4787%! hf = figure ("visible", "off");
4788%! unwind_protect
4789%! set (hf, "paperunits", "inches");
4790%! set (hf, "papersize", [5, 4]);
4791%! set (hf, "paperunits", "centimeters");
4792%! assert (get (hf, "papersize"), [5, 4] * 2.54, 2.54/72);
4793%! papersize = get (hf, "papersize");
4794%! set (hf, "papersize", papersize + 1);
4795%! set (hf, "papersize", papersize);
4796%! assert (get (hf, "papersize"), [5, 4] * 2.54, 2.54/72);
4797%! unwind_protect_cleanup
4798%! close (hf);
4799%! end_unwind_protect
4800*/
4801
4802void
4803figure::properties::update_paperorientation ()
4804{
4805 std::string porient = get_paperorientation ();
4806 Matrix sz = get_papersize ().matrix_value ();
4807 if ((sz(0) > sz(1) && porient == "portrait")
4808 || (sz(0) < sz(1) && porient == "landscape"))
4809 {
4810 std::swap (sz(0), sz(1));
4811 // Call papertype.set rather than set_papertype to avoid loops
4812 // between update_papersize and update_papertype
4813 m_papersize.set (octave_value (sz));
4814 }
4815
4816 if (m_paperpositionmode.is ("auto"))
4817 m_paperposition.set (get_auto_paperposition ());
4818}
4819
4820/*
4821%!test
4822%! hf = figure ("visible", "off");
4823%! unwind_protect
4824%! tol = 100 * eps ();
4825%! ## UPPER case and MiXed case is part of test and should not be changed.
4826%! set (hf, "paperorientation", "PORTRAIT");
4827%! set (hf, "paperunits", "inches");
4828%! set (hf, "papertype", "USletter");
4829%! assert (get (hf, "papersize"), [8.5, 11.0], tol);
4830%! set (hf, "paperorientation", "Landscape");
4831%! assert (get (hf, "papersize"), [11.0, 8.5], tol);
4832%! set (hf, "paperunits", "centimeters");
4833%! assert (get (hf, "papersize"), [11.0, 8.5] * 2.54, tol);
4834%! set (hf, "papertype", "a4");
4835%! assert (get (hf, "papersize"), [29.7, 21.0], tol);
4836%! set (hf, "paperunits", "inches", "papersize", [8.5, 11.0]);
4837%! assert (get (hf, "papertype"), "usletter");
4838%! assert (get (hf, "paperorientation"), "portrait");
4839%! set (hf, "papersize", [11.0, 8.5]);
4840%! assert (get (hf, "papertype"), "usletter");
4841%! assert (get (hf, "paperorientation"), "landscape");
4842%! unwind_protect_cleanup
4843%! close (hf);
4844%! end_unwind_protect
4845*/
4846
4847void
4848figure::properties::set_units (const octave_value& val)
4849{
4850 caseless_str old_units = get_units ();
4851
4852 if (m_units.set (val, true))
4853 {
4854 update_units (old_units);
4855 mark_modified ();
4856 }
4857}
4858
4859void
4860figure::properties::update_units (const caseless_str& old_units)
4861{
4862 m_position.set (convert_position (get_position ().matrix_value (),
4863 old_units, get_units (),
4864 screen_size_pixels ()), false);
4865}
4866
4867/*
4868%!test
4869%! hf = figure ("visible", "off");
4870%! old_units = get (0, "units");
4871%! unwind_protect
4872%! set (0, "units", "pixels");
4873%! rsz = get (0, "screensize");
4874%! set (gcf (), "units", "pixels");
4875%! fsz = get (gcf (), "position");
4876%! set (gcf (), "units", "normalized");
4877%! pos = get (gcf (), "position");
4878%! assert (pos, (fsz - [1, 1, 0, 0]) ./ rsz([3, 4, 3, 4]));
4879%! unwind_protect_cleanup
4880%! close (hf);
4881%! set (0, "units", old_units);
4882%! end_unwind_protect
4883*/
4884
4885std::string
4886figure::properties::get_title () const
4887{
4888 std::string title;
4889 if (! get_number ().isempty () && is_numbertitle ())
4890 {
4891 std::ostringstream os;
4892 std::string nm = get_name ();
4893
4894 os << "Figure " << m___myhandle__.value ();
4895 if (! nm.empty ())
4896 os << ": " << get_name ();
4897
4898 title = os.str ();
4899 }
4900 else
4901 title = get_name ();
4902
4903 // Qt will use QCoreApplication name (set in main-window.cc)
4904 // if the name is empty, so force blank.
4905 if (title.empty ())
4906 title = " ";
4907
4908 return title;
4909}
4910
4912figure::get_default (const caseless_str& name) const
4913{
4914 octave_value retval = m_default_properties.lookup (name);
4915
4916 if (retval.is_undefined ())
4917 {
4918 graphics_handle parent_h = get_parent ();
4919
4920 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
4921
4922 graphics_object parent_go = gh_mgr.get_object (parent_h);
4923
4924 retval = parent_go.get_default (name);
4925 }
4926
4927 return retval;
4928}
4929
4930void
4931figure::reset_default_properties ()
4932{
4933 // empty list of local defaults
4934 m_default_properties = property_list ();
4935 property_list::pval_map_type plist = m_properties.factory_defaults ();
4936
4937 plist.erase ("units");
4938 plist.erase ("position");
4939 plist.erase ("innerposition");
4940 plist.erase ("outerposition");
4941 plist.erase ("paperunits");
4942 plist.erase ("paperposition");
4943 plist.erase ("paperpositionmode");
4944 plist.erase ("windowstyle");
4945
4946 remove_all_listeners ();
4947 xreset_default_properties (get_handle (), plist);
4948}
4949
4950// ---------------------------------------------------------------------
4951
4952void
4953axes::properties::init ()
4954{
4955 m_position.add_constraint (dim_vector (1, 4));
4956 m_outerposition.add_constraint (dim_vector (1, 4));
4957 m_tightinset.add_constraint (dim_vector (1, 4));
4958 m_looseinset.add_constraint (dim_vector (1, 4));
4959 m_colororder.add_constraint (dim_vector (-1, 3));
4960 m_dataaspectratio.add_constraint (3);
4961 m_dataaspectratio.add_constraint ("min", 0, false);
4962 m_dataaspectratio.add_constraint (FINITE);
4963 m_plotboxaspectratio.add_constraint (3);
4964 m_plotboxaspectratio.add_constraint ("min", 0, false);
4965 m_plotboxaspectratio.add_constraint (FINITE);
4966 // FIXME: Should these use dimension vectors? Currently can set 'xlim' to
4967 // any matrix size, but only first two elements are used.
4968 m_alim.add_constraint (2);
4969 m_alim.add_constraint (NOT_NAN);
4970 m_clim.add_constraint (2);
4971 m_clim.add_constraint (NOT_NAN);
4972 m_xlim.add_constraint (2);
4973 m_xlim.add_constraint (NOT_NAN);
4974 m_ylim.add_constraint (2);
4975 m_ylim.add_constraint (NOT_NAN);
4976 m_zlim.add_constraint (2);
4977 m_zlim.add_constraint (NOT_NAN);
4978 m_xtick.add_constraint (dim_vector (1, -1));
4979 m_xtick.add_constraint (FINITE);
4980 m_ytick.add_constraint (dim_vector (1, -1));
4981 m_ytick.add_constraint (FINITE);
4982 m_ztick.add_constraint (dim_vector (1, -1));
4983 m_ztick.add_constraint (FINITE);
4984 m_ticklength.add_constraint (dim_vector (1, 2));
4985 Matrix vw (1, 2, 0);
4986 vw(1) = 90;
4987 m_view = vw;
4988 m_view.add_constraint (dim_vector (1, 2));
4989 m_cameraposition.add_constraint (3);
4990 m_cameraposition.add_constraint (FINITE);
4991 m_cameratarget.add_constraint (3);
4992 m_cameratarget.add_constraint (FINITE);
4993 Matrix upv (1, 3, 0.0);
4994 upv(2) = 1.0;
4995 m_cameraupvector = upv;
4996 m_cameraupvector.add_constraint (3);
4997 m_cameraupvector.add_constraint (FINITE);
4998 m_cameraviewangle.add_constraint (FINITE);
4999 m_currentpoint.add_constraint (dim_vector (2, 3));
5000
5001 // Range constraints for double properties
5002 m_fontsize.add_constraint ("min", 0.0, false);
5003 m_gridalpha.add_constraint ("min", 0.0, true);
5004 m_gridalpha.add_constraint ("max", 1.0, true);
5005 m_labelfontsizemultiplier.add_constraint ("min", 0.0, false);
5006 m_linewidth.add_constraint ("min", 0.0, false);
5007 m_minorgridalpha.add_constraint ("min", 0.0, true);
5008 m_minorgridalpha.add_constraint ("max", 1.0, true);
5009 m_titlefontsizemultiplier.add_constraint ("min", 0.0, false);
5010
5011 // No constraints for hidden transform properties
5012 update_font ();
5013
5014 m_x_zlim.resize (1, 2);
5015
5016 m_sx = "linear";
5017 m_sy = "linear";
5018 m_sz = "linear";
5019
5020 calc_ticklabels (m_xtick, m_xticklabel, m_xscale.is ("log"),
5021 xaxislocation_is ("origin"),
5022 m_yscale.is ("log") ? 2 : (yaxislocation_is ("origin") ? 0 :
5023 (yaxislocation_is ("left") ? -1 : 1)), m_xlim);
5024 calc_ticklabels (m_ytick, m_yticklabel, m_yscale.is ("log"),
5025 yaxislocation_is ("origin"),
5026 m_xscale.is ("log") ? 2 : (xaxislocation_is ("origin") ? 0 :
5027 (xaxislocation_is ("bottom") ? -1 : 1)), m_ylim);
5028 calc_ticklabels (m_ztick, m_zticklabel, m_zscale.is ("log"),
5029 false, 2, m_zlim);
5030
5031 xset (m_xlabel.handle_value (), "handlevisibility", "off");
5032 xset (m_ylabel.handle_value (), "handlevisibility", "off");
5033 xset (m_zlabel.handle_value (), "handlevisibility", "off");
5034 xset (m_title.handle_value (), "handlevisibility", "off");
5035
5036 xset (m_xlabel.handle_value (), "horizontalalignment", "center");
5037 xset (m_xlabel.handle_value (), "horizontalalignmentmode", "auto");
5038 xset (m_ylabel.handle_value (), "horizontalalignment", "center");
5039 xset (m_ylabel.handle_value (), "horizontalalignmentmode", "auto");
5040 xset (m_zlabel.handle_value (), "horizontalalignment", "right");
5041 xset (m_zlabel.handle_value (), "horizontalalignmentmode", "auto");
5042 xset (m_title.handle_value (), "horizontalalignment", "center");
5043 xset (m_title.handle_value (), "horizontalalignmentmode", "auto");
5044
5045 xset (m_xlabel.handle_value (), "verticalalignment", "top");
5046 xset (m_xlabel.handle_value (), "verticalalignmentmode", "auto");
5047 xset (m_ylabel.handle_value (), "verticalalignment", "bottom");
5048 xset (m_ylabel.handle_value (), "verticalalignmentmode", "auto");
5049 xset (m_title.handle_value (), "verticalalignment", "bottom");
5050 xset (m_title.handle_value (), "verticalalignmentmode", "auto");
5051
5052 xset (m_ylabel.handle_value (), "rotation", 90.0);
5053 xset (m_ylabel.handle_value (), "rotationmode", "auto");
5054
5055 xset (m_zlabel.handle_value (), "visible", "off");
5056
5057 xset (m_xlabel.handle_value (), "clipping", "off");
5058 xset (m_ylabel.handle_value (), "clipping", "off");
5059 xset (m_zlabel.handle_value (), "clipping", "off");
5060 xset (m_title.handle_value (), "clipping", "off");
5061
5062 xset (m_xlabel.handle_value (), "__autopos_tag__", "xlabel");
5063 xset (m_ylabel.handle_value (), "__autopos_tag__", "ylabel");
5064 xset (m_zlabel.handle_value (), "__autopos_tag__", "zlabel");
5065 xset (m_title.handle_value (), "__autopos_tag__", "title");
5066
5067 double fs = m_labelfontsizemultiplier.double_value () *
5068 m_fontsize.double_value ();
5069 xset (m_xlabel.handle_value (), "fontsize", octave_value (fs));
5070 xset (m_ylabel.handle_value (), "fontsize", octave_value (fs));
5071 xset (m_zlabel.handle_value (), "fontsize", octave_value (fs));
5072 fs = m_titlefontsizemultiplier.double_value () * m_fontsize.double_value ();
5073 xset (m_title.handle_value (), "fontsize", octave_value (fs));
5074 xset (m_title.handle_value (), "fontweight", m_titlefontweight.get ());
5075
5076 adopt (m_xlabel.handle_value ());
5077 adopt (m_ylabel.handle_value ());
5078 adopt (m_zlabel.handle_value ());
5079 adopt (m_title.handle_value ());
5080
5081 Matrix tlooseinset = default_axes_position ();
5082 tlooseinset(2) = 1-tlooseinset(0)-tlooseinset(2);
5083 tlooseinset(3) = 1-tlooseinset(1)-tlooseinset(3);
5084 m_looseinset = tlooseinset;
5085}
5086
5087/*
5088## Test validation of axes double properties range
5089%!test
5090%! hf = figure ("visible", "off");
5091%! unwind_protect
5092%! hax = axes ("parent", hf);
5093%! try
5094%! set (hax, "linewidth", -1);
5095%! catch
5096%! err = lasterr ();
5097%! end_try_catch
5098%! assert (err, 'set: "linewidth" must be greater than 0');
5099%! try
5100%! set (hax, "minorgridalpha", 1.5);
5101%! catch
5102%! err = lasterr ();
5103%! end_try_catch
5104%! assert (err, 'set: "minorgridalpha" must be less than or equal to 1');
5105%! unwind_protect_cleanup
5106%! delete (hf);
5107%! end_unwind_protect
5108*/
5109
5110Matrix
5111axes::properties::calc_tightbox (const Matrix& init_pos)
5112{
5113 Matrix pos = init_pos;
5114
5115 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
5116
5117 graphics_object go = gh_mgr.get_object (get_parent ());
5118
5119 Matrix parent_bb = go.get_properties ().get_boundingbox (true);
5120
5121 // FIXME: The layout should be clean at this stage and we should not have to
5122 // update ticks and labels positions here again. See bug #48718.
5123 update_ticklength ();
5124
5125 Matrix ext = get_extent (true, true);
5126 ext(1) = parent_bb(3) - ext(1) - ext(3);
5127 ext(0)++;
5128 ext(1)++;
5129 ext = convert_position (ext, "pixels", get_units (),
5130 parent_bb.extract_n (0, 2, 1, 2));
5131 if (ext(0) < pos(0))
5132 {
5133 pos(2) += pos(0)-ext(0);
5134 pos(0) = ext(0);
5135 }
5136 if (ext(0)+ext(2) > pos(0)+pos(2))
5137 pos(2) = ext(0)+ext(2)-pos(0);
5138
5139 if (ext(1) < pos(1))
5140 {
5141 pos(3) += pos(1)-ext(1);
5142 pos(1) = ext(1);
5143 }
5144 if (ext(1)+ext(3) > pos(1)+pos(3))
5145 pos(3) = ext(1)+ext(3)-pos(1);
5146
5147 return pos;
5148}
5149
5150void
5151axes::properties::sync_positions ()
5152{
5153 // First part is equivalent to 'update_tightinset ()'
5154 if (m_positionconstraint.is ("innerposition"))
5155 update_position ();
5156 else
5157 update_outerposition ();
5158 caseless_str old_units = get_units ();
5159 set_units ("normalized");
5160 Matrix pos = m_position.get ().matrix_value ();
5161 Matrix outpos = m_outerposition.get ().matrix_value ();
5162 Matrix tightpos = calc_tightbox (pos);
5163 Matrix tinset (1, 4, 1.0);
5164 tinset(0) = pos(0)-tightpos(0);
5165 tinset(1) = pos(1)-tightpos(1);
5166 tinset(2) = tightpos(0)+tightpos(2)-pos(0)-pos(2);
5167 tinset(3) = tightpos(1)+tightpos(3)-pos(1)-pos(3);
5168 m_tightinset = tinset;
5169 set_units (old_units);
5170 update_transform ();
5171 if (m_positionconstraint.is ("innerposition"))
5172 update_position ();
5173 else
5174 update_outerposition ();
5175}
5176
5177/*
5178%!testif HAVE_OPENGL, HAVE_QT; have_window_system () && any (strcmp ("qt", available_graphics_toolkits ()))
5179%! hf = figure ("visible", "off");
5180%! graphics_toolkit (hf, "qt");
5181%! unwind_protect
5182%! subplot (2,1,1); plot (rand (10,1)); subplot (2,1,2); plot (rand (10,1));
5183%! hax = findall (gcf (), "type", "axes");
5184%! positions = cell2mat (get (hax, "position"));
5185%! outerpositions = cell2mat (get (hax, "outerposition"));
5186%! looseinsets = cell2mat (get (hax, "looseinset"));
5187%! tightinsets = cell2mat (get (hax, "tightinset"));
5188%! subplot (2,1,1); plot (rand (10,1)); subplot (2,1,2); plot (rand (10,1));
5189%! hax = findall (gcf (), "type", "axes");
5190%! assert (cell2mat (get (hax, "position")), positions, 1e-4);
5191%! assert (cell2mat (get (hax, "outerposition")), outerpositions, 1e-4);
5192%! assert (cell2mat (get (hax, "looseinset")), looseinsets, 1e-4);
5193%! assert (cell2mat (get (hax, "tightinset")), tightinsets, 1e-4);
5194%! unwind_protect_cleanup
5195%! close (hf);
5196%! end_unwind_protect
5197
5198%!testif HAVE_OPENGL, HAVE_QT; have_window_system () && any (strcmp ("qt", available_graphics_toolkits ()))
5199%! hf = figure ("visible", "off");
5200%! graphics_toolkit (hf, "qt");
5201%! fpos = get (hf, "position");
5202%! unwind_protect
5203%! plot (rand (3));
5204%! position = get (gca, "position");
5205%! outerposition = get (gca, "outerposition");
5206%! looseinset = get (gca, "looseinset");
5207%! tightinset = get (gca, "tightinset");
5208%! set (hf, "position", [fpos(1:2), 2*fpos(3:4)]);
5209%! set (hf, "position", fpos);
5210%! assert (get (gca, "outerposition"), outerposition, 0.001);
5211%! assert (get (gca, "position"), position, 0.001);
5212%! assert (get (gca, "looseinset"), looseinset, 0.001);
5213%! assert (get (gca, "tightinset"), tightinset, 0.001);
5214%! unwind_protect_cleanup
5215%! close (hf);
5216%! end_unwind_protect
5217
5218%!testif HAVE_OPENGL, HAVE_QT; have_window_system () && any (strcmp ("qt", available_graphics_toolkits ()))
5219%! hf = figure ("visible", "off");
5220%! graphics_toolkit (hf, "qt");
5221%! fpos = get (hf, "position");
5222%! set (gca, "positionconstraint", "innerposition");
5223%! unwind_protect
5224%! plot (rand (3));
5225%! position = get (gca, "position");
5226%! outerposition = get (gca, "outerposition");
5227%! looseinset = get (gca, "looseinset");
5228%! tightinset = get (gca, "tightinset");
5229%! set (hf, "position", [fpos(1:2), 2*fpos(3:4)]);
5230%! set (hf, "position", fpos);
5231%! assert (get (gca, "position"), position, 0.001);
5232%! assert (get (gca, "outerposition"), outerposition, 0.001);
5233%! assert (get (gca, "looseinset"), looseinset, 0.001);
5234%! assert (get (gca, "tightinset"), tightinset, 0.001);
5235%! unwind_protect_cleanup
5236%! close (hf);
5237%! end_unwind_protect
5238*/
5239
5240void
5241axes::properties::set_text_child (handle_property& hp,
5242 const std::string& who,
5243 const octave_value& v)
5244{
5245 if (v.is_string ())
5246 {
5247 xset (hp.handle_value (), "string", v);
5248 return;
5249 }
5250
5251 graphics_handle val;
5252
5253 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
5254
5255 graphics_object go = gh_mgr.get_object (gh_mgr.lookup (v));
5256
5257 if (go.isa ("text"))
5258 val = octave::reparent (v, "set", who, m___myhandle__, false);
5259 else
5260 {
5261 std::string cname = v.class_name ();
5262
5263 error ("set: expecting text graphics object or character string for %s property, found %s",
5264 who.c_str (), cname.c_str ());
5265 }
5266
5267 xset (val, "handlevisibility", "off");
5268
5269 gh_mgr.free (hp.handle_value ());
5270
5271 hp = val;
5272
5273 adopt (hp.handle_value ());
5274}
5275
5276void
5277axes::properties::set_xlabel (const octave_value& v)
5278{
5279 set_text_child (m_xlabel, "xlabel", v);
5280 xset (m_xlabel.handle_value (), "positionmode", "auto");
5281 xset (m_xlabel.handle_value (), "rotationmode", "auto");
5282 xset (m_xlabel.handle_value (), "horizontalalignmentmode", "auto");
5283 xset (m_xlabel.handle_value (), "verticalalignmentmode", "auto");
5284 xset (m_xlabel.handle_value (), "clipping", "off");
5285 xset (m_xlabel.handle_value (), "color", get_xcolor ());
5286 xset (m_xlabel.handle_value (), "__autopos_tag__", "xlabel");
5287 update_xlabel_position ();
5288}
5289
5290void
5291axes::properties::set_ylabel (const octave_value& v)
5292{
5293 set_text_child (m_ylabel, "ylabel", v);
5294 xset (m_ylabel.handle_value (), "positionmode", "auto");
5295 xset (m_ylabel.handle_value (), "rotationmode", "auto");
5296 xset (m_ylabel.handle_value (), "horizontalalignmentmode", "auto");
5297 xset (m_ylabel.handle_value (), "verticalalignmentmode", "auto");
5298 xset (m_ylabel.handle_value (), "clipping", "off");
5299 xset (m_ylabel.handle_value (), "color", get_ycolor ());
5300 xset (m_ylabel.handle_value (), "__autopos_tag__", "ylabel");
5301 update_ylabel_position ();
5302}
5303
5304void
5305axes::properties::set_zlabel (const octave_value& v)
5306{
5307 set_text_child (m_zlabel, "zlabel", v);
5308 xset (m_zlabel.handle_value (), "positionmode", "auto");
5309 xset (m_zlabel.handle_value (), "rotationmode", "auto");
5310 xset (m_zlabel.handle_value (), "horizontalalignmentmode", "auto");
5311 xset (m_zlabel.handle_value (), "verticalalignmentmode", "auto");
5312 xset (m_zlabel.handle_value (), "clipping", "off");
5313 xset (m_zlabel.handle_value (), "color", get_zcolor ());
5314 xset (m_zlabel.handle_value (), "__autopos_tag__", "zlabel");
5315 update_zlabel_position ();
5316}
5317
5318void
5319axes::properties::set_title (const octave_value& v)
5320{
5321 set_text_child (m_title, "title", v);
5322 xset (m_title.handle_value (), "positionmode", "auto");
5323 xset (m_title.handle_value (), "horizontalalignment", "center");
5324 xset (m_title.handle_value (), "horizontalalignmentmode", "auto");
5325 xset (m_title.handle_value (), "verticalalignment", "bottom");
5326 xset (m_title.handle_value (), "verticalalignmentmode", "auto");
5327 xset (m_title.handle_value (), "clipping", "off");
5328 xset (m_title.handle_value (), "__autopos_tag__", "title");
5329 update_title_position ();
5330}
5331
5332void
5333axes::properties::set_defaults (base_graphics_object& bgo,
5334 const std::string& mode)
5335{
5336 // FIXME: Should this have all properties in it?
5337 // Including ones we do don't implement?
5338
5339 // FIXME: This function is probably never called without mode == "reset".
5340 // Error if this is not true. If there are reports of problems
5341 // then figure out what code is calling it with the mode set to
5342 // something else. It's apparently been this way since
5343 // 1/6/2017 without any reports. Maybe we should eliminate the
5344 // mode argument?
5345
5346 if (mode != "reset")
5347 error (R"(axes::properties::set_defaults: expected mode = "reset", found "%s")", mode.c_str ());
5348
5349 Matrix tlim (1, 2, 0.0);
5350 tlim(1) = 1;
5351 m_alim = tlim;
5352 m_clim = tlim;
5353 m_xlim = tlim;
5354 m_ylim = tlim;
5355 m_zlim = tlim;
5356
5357 m_alimmode = "auto";
5358 m_climmode = "auto";
5359 m_xlimmode = "auto";
5360 m_ylimmode = "auto";
5361 m_zlimmode = "auto";
5362
5363 m_alphamap = Matrix ();
5364 m_alphascale = "linear";
5365
5366 m_ambientlightcolor = Matrix (1, 3, 1.0);
5367
5368 m_box = "off";
5369 m_boxstyle = "back";
5370
5371 // Note: camera properties (not mode) will be set in update_transform
5372 m_camerapositionmode = "auto";
5373 m_cameratargetmode = "auto";
5374 m_cameraupvectormode = "auto";
5375 m_cameraviewanglemode = "auto";
5376
5377 m_clippingstyle = "3dbox";
5378
5379 m_color = color_values ("white");
5380 m_colormap = Matrix ();
5381 m_colororder = default_colororder ();
5382 m_colororderindex = 1.0;
5383 m_colorscale = "linear";
5384
5385 // Note: dataspectratio (not mode) will be set through update_aspectratios
5386 m_dataaspectratiomode = "auto";
5387
5388 m_fontangle = "normal";
5389 m_fontname = OCTAVE_DEFAULT_FONTNAME;
5390 m_fontsize = 10;
5391 m_fontsizemode = "auto";
5392 m_fontsmoothing = "on";
5393 m_fontunits = "points";
5394 m_fontweight = "normal";
5395
5396 m_gridalpha = 0.15;
5397 m_gridalphamode = "auto";
5398 m_gridcolor = color_values (0.15, 0.15, 0.15);
5399 m_gridcolormode = "auto";
5400 m_gridlinestyle = "-";
5401
5402 m_labelfontsizemultiplier = 1.1;
5403
5404 m_layer = "bottom";
5405
5406 m_linestyleorder = "-";
5407 m_linestyleorderindex = 1.0;
5408
5409 m_linewidth = 0.5;
5410
5411 m_minorgridalpha = 0.25;
5412 m_minorgridalphamode = "auto";
5413 m_minorgridcolor = color_values (0.1, 0.1, 0.1);
5414 m_minorgridcolormode = "auto";
5415 m_minorgridlinestyle = ":";
5416
5417 m_nextplot = "replace";
5418
5419 // Note: plotboxaspectratio will be set through update_aspectratios
5420 m_plotboxaspectratiomode = "auto";
5421 m_projection = "orthographic";
5422
5423 m_sortmethod = "depth";
5424
5425 m_tickdir = "in";
5426 m_tickdirmode = "auto";
5427 m_ticklabelinterpreter = "tex";
5428 m_ticklength = default_axes_ticklength ();
5429
5430 m_tightinset = Matrix (1, 4, 0.0);
5431
5432 m_titlefontsizemultiplier = 1.1;
5433 m_titlefontweight = "bold";
5434
5435 Matrix tview (1, 2, 0.0);
5436 tview(1) = 90;
5437 m_view = tview;
5438
5439 m_xaxislocation = "bottom";
5440
5441 m_xcolor = color_values (0.15, 0.15, 0.15);
5442 m_xcolormode = "auto";
5443 m_xdir = "normal";
5444 m_xgrid = "off";
5445 m_xlimitmethod = "tickaligned";
5446 m_xminorgrid = "off";
5447 m_xminortick = "off";
5448 m_xminortickvalues = Matrix ();
5449 m_xminortickvaluesmode = "auto";
5450 m_xscale = "linear";
5451 m_xtick = Matrix ();
5452 m_xticklabel = "";
5453 m_xticklabelmode = "auto";
5454 m_xticklabelrotation = 0.0;
5455 m_xtickmode = "auto";
5456
5457 m_yaxislocation = "left";
5458
5459 m_ycolor = color_values (0.15, 0.15, 0.15);
5460 m_ycolormode = "auto";
5461 m_ydir = "normal";
5462 m_ygrid = "off";
5463 m_ylimitmethod = "tickaligned";
5464 m_yminorgrid = "off";
5465 m_yminortick = "off";
5466 m_yminortickvalues = Matrix ();
5467 m_yminortickvaluesmode = "auto";
5468 m_yscale = "linear";
5469 m_ytick = Matrix ();
5470 m_yticklabel = "";
5471 m_yticklabelmode = "auto";
5472 m_yticklabelrotation = 0.0;
5473 m_ytickmode = "auto";
5474
5475 m_zcolor = color_values (0.15, 0.15, 0.15);
5476 m_zcolormode = "auto";
5477 m_zdir = "normal";
5478 m_zgrid = "off";
5479 m_zlimitmethod = "tickaligned";
5480 m_zminorgrid = "off";
5481 m_zminortick = "off";
5482 m_zminortickvalues = Matrix ();
5483 m_zminortickvaluesmode = "auto";
5484 m_zscale = "linear";
5485 m_ztick = Matrix ();
5486 m_zticklabel = "";
5487 m_zticklabelmode = "auto";
5488 m_zticklabelrotation = 0.0;
5489 m_ztickmode = "auto";
5490
5491 m_sx = "linear";
5492 m_sy = "linear";
5493 m_sz = "linear";
5494
5495 m_visible = "on";
5496
5497 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
5498
5499 graphics_object go = gh_mgr.get_object (m_xlabel.handle_value ());
5500 go.reset_default_properties ();
5501 go = gh_mgr.get_object (m_ylabel.handle_value ());
5502 go.reset_default_properties ();
5503 go = gh_mgr.get_object (m_zlabel.handle_value ());
5504 go.reset_default_properties ();
5505 go = gh_mgr.get_object (m_title.handle_value ());
5506 go.reset_default_properties ();
5507
5508 xset (m_xlabel.handle_value (), "handlevisibility", "off");
5509 xset (m_ylabel.handle_value (), "handlevisibility", "off");
5510 xset (m_zlabel.handle_value (), "handlevisibility", "off");
5511 xset (m_title.handle_value (), "handlevisibility", "off");
5512
5513 xset (m_xlabel.handle_value (), "horizontalalignment", "center");
5514 xset (m_xlabel.handle_value (), "horizontalalignmentmode", "auto");
5515 xset (m_ylabel.handle_value (), "horizontalalignment", "center");
5516 xset (m_ylabel.handle_value (), "horizontalalignmentmode", "auto");
5517 xset (m_zlabel.handle_value (), "horizontalalignment", "right");
5518 xset (m_zlabel.handle_value (), "horizontalalignmentmode", "auto");
5519 xset (m_title.handle_value (), "horizontalalignment", "center");
5520 xset (m_title.handle_value (), "horizontalalignmentmode", "auto");
5521
5522 xset (m_xlabel.handle_value (), "verticalalignment", "top");
5523 xset (m_xlabel.handle_value (), "verticalalignmentmode", "auto");
5524 xset (m_ylabel.handle_value (), "verticalalignment", "bottom");
5525 xset (m_ylabel.handle_value (), "verticalalignmentmode", "auto");
5526 xset (m_title.handle_value (), "verticalalignment", "bottom");
5527 xset (m_title.handle_value (), "verticalalignmentmode", "auto");
5528
5529 xset (m_ylabel.handle_value (), "rotation", 90.0);
5530 xset (m_ylabel.handle_value (), "rotationmode", "auto");
5531
5532 xset (m_zlabel.handle_value (), "visible", "off");
5533
5534 xset (m_xlabel.handle_value (), "clipping", "off");
5535 xset (m_ylabel.handle_value (), "clipping", "off");
5536 xset (m_zlabel.handle_value (), "clipping", "off");
5537 xset (m_title.handle_value (), "clipping", "off");
5538
5539 xset (m_xlabel.handle_value (), "__autopos_tag__", "xlabel");
5540 xset (m_ylabel.handle_value (), "__autopos_tag__", "ylabel");
5541 xset (m_zlabel.handle_value (), "__autopos_tag__", "zlabel");
5542 xset (m_title.handle_value (), "__autopos_tag__", "title");
5543
5544 double fs;
5545 fs = m_labelfontsizemultiplier.double_value () * m_fontsize.double_value ();
5546 xset (m_xlabel.handle_value (), "fontsize", octave_value (fs));
5547 xset (m_ylabel.handle_value (), "fontsize", octave_value (fs));
5548 xset (m_zlabel.handle_value (), "fontsize", octave_value (fs));
5549 fs = m_titlefontsizemultiplier.double_value () * m_fontsize.double_value ();
5550 xset (m_title.handle_value (), "fontsize", octave_value (fs));
5551 xset (m_title.handle_value (), "fontweight", m_titlefontweight.get ());
5552
5553 update_transform ();
5554 sync_positions ();
5555 override_defaults (bgo);
5556}
5557
5559axes::properties::get_colormap () const
5560{
5561 if (m___colormap__.get ().isempty ())
5562 {
5563 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
5564
5565 graphics_object go (gh_mgr.get_object (get___myhandle__ ()));
5566 graphics_object go_f (go.get_ancestor ("figure"));
5567 figure::properties& figure_props
5568 = reinterpret_cast<figure::properties&> (go_f.get_properties ());
5569 return figure_props.get_colormap ();
5570 }
5571
5572 return get___colormap__ ();
5573}
5574
5575void
5576axes::properties::delete_text_child (handle_property& hp, bool from_root)
5577{
5578 graphics_handle h = hp.handle_value ();
5579
5580 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
5581
5582 if (h.ok ())
5583 {
5584 graphics_object go = gh_mgr.get_object (h);
5585
5586 if (go.valid_object ())
5587 gh_mgr.free (h, from_root);
5588 }
5589
5590 // FIXME: is it necessary to check whether the axes object is
5591 // being deleted now? I think this function is only called when an
5592 // individual child object is delete and not when the parent axes
5593 // object is deleted.
5594
5595 if (! is_beingdeleted ())
5596 {
5597 hp = gh_mgr.make_graphics_handle ("text", m___myhandle__, false, false);
5598
5599 xset (hp.handle_value (), "handlevisibility", "off");
5600
5601 adopt (hp.handle_value ());
5602 }
5603}
5604
5605void
5606axes::properties::remove_child (const graphics_handle& h, bool from_root)
5607{
5608 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
5609
5610 graphics_object go = gh_mgr.get_object (h);
5611
5612 if (m_xlabel.handle_value ().ok () && h == m_xlabel.handle_value ())
5613 {
5614 delete_text_child (m_xlabel, from_root);
5615 update_xlabel_position ();
5616 }
5617 else if (m_ylabel.handle_value ().ok () && h == m_ylabel.handle_value ())
5618 {
5619 delete_text_child (m_ylabel, from_root);
5620 update_ylabel_position ();
5621 }
5622 else if (m_zlabel.handle_value ().ok () && h == m_zlabel.handle_value ())
5623 {
5624 delete_text_child (m_zlabel, from_root);
5625 update_zlabel_position ();
5626 }
5627 else if (m_title.handle_value ().ok () && h == m_title.handle_value ())
5628 {
5629 delete_text_child (m_title, from_root);
5630 update_title_position ();
5631 }
5632 else if (get_num_lights () > 0 && go.isa ("light")
5633 && go.get_properties ().is_visible ())
5634 decrease_num_lights ();
5635
5636 if (go.valid_object ())
5637 base_properties::remove_child (h, from_root);
5638
5639}
5640
5641void
5642axes::properties::adopt (const graphics_handle& h)
5643{
5644 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
5645
5646 graphics_object go (gh_mgr.get_object (h));
5647
5648 if (go.isa ("light") && go.get_properties ().is_visible ())
5649 increase_num_lights ();
5650
5651 base_properties::adopt (h);
5652
5653 // FIXME: For performance reasons, we would like to call
5654 // update_axis_limits ("xlim", h);
5655 // which updates the limits based ONLY on the new data from h.
5656 // But this isn't working properly at the moment, so we
5657 // call the other form which invokes a full tree traversal of all
5658 // of the axes children.
5659 if (xlimmode_is ("auto"))
5660 update_axis_limits ("xlim");
5661
5662 if (ylimmode_is ("auto"))
5663 update_axis_limits ("ylim");
5664
5665 if (zlimmode_is ("auto"))
5666 update_axis_limits ("zlim");
5667
5668 if (climmode_is ("auto"))
5669 update_axis_limits ("clim");
5670
5671 if (climmode_is ("auto"))
5672 update_axis_limits ("alim");
5673}
5674
5675inline Matrix
5677{
5678 Matrix m (4, 4, 0.0);
5679
5680 for (int i = 0; i < 4; i++)
5681 m(i, i) = 1;
5682
5683 return m;
5684}
5685
5686inline ColumnVector
5688{
5689 ColumnVector v (4, 0.0);
5690
5691 v(3) = 1;
5692
5693 return v;
5694}
5695
5696inline ColumnVector
5697xform_vector (double x, double y, double z)
5698{
5699 ColumnVector v (4, 1.0);
5700
5701 v(0) = x;
5702 v(1) = y;
5703 v(2) = z;
5704
5705 return v;
5706}
5707
5708inline ColumnVector
5709transform (const Matrix& m, double x, double y, double z)
5710{
5711 return (m * xform_vector (x, y, z));
5712}
5713
5714inline Matrix
5715xform_scale (double x, double y, double z)
5716{
5717 Matrix m (4, 4, 0.0);
5718
5719 m(0, 0) = x;
5720 m(1, 1) = y;
5721 m(2, 2) = z;
5722 m(3, 3) = 1;
5723
5724 return m;
5725}
5726
5727inline Matrix
5728xform_translate (double x, double y, double z)
5729{
5730 Matrix m = xform_matrix ();
5731
5732 m(0, 3) = x;
5733 m(1, 3) = y;
5734 m(2, 3) = z;
5735 m(3, 3) = 1;
5736
5737 return m;
5738}
5739
5740inline void
5741scale (Matrix& m, double x, double y, double z)
5742{
5743 m = m * xform_scale (x, y, z);
5744}
5745
5746inline void
5747translate (Matrix& m, double x, double y, double z)
5748{
5749 m = m * xform_translate (x, y, z);
5750}
5751
5752inline void
5754{
5755 v = m * v;
5756}
5757
5758inline void
5759scale (ColumnVector& v, double x, double y, double z)
5760{
5761 v(0) *= x;
5762 v(1) *= y;
5763 v(2) *= z;
5764}
5765
5766inline void
5767translate (ColumnVector& v, double x, double y, double z)
5768{
5769 v(0) += x;
5770 v(1) += y;
5771 v(2) += z;
5772}
5773
5774inline void
5776{
5777 double fact = 1.0 / sqrt (v(0)*v(0)+v(1)*v(1)+v(2)*v(2));
5778 scale (v, fact, fact, fact);
5779}
5780
5781inline double
5782dot (const ColumnVector& v1, const ColumnVector& v2)
5783{
5784 return (v1(0)*v2(0)+v1(1)*v2(1)+v1(2)*v2(2));
5785}
5786
5787inline double
5789{
5790 return sqrt (dot (v, v));
5791}
5792
5793inline ColumnVector
5795{
5797
5798 r(0) = v1(1)*v2(2) - v1(2)*v2(1);
5799 r(1) = v1(2)*v2(0) - v1(0)*v2(2);
5800 r(2) = v1(0)*v2(1) - v1(1)*v2(0);
5801
5802 return r;
5803}
5804
5805inline Matrix
5807{
5808 static double data[32] =
5809 {
5810 0, 0, 0, 1,
5811 1, 0, 0, 1,
5812 0, 1, 0, 1,
5813 0, 0, 1, 1,
5814 1, 1, 0, 1,
5815 1, 0, 1, 1,
5816 0, 1, 1, 1,
5817 1, 1, 1, 1
5818 };
5819 Matrix m (4, 8);
5820
5821 memcpy (m.rwdata (), data, sizeof (double)*32);
5822
5823 return m;
5824}
5825
5826inline ColumnVector
5828{
5829 ColumnVector retval (4, 1.0);
5830
5831 memcpy (retval.rwdata (), m.data (), sizeof (double)*3);
5832
5833 return retval;
5834}
5835
5836inline RowVector
5838{
5839 return v.extract_n (0, 3).transpose ();
5840}
5841
5842void
5843axes::properties::update_camera ()
5844{
5845 double xd = (xdir_is ("normal") ? 1 : -1);
5846 double yd = (ydir_is ("normal") ? 1 : -1);
5847 double zd = (zdir_is ("normal") ? 1 : -1);
5848
5849 Matrix xlimits = m_sx.scale (get_xlim ().matrix_value ());
5850 Matrix ylimits = m_sy.scale (get_ylim ().matrix_value ());
5851 Matrix zlimits = m_sz.scale (get_zlim ().matrix_value ());
5852
5853 double xo = xlimits(xd > 0 ? 0 : 1);
5854 double yo = ylimits(yd > 0 ? 0 : 1);
5855 double zo = zlimits(zd > 0 ? 0 : 1);
5856
5857 Matrix pb = get_plotboxaspectratio ().matrix_value ();
5858
5859 bool autocam = (camerapositionmode_is ("auto")
5860 && cameratargetmode_is ("auto")
5861 && cameraupvectormode_is ("auto")
5862 && cameraviewanglemode_is ("auto"));
5863 bool dowarp = (autocam && dataaspectratiomode_is ("auto")
5864 && plotboxaspectratiomode_is ("auto"));
5865
5866 ColumnVector c_eye (xform_vector ());
5867 ColumnVector c_center (xform_vector ());
5868 ColumnVector c_upv (xform_vector ());
5869
5870 if (cameratargetmode_is ("auto"))
5871 {
5872 c_center(0) = (xlimits(0) + xlimits(1)) / 2;
5873 c_center(1) = (ylimits(0) + ylimits(1)) / 2;
5874 c_center(2) = (zlimits(0) + zlimits(1)) / 2;
5875
5876 m_cameratarget = xform2cam (c_center);
5877 }
5878 else
5879 c_center = cam2xform (get_cameratarget ().matrix_value ());
5880
5881 if (camerapositionmode_is ("auto"))
5882 {
5883 Matrix tview = get_view ().matrix_value ();
5884 double az = tview(0);
5885 double el = tview(1);
5886 double d = 5 * sqrt (pb(0)*pb(0) + pb(1)*pb(1) + pb(2)*pb(2));
5887
5888 if (el == 90 || el == -90)
5889 c_eye(2) = d*octave::math::signum (el);
5890 else
5891 {
5892 az *= M_PI/180.0;
5893 el *= M_PI/180.0;
5894 c_eye(0) = d * cos (el) * sin (az);
5895 c_eye(1) = -d* cos (el) * cos (az);
5896 c_eye(2) = d * sin (el);
5897 }
5898 c_eye(0) = c_eye(0)*(xlimits(1)-xlimits(0))/(xd*pb(0))+c_center(0);
5899 c_eye(1) = c_eye(1)*(ylimits(1)-ylimits(0))/(yd*pb(1))+c_center(1);
5900 c_eye(2) = c_eye(2)*(zlimits(1)-zlimits(0))/(zd*pb(2))+c_center(2);
5901
5902 m_cameraposition = xform2cam (c_eye);
5903 }
5904 else
5905 c_eye = cam2xform (get_cameraposition ().matrix_value ());
5906
5907 if (cameraupvectormode_is ("auto"))
5908 {
5909 Matrix tview = get_view ().matrix_value ();
5910 double az = tview(0);
5911 double el = tview(1);
5912
5913 if (el == 90 || el == -90)
5914 {
5915 c_upv(0) = -octave::math::signum (el)
5916 * sin (az*M_PI/180.0)*(xlimits(1)-xlimits(0))/pb(0);
5917 c_upv(1) = octave::math::signum (el)
5918 * cos (az*M_PI/180.0)*(ylimits(1)-ylimits(0))/pb(1);
5919 }
5920 else
5921 c_upv(2) = 1;
5922
5923 m_cameraupvector = xform2cam (c_upv);
5924 }
5925 else
5926 c_upv = cam2xform (get_cameraupvector ().matrix_value ());
5927
5928 Matrix x_view = xform_matrix ();
5929 Matrix x_projection = xform_matrix ();
5930 Matrix x_viewport = xform_matrix ();
5931 Matrix x_normrender;
5932 Matrix x_pre = xform_matrix ();
5933
5934 m_x_render = xform_matrix ();
5935 m_x_render_inv = xform_matrix ();
5936
5937 scale (x_pre, pb(0), pb(1), pb(2));
5938 translate (x_pre, -0.5, -0.5, -0.5);
5939 scale (x_pre, xd/(xlimits(1)-xlimits(0)), yd/(ylimits(1)-ylimits(0)),
5940 zd/(zlimits(1)-zlimits(0)));
5941 translate (x_pre, -xo, -yo, -zo);
5942
5943 xform (c_eye, x_pre);
5944 xform (c_center, x_pre);
5945 scale (c_upv, pb(0)/(xlimits(1)-xlimits(0)), pb(1)/(ylimits(1)-ylimits(0)),
5946 pb(2)/(zlimits(1)-zlimits(0)));
5947 translate (c_center, -c_eye(0), -c_eye(1), -c_eye(2));
5948
5949 ColumnVector F (c_center), f (F), UP (c_upv);
5950 normalize (f);
5951 normalize (UP);
5952
5953 if (std::abs (dot (f, UP)) > 1e-15)
5954 {
5955 double fa = 1 / sqrt (1 - f(2)*f(2));
5956 scale (UP, fa, fa, fa);
5957 }
5958
5959 ColumnVector s = cross (f, UP);
5960 ColumnVector u = cross (s, f);
5961
5962 scale (x_view, 1, 1, -1);
5963 Matrix l = xform_matrix ();
5964 l(0, 0) = s(0); l(0, 1) = s(1); l(0, 2) = s(2);
5965 l(1, 0) = u(0); l(1, 1) = u(1); l(1, 2) = u(2);
5966 l(2, 0) = -f(0); l(2, 1) = -f(1); l(2, 2) = -f(2);
5967 x_view = x_view * l;
5968 translate (x_view, -c_eye(0), -c_eye(1), -c_eye(2));
5969 scale (x_view, pb(0), pb(1), pb(2));
5970 translate (x_view, -0.5, -0.5, -0.5);
5971
5972 Matrix x_cube = x_view * unit_cube ();
5973 ColumnVector cmin = x_cube.row_min ();
5974 ColumnVector cmax = x_cube.row_max ();
5975 double xM = cmax(0) - cmin(0);
5976 double yM = cmax(1) - cmin(1);
5977
5978 Matrix bb = get_boundingbox (true);
5979
5980 double v_angle;
5981
5982 if (cameraviewanglemode_is ("auto"))
5983 {
5984 double af;
5985
5986 // FIXME: was this really needed? When compared to Matlab, it
5987 // does not seem to be required. Need investigation with concrete
5988 // graphics toolkit to see results visually.
5989 if (false && dowarp)
5990 af = (1.0 / (xM > yM ? xM : yM));
5991 else
5992 {
5993 if ((bb(2)/bb(3)) > (xM/yM))
5994 af = 1.0 / yM;
5995 else
5996 af = 1.0 / xM;
5997 }
5998 v_angle = 2 * (180.0 / M_PI) * atan (1 / (2 * af * norm (F)));
5999
6000 m_cameraviewangle = v_angle;
6001 }
6002 else
6003 v_angle = get_cameraviewangle ();
6004
6005 double pf = 1 / (2 * tan ((v_angle / 2) * M_PI / 180.0) * norm (F));
6006 scale (x_projection, pf, pf, 1);
6007
6008 if (dowarp)
6009 {
6010 xM *= pf;
6011 yM *= pf;
6012 translate (x_viewport, bb(0)+bb(2)/2, bb(1)+bb(3)/2, 0);
6013 scale (x_viewport, bb(2)/xM, -bb(3)/yM, 1);
6014 }
6015 else
6016 {
6017 double pix = 1;
6018 if (autocam)
6019 {
6020 if ((bb(2)/bb(3)) > (xM/yM))
6021 pix = bb(3);
6022 else
6023 pix = bb(2);
6024 }
6025 else
6026 pix = (bb(2) < bb(3) ? bb(2) : bb(3));
6027 translate (x_viewport, bb(0)+bb(2)/2, bb(1)+bb(3)/2, 0);
6028 scale (x_viewport, pix, -pix, 1);
6029 }
6030
6031 x_normrender = x_viewport * x_projection * x_view;
6032
6033 x_cube = x_normrender * unit_cube ();
6034 cmin = x_cube.row_min ();
6035 cmax = x_cube.row_max ();
6036 m_x_zlim.resize (1, 2);
6037 m_x_zlim(0) = cmin(2);
6038 m_x_zlim(1) = cmax(2);
6039
6040 m_x_render = x_normrender;
6041 scale (m_x_render, xd/(xlimits(1)-xlimits(0)), yd/(ylimits(1)-ylimits(0)),
6042 zd/(zlimits(1)-zlimits(0)));
6043 translate (m_x_render, -xo, -yo, -zo);
6044
6045 m_x_render_inv = m_x_render.inverse ();
6046
6047 // Note: these matrices are a slight modified version of the regular matrices,
6048 // more suited for OpenGL rendering (m_x_gl_mat1 => light => m_x_gl_mat2)
6049 m_x_gl_mat1 = x_view;
6050 scale (m_x_gl_mat1, xd/(xlimits(1)-xlimits(0)), yd/(ylimits(1)-ylimits(0)),
6051 zd/(zlimits(1)-zlimits(0)));
6052 translate (m_x_gl_mat1, -xo, -yo, -zo);
6053 m_x_gl_mat2 = x_viewport * x_projection;
6054}
6055
6056static bool updating_axes_layout = false;
6057
6058void
6059axes::properties::update_axes_layout ()
6060{
6061 if (updating_axes_layout)
6062 return;
6063
6064 graphics_xform xform = get_transform ();
6065
6066 double xd = (xdir_is ("normal") ? 1 : -1);
6067 double yd = (ydir_is ("normal") ? 1 : -1);
6068 double zd = (zdir_is ("normal") ? 1 : -1);
6069
6070 const Matrix xlims = xform.xscale (get_xlim ().matrix_value ());
6071 const Matrix ylims = xform.yscale (get_ylim ().matrix_value ());
6072 const Matrix zlims = xform.zscale (get_zlim ().matrix_value ());
6073
6074 double x_min, x_max, y_min, y_max, z_min, z_max;
6075 x_min = xlims(0), x_max = xlims(1);
6076 y_min = ylims(0), y_max = ylims(1);
6077 z_min = zlims(0), z_max = zlims(1);
6078
6079 ColumnVector p1, p2, dir (3);
6080
6081 m_xstate = m_ystate = m_zstate = AXE_ANY_DIR;
6082
6083 p1 = xform.transform (x_min, (y_min+y_max)/2, (z_min+z_max)/2, false);
6084 p2 = xform.transform (x_max, (y_min+y_max)/2, (z_min+z_max)/2, false);
6085 dir(0) = octave::math::round (p2(0) - p1(0));
6086 dir(1) = octave::math::round (p2(1) - p1(1));
6087 dir(2) = (p2(2) - p1(2));
6088 if (dir(0) == 0 && dir(1) == 0)
6089 m_xstate = AXE_DEPTH_DIR;
6090 else if (dir(2) == 0)
6091 {
6092 if (dir(0) == 0)
6093 m_xstate = AXE_VERT_DIR;
6094 else if (dir(1) == 0)
6095 m_xstate = AXE_HORZ_DIR;
6096 }
6097
6098 if (dir(2) == 0)
6099 {
6100 if (dir(1) == 0)
6101 m_xPlane = (dir(0) > 0 ? x_max : x_min);
6102 else
6103 m_xPlane = (dir(1) < 0 ? x_max : x_min);
6104 }
6105 else
6106 m_xPlane = (dir(2) < 0 ? x_min : x_max);
6107
6108 m_xPlaneN = (m_xPlane == x_min ? x_max : x_min);
6109 m_fx = (x_max - x_min) / sqrt (dir(0)*dir(0) + dir(1)*dir(1));
6110
6111 p1 = xform.transform ((x_min + x_max)/2, y_min, (z_min + z_max)/2, false);
6112 p2 = xform.transform ((x_min + x_max)/2, y_max, (z_min + z_max)/2, false);
6113 dir(0) = octave::math::round (p2(0) - p1(0));
6114 dir(1) = octave::math::round (p2(1) - p1(1));
6115 dir(2) = (p2(2) - p1(2));
6116 if (dir(0) == 0 && dir(1) == 0)
6117 m_ystate = AXE_DEPTH_DIR;
6118 else if (dir(2) == 0)
6119 {
6120 if (dir(0) == 0)
6121 m_ystate = AXE_VERT_DIR;
6122 else if (dir(1) == 0)
6123 m_ystate = AXE_HORZ_DIR;
6124 }
6125
6126 if (dir(2) == 0)
6127 {
6128 if (dir(1) == 0)
6129 m_yPlane = (dir(0) > 0 ? y_max : y_min);
6130 else
6131 m_yPlane = (dir(1) < 0 ? y_max : y_min);
6132 }
6133 else
6134 m_yPlane = (dir(2) < 0 ? y_min : y_max);
6135
6136 m_yPlaneN = (m_yPlane == y_min ? y_max : y_min);
6137 m_fy = (y_max - y_min) / sqrt (dir(0)*dir(0) + dir(1)*dir(1));
6138
6139 p1 = xform.transform ((x_min + x_max)/2, (y_min + y_max)/2, z_min, false);
6140 p2 = xform.transform ((x_min + x_max)/2, (y_min + y_max)/2, z_max, false);
6141 dir(0) = octave::math::round (p2(0) - p1(0));
6142 dir(1) = octave::math::round (p2(1) - p1(1));
6143 dir(2) = (p2(2) - p1(2));
6144 if (dir(0) == 0 && dir(1) == 0)
6145 m_zstate = AXE_DEPTH_DIR;
6146 else if (dir(2) == 0)
6147 {
6148 if (dir(0) == 0)
6149 m_zstate = AXE_VERT_DIR;
6150 else if (dir(1) == 0)
6151 m_zstate = AXE_HORZ_DIR;
6152 }
6153
6154 if (dir(2) == 0)
6155 {
6156 if (dir(1) == 0)
6157 m_zPlane = (dir(0) > 0 ? z_min : z_max);
6158 else
6159 m_zPlane = (dir(1) < 0 ? z_min : z_max);
6160 }
6161 else
6162 m_zPlane = (dir(2) < 0 ? z_min : z_max);
6163
6164 m_zPlaneN = (m_zPlane == z_min ? z_max : z_min);
6165 m_fz = (z_max - z_min) / sqrt (dir(0)*dir(0) + dir(1)*dir(1));
6166
6167 octave::unwind_protect_var<bool> restore_var (updating_axes_layout, true);
6168
6169 m_xySym = (xd*yd*(m_xPlane-m_xPlaneN)*(m_yPlane-m_yPlaneN) > 0);
6170 m_zSign = (zd*(m_zPlane-m_zPlaneN) <= 0);
6171 m_xyzSym = (m_zSign ? m_xySym : ! m_xySym);
6172 m_xpTick = (m_zSign ? m_xPlaneN : m_xPlane);
6173 m_ypTick = (m_zSign ? m_yPlaneN : m_yPlane);
6174 m_zpTick = (m_zSign ? m_zPlane : m_zPlaneN);
6175 m_xpTickN = (m_zSign ? m_xPlane : m_xPlaneN);
6176 m_ypTickN = (m_zSign ? m_yPlane : m_yPlaneN);
6177 m_zpTickN = (m_zSign ? m_zPlaneN : m_zPlane);
6178
6179 // 2-D mode
6180 m_x2Dtop = false;
6181 m_y2Dright = false;
6182 m_layer2Dtop = false;
6183 if (m_xstate == AXE_HORZ_DIR && m_ystate == AXE_VERT_DIR)
6184 {
6185 Matrix ylimits = get_ylim ().matrix_value ();
6186 if (xaxislocation_is ("top")
6187 || (yscale_is ("log") && xaxislocation_is ("origin")
6188 && (ylimits(1) < 0.)))
6189 {
6190 std::swap (m_yPlane, m_yPlaneN);
6191 m_x2Dtop = true;
6192 }
6193 m_ypTick = m_yPlaneN;
6194 m_ypTickN = m_yPlane;
6195 Matrix xlimits = get_xlim ().matrix_value ();
6196 if (yaxislocation_is ("right")
6197 || (xscale_is ("log") && yaxislocation_is ("origin")
6198 && (xlimits(1) < 0.)))
6199 {
6200 std::swap (m_xPlane, m_xPlaneN);
6201 m_y2Dright = true;
6202 }
6203 m_xpTick = m_xPlaneN;
6204 m_xpTickN = m_xPlane;
6205 if (layer_is ("top"))
6206 {
6207 m_zpTick = m_zPlaneN;
6208 m_layer2Dtop = true;
6209 }
6210 else
6211 m_zpTick = m_zPlane;
6212 }
6213
6214 Matrix viewmat = get_view ().matrix_value ();
6215 m_nearhoriz = std::abs (viewmat(1)) <= 5;
6216 m_is2D = viewmat(1) == 90;
6217
6218 update_ticklength ();
6219}
6220
6221void
6222axes::properties::update_ticklength ()
6223{
6224 bool mode2D = (((m_xstate > AXE_DEPTH_DIR ? 1 : 0) +
6225 (m_ystate > AXE_DEPTH_DIR ? 1 : 0) +
6226 (m_zstate > AXE_DEPTH_DIR ? 1 : 0)) == 2);
6227
6228 if (tickdirmode_is ("auto"))
6229 m_tickdir.set (mode2D ? "in" : "out", true);
6230
6231 double ticksign;
6232 std::string tickdir = get_tickdir ();
6233 if (tickdir == "in")
6234 ticksign = -1;
6235 else if (tickdir == "out")
6236 ticksign = 1;
6237 else if (tickdir == "both")
6238 ticksign = 1;
6239 else // tickdir == "none"
6240 ticksign = 0;
6241
6242 Matrix bbox = get_boundingbox (true);
6243 Matrix ticklen = get_ticklength ().matrix_value ();
6244 ticklen(0) *= std::max (bbox(2), bbox(3));
6245 // FIXME: This algorithm is not Matlab-compatible. See bug #55483.
6246 // Scale the results of Octave's algorithm for better visuals.
6247 ticklen(1) *= (0.76 * std::max (bbox(2), bbox(3)));
6248
6249 m_xticklen = ticksign * (mode2D ? ticklen(0) : ticklen(1));
6250 m_yticklen = ticksign * (mode2D ? ticklen(0) : ticklen(1));
6251 m_zticklen = ticksign * (mode2D ? ticklen(0) : ticklen(1));
6252
6253 double offset = get___fontsize_points__ () / 2;
6254
6255 m_xtickoffset = (mode2D ? std::max (0., m_xticklen) : std::abs (m_xticklen)) +
6256 (m_xstate == AXE_HORZ_DIR ? offset*1.5 : offset);
6257 m_ytickoffset = (mode2D ? std::max (0., m_yticklen) : std::abs (m_yticklen)) +
6258 (m_ystate == AXE_HORZ_DIR ? offset*1.5 : offset);
6259 m_ztickoffset = (mode2D ? std::max (0., m_zticklen) : std::abs (m_zticklen)) +
6260 (m_zstate == AXE_HORZ_DIR ? offset*1.5 : offset);
6261
6262 update_xlabel_position ();
6263 update_ylabel_position ();
6264 update_zlabel_position ();
6265 update_title_position ();
6266}
6267
6268/*
6269## FIXME: A demo can't be called in a C++ file. This should be made a test
6270## or moved to a .m file where it can be called.
6271%!demo
6272%! clf;
6273%! subplot (2,1,1);
6274%! plot (rand (3));
6275%! xlabel xlabel;
6276%! ylabel ylabel;
6277%! title title;
6278%! subplot (2,1,2);
6279%! plot (rand (3));
6280%! set (gca, "ticklength", get (gca, "ticklength") * 2, "tickdir", "out");
6281%! xlabel xlabel;
6282%! ylabel ylabel;
6283%! title title;
6284*/
6285
6286static ColumnVector
6287convert_label_position (const ColumnVector& p,
6288 const text::properties& props,
6289 const graphics_xform& xform,
6290 const Matrix& bbox)
6291{
6292 ColumnVector retval;
6293
6294 std::string to_units = props.get_units ();
6295
6296 if (to_units != "data")
6297 {
6298 ColumnVector v = xform.transform (p(0), p(1), p(2));
6299
6300 retval.resize (3);
6301
6302 retval(0) = v(0) - bbox(0) + 1;
6303 retval(1) = bbox(1) + bbox(3) - v(1) + 1;
6304 retval(2) = 0;
6305
6306 retval = convert_position (retval, "pixels", to_units,
6307 bbox.extract_n (0, 2, 1, 2));
6308 }
6309 else
6310 retval = p;
6311
6312 return retval;
6313}
6314
6315static bool updating_xlabel_position = false;
6316
6317void
6318axes::properties::update_xlabel_position ()
6319{
6320 if (updating_xlabel_position)
6321 return;
6322
6323 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
6324
6325 graphics_object go = gh_mgr.get_object (get_xlabel ());
6326
6327 if (! go.valid_object ())
6328 return;
6329
6330 text::properties& xlabel_props
6331 = reinterpret_cast<text::properties&> (go.get_properties ());
6332
6333 bool isempty = xlabel_props.get_string ().isempty ();
6334
6335 octave::unwind_protect_var<bool>
6336 restore_var (updating_xlabel_position, true);
6337
6338 if (! isempty)
6339 {
6340 if (xlabel_props.horizontalalignmentmode_is ("auto"))
6341 {
6342 xlabel_props.set_horizontalalignment
6343 (m_xstate > AXE_DEPTH_DIR ? "center"
6344 : (m_xyzSym ? "left" : "right"));
6345
6346 xlabel_props.set_horizontalalignmentmode ("auto");
6347 }
6348
6349 if (xlabel_props.verticalalignmentmode_is ("auto"))
6350 {
6351 xlabel_props.set_verticalalignment
6352 (m_xstate == AXE_VERT_DIR || m_x2Dtop ? "bottom" : "top");
6353
6354 xlabel_props.set_verticalalignmentmode ("auto");
6355 }
6356 }
6357
6358 if (xlabel_props.positionmode_is ("auto")
6359 || xlabel_props.rotationmode_is ("auto"))
6360 {
6361 graphics_xform xform = get_transform ();
6362
6363 Matrix ext (1, 2, 0.0);
6364 ext = get_ticklabel_extents (get_xtick ().matrix_value (),
6365 get_xticklabel ().string_vector_value (),
6366 get_xlim ().matrix_value ());
6367
6368 double margin = 5;
6369 double wmax = ext(0) + margin;
6370 double hmax = ext(1) + margin;
6371 double angle = 0.0;
6372 ColumnVector p
6373 = graphics_xform::xform_vector ((m_xpTickN + m_xpTick)/2, m_ypTick, m_zpTick);
6374
6375 bool tick_along_z = m_nearhoriz || octave::math::isinf (m_fy);
6376 if (tick_along_z)
6377 p(2) += (octave::math::signum (m_zpTick - m_zpTickN) * m_fz * m_xtickoffset);
6378 else
6379 p(1) += (octave::math::signum (m_ypTick - m_ypTickN) * m_fy * m_xtickoffset);
6380
6381 p = xform.transform (p(0), p(1), p(2), false);
6382
6383 switch (m_xstate)
6384 {
6385 case AXE_ANY_DIR:
6386 p(0) += (m_xyzSym ? wmax : -wmax);
6387 p(1) += hmax;
6388 break;
6389
6390 case AXE_VERT_DIR:
6391 p(0) -= wmax;
6392 angle = 90;
6393 break;
6394
6395 case AXE_HORZ_DIR:
6396 p(1) += (m_x2Dtop ? -hmax : hmax);
6397 break;
6398 }
6399
6400 if (xlabel_props.positionmode_is ("auto"))
6401 {
6402 p = xform.untransform (p(0), p(1), p(2), true);
6403
6404 p = convert_label_position (p, xlabel_props, xform,
6405 get_extent (false));
6406
6407 xlabel_props.set_position (p.extract_n (0, 3).transpose ());
6408 xlabel_props.set_positionmode ("auto");
6409 }
6410
6411 if (! isempty && xlabel_props.rotationmode_is ("auto"))
6412 {
6413 xlabel_props.set_rotation (angle);
6414 xlabel_props.set_rotationmode ("auto");
6415 }
6416 }
6417}
6418
6419static bool updating_ylabel_position = false;
6420
6421void
6422axes::properties::update_ylabel_position ()
6423{
6424 if (updating_ylabel_position)
6425 return;
6426
6427 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
6428
6429 graphics_object go = gh_mgr.get_object (get_ylabel ());
6430
6431 if (! go.valid_object ())
6432 return;
6433
6434 text::properties& ylabel_props
6435 = reinterpret_cast<text::properties&> (go.get_properties ());
6436
6437 bool isempty = ylabel_props.get_string ().isempty ();
6438
6439 octave::unwind_protect_var<bool>
6440 restore_var (updating_ylabel_position, true);
6441
6442 if (! isempty)
6443 {
6444 if (ylabel_props.horizontalalignmentmode_is ("auto"))
6445 {
6446 ylabel_props.set_horizontalalignment
6447 (m_ystate > AXE_DEPTH_DIR ? "center"
6448 : (! m_xyzSym ? "left" : "right"));
6449
6450 ylabel_props.set_horizontalalignmentmode ("auto");
6451 }
6452
6453 if (ylabel_props.verticalalignmentmode_is ("auto"))
6454 {
6455 ylabel_props.set_verticalalignment
6456 (m_ystate == AXE_VERT_DIR && ! m_y2Dright ? "bottom" : "top");
6457
6458 ylabel_props.set_verticalalignmentmode ("auto");
6459 }
6460 }
6461
6462 if (ylabel_props.positionmode_is ("auto")
6463 || ylabel_props.rotationmode_is ("auto"))
6464 {
6465 graphics_xform xform = get_transform ();
6466
6467 Matrix ext (1, 2, 0.0);
6468
6469 ext = get_ticklabel_extents (get_ytick ().matrix_value (),
6470 get_yticklabel ().string_vector_value (),
6471 get_ylim ().matrix_value ());
6472 double margin = 5;
6473 double wmax = ext(0) + margin;
6474 double hmax = ext(1) + margin;
6475 double angle = 0.0;
6476 ColumnVector p
6477 = graphics_xform::xform_vector (m_xpTick, (m_ypTickN + m_ypTick)/2, m_zpTick);
6478
6479 bool tick_along_z = m_nearhoriz || octave::math::isinf (m_fx);
6480 if (tick_along_z)
6481 p(2) += (octave::math::signum (m_zpTick - m_zpTickN) * m_fz * m_ytickoffset);
6482 else
6483 p(0) += (octave::math::signum (m_xpTick - m_xpTickN) * m_fx * m_ytickoffset);
6484
6485 p = xform.transform (p(0), p(1), p(2), false);
6486
6487 switch (m_ystate)
6488 {
6489 case AXE_ANY_DIR:
6490 p(0) += (! m_xyzSym ? wmax : -wmax);
6491 p(1) += hmax;
6492 break;
6493
6494 case AXE_VERT_DIR:
6495 p(0) += (m_y2Dright ? wmax : -wmax);
6496 angle = 90;
6497 break;
6498
6499 case AXE_HORZ_DIR:
6500 p(1) += hmax;
6501 break;
6502 }
6503
6504 if (ylabel_props.positionmode_is ("auto"))
6505 {
6506 p = xform.untransform (p(0), p(1), p(2), true);
6507
6508 p = convert_label_position (p, ylabel_props, xform,
6509 get_extent (false));
6510
6511 ylabel_props.set_position (p.extract_n (0, 3).transpose ());
6512 ylabel_props.set_positionmode ("auto");
6513 }
6514
6515 if (! isempty && ylabel_props.rotationmode_is ("auto"))
6516 {
6517 ylabel_props.set_rotation (angle);
6518 ylabel_props.set_rotationmode ("auto");
6519 }
6520 }
6521}
6522
6523static bool updating_zlabel_position = false;
6524
6525void
6526axes::properties::update_zlabel_position ()
6527{
6528 if (updating_zlabel_position)
6529 return;
6530
6531 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
6532
6533 graphics_object go = gh_mgr.get_object (get_zlabel ());
6534
6535 if (! go.valid_object ())
6536 return;
6537
6538 text::properties& zlabel_props
6539 = reinterpret_cast<text::properties&> (go.get_properties ());
6540
6541 bool camAuto = cameraupvectormode_is ("auto");
6542 bool isempty = zlabel_props.get_string ().isempty ();
6543
6544 octave::unwind_protect_var<bool>
6545 restore_updating_zlabel_position (updating_zlabel_position, true);
6546
6547 if (! isempty)
6548 {
6549 if (zlabel_props.horizontalalignmentmode_is ("auto"))
6550 {
6551 zlabel_props.set_horizontalalignment
6552 ((m_zstate > AXE_DEPTH_DIR || camAuto) ? "center" : "right");
6553
6554 zlabel_props.set_horizontalalignmentmode ("auto");
6555 }
6556
6557 if (zlabel_props.verticalalignmentmode_is ("auto"))
6558 {
6559 zlabel_props.set_verticalalignment
6560 (m_zstate == AXE_VERT_DIR
6561 ? "bottom" : ((m_zSign || camAuto) ? "bottom" : "top"));
6562
6563 zlabel_props.set_verticalalignmentmode ("auto");
6564 }
6565 }
6566
6567 if (zlabel_props.positionmode_is ("auto")
6568 || zlabel_props.rotationmode_is ("auto"))
6569 {
6570 graphics_xform xform = get_transform ();
6571
6572 Matrix ext (1, 2, 0.0);
6573 ext = get_ticklabel_extents (get_ztick ().matrix_value (),
6574 get_zticklabel ().string_vector_value (),
6575 get_zlim ().matrix_value ());
6576
6577 double margin = 5;
6578 double wmax = ext(0) + margin;
6579 double hmax = ext(1) + margin;
6580 double angle = 0.0;
6581 ColumnVector p;
6582
6583 if (m_xySym)
6584 {
6585 p = graphics_xform::xform_vector (m_xPlaneN, m_yPlane,
6586 (m_zpTickN + m_zpTick)/2);
6587 if (octave::math::isinf (m_fy))
6588 p(0) += octave::math::signum (m_xPlaneN - m_xPlane) * m_fx * m_ztickoffset;
6589 else
6590 p(1) += octave::math::signum (m_yPlane - m_yPlaneN) * m_fy * m_ztickoffset;
6591 }
6592 else
6593 {
6594 p = graphics_xform::xform_vector (m_xPlane, m_yPlaneN,
6595 (m_zpTickN + m_zpTick)/2);
6596 if (octave::math::isinf (m_fx))
6597 p(1) += octave::math::signum (m_yPlaneN - m_yPlane) * m_fy * m_ztickoffset;
6598 else
6599 p(0) += octave::math::signum (m_xPlane - m_xPlaneN) * m_fx * m_ztickoffset;
6600 }
6601
6602 p = xform.transform (p(0), p(1), p(2), false);
6603
6604 switch (m_zstate)
6605 {
6606 case AXE_ANY_DIR:
6607 if (camAuto)
6608 {
6609 p(0) -= wmax;
6610 angle = 90;
6611 }
6612
6613 // FIXME: what's the correct offset?
6614 //
6615 // p[0] += (! m_xySym ? wmax : -wmax);
6616 // p[1] += (m_zSign ? hmax : -hmax);
6617
6618 break;
6619
6620 case AXE_VERT_DIR:
6621 p(0) -= wmax;
6622 angle = 90;
6623 break;
6624
6625 case AXE_HORZ_DIR:
6626 p(1) += hmax;
6627 break;
6628 }
6629
6630 if (zlabel_props.positionmode_is ("auto"))
6631 {
6632 p = xform.untransform (p(0), p(1), p(2), true);
6633
6634 p = convert_label_position (p, zlabel_props, xform,
6635 get_extent (false));
6636
6637 zlabel_props.set_position (p.extract_n (0, 3).transpose ());
6638 zlabel_props.set_positionmode ("auto");
6639 }
6640
6641 if (! isempty && zlabel_props.rotationmode_is ("auto"))
6642 {
6643 zlabel_props.set_rotation (angle);
6644 zlabel_props.set_rotationmode ("auto");
6645 }
6646 }
6647}
6648
6649static bool updating_title_position = false;
6650
6651void
6652axes::properties::update_title_position ()
6653{
6654 if (updating_title_position)
6655 return;
6656
6657 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
6658
6659 graphics_object go = gh_mgr.get_object (get_title ());
6660
6661 if (! go.valid_object ())
6662 return;
6663
6664 text::properties& title_props
6665 = reinterpret_cast<text::properties&> (go.get_properties ());
6666
6667 octave::unwind_protect_var<bool> restore_var (updating_title_position, true);
6668
6669 if (title_props.positionmode_is ("auto"))
6670 {
6671 graphics_xform xform = get_transform ();
6672
6673 // FIXME: bbox should be stored in axes::properties
6674 Matrix bbox = get_extent (false);
6675
6676 ColumnVector p
6677 = graphics_xform::xform_vector (bbox(0) + bbox(2)/2, bbox(1) - 10,
6678 (m_x_zlim(0) + m_x_zlim(1))/2);
6679
6680 if (m_x2Dtop)
6681 {
6682 Matrix ext (1, 2, 0.0);
6683 ext = get_ticklabel_extents (get_xtick ().matrix_value (),
6684 get_xticklabel ().string_vector_value (),
6685 get_xlim ().matrix_value ());
6686 p(1) -= ext(1);
6687 }
6688
6689 p = xform.untransform (p(0), p(1), p(2), true);
6690
6691 p = convert_label_position (p, title_props, xform, bbox);
6692
6693 title_props.set_position (p.extract_n (0, 3).transpose ());
6694 title_props.set_positionmode ("auto");
6695 }
6696}
6697
6698void
6699axes::properties::update_autopos (const std::string& elem_type)
6700{
6701 if (elem_type == "xlabel")
6702 update_xlabel_position ();
6703 else if (elem_type == "ylabel")
6704 update_ylabel_position ();
6705 else if (elem_type == "zlabel")
6706 update_zlabel_position ();
6707 else if (elem_type == "title")
6708 update_title_position ();
6709 else if (elem_type == "sync")
6710 sync_positions ();
6711}
6712
6713static void
6714normalized_aspectratios (Matrix& aspectratios, const Matrix& scalefactors,
6715 double xlength, double ylength, double zlength)
6716{
6717 double xval = xlength / scalefactors(0);
6718 double yval = ylength / scalefactors(1);
6719 double zval = zlength / scalefactors(2);
6720
6721 double minval = octave::math::min (octave::math::min (xval, yval), zval);
6722
6723 aspectratios(0) = xval / minval;
6724 aspectratios(1) = yval / minval;
6725 aspectratios(2) = zval / minval;
6726}
6727
6728static void
6729max_axes_scale (double& s, Matrix& limits, const Matrix& kids,
6730 double pbfactor, double dafactor, char limit_type, bool tight)
6731{
6732 if (tight)
6733 {
6734 double minval = octave::numeric_limits<double>::Inf ();
6735 double maxval = -octave::numeric_limits<double>::Inf ();
6736 double min_pos = octave::numeric_limits<double>::Inf ();
6737 double max_neg = -octave::numeric_limits<double>::Inf ();
6738 get_children_limits (minval, maxval, min_pos, max_neg, kids, limit_type);
6739 if (octave::math::isfinite (minval) && octave::math::isfinite (maxval))
6740 {
6741 limits(0) = minval;
6742 limits(1) = maxval;
6743 s = octave::math::max (s, (maxval - minval) / (pbfactor * dafactor));
6744 }
6745 }
6746 else
6747 s = octave::math::max (s, (limits(1) - limits(0)) / (pbfactor * dafactor));
6748}
6749
6750static std::set<double> updating_aspectratios;
6751
6752void
6753axes::properties::update_aspectratios ()
6754{
6755 if (updating_aspectratios.find (get___myhandle__ ().value ())
6756 != updating_aspectratios.end ())
6757 return;
6758
6759 Matrix xlimits = get_xlim ().matrix_value ();
6760 Matrix ylimits = get_ylim ().matrix_value ();
6761 Matrix zlimits = get_zlim ().matrix_value ();
6762
6763 double dx = (xlimits(1) - xlimits(0));
6764 double dy = (ylimits(1) - ylimits(0));
6765 double dz = (zlimits(1) - zlimits(0));
6766
6767 Matrix da = get_dataaspectratio ().matrix_value ();
6768 Matrix pba = get_plotboxaspectratio ().matrix_value ();
6769
6770 if (dataaspectratiomode_is ("auto"))
6771 {
6772 if (plotboxaspectratiomode_is ("auto"))
6773 {
6774 pba = Matrix (1, 3, 1.0);
6775 m_plotboxaspectratio.set (pba, false);
6776 }
6777
6778 normalized_aspectratios (da, pba, dx, dy, dz);
6779 m_dataaspectratio.set (da, false);
6780 }
6781 else if (plotboxaspectratiomode_is ("auto"))
6782 {
6783 normalized_aspectratios (pba, da, dx, dy, dz);
6784 m_plotboxaspectratio.set (pba, false);
6785 }
6786 else
6787 {
6788 double s = -octave::numeric_limits<double>::Inf ();
6789 bool modified_limits = false;
6790 Matrix kids;
6791
6792 if (xlimmode_is ("auto") && ylimmode_is ("auto") && zlimmode_is ("auto"))
6793 {
6794 modified_limits = true;
6795 kids = get_children ();
6796 max_axes_scale (s, xlimits, kids, pba(0), da(0), 'x', true);
6797 max_axes_scale (s, ylimits, kids, pba(1), da(1), 'y', true);
6798 max_axes_scale (s, zlimits, kids, pba(2), da(2), 'z', true);
6799 }
6800 else if (xlimmode_is ("auto") && ylimmode_is ("auto"))
6801 {
6802 modified_limits = true;
6803 max_axes_scale (s, zlimits, kids, pba(2), da(2), 'z', false);
6804 }
6805 else if (ylimmode_is ("auto") && zlimmode_is ("auto"))
6806 {
6807 modified_limits = true;
6808 max_axes_scale (s, xlimits, kids, pba(0), da(0), 'x', false);
6809 }
6810 else if (zlimmode_is ("auto") && xlimmode_is ("auto"))
6811 {
6812 modified_limits = true;
6813 max_axes_scale (s, ylimits, kids, pba(1), da(1), 'y', false);
6814 }
6815
6816 if (modified_limits)
6817 {
6818 octave::unwind_protect_var<std::set<double>>
6819 restore_var (updating_aspectratios);
6820
6821 updating_aspectratios.insert (get___myhandle__ ().value ());
6822
6823 dx = pba(0) * da(0);
6824 dy = pba(1) * da(1);
6825 dz = pba(2) * da(2);
6826 if (octave::math::isinf (s))
6827 s = 1 / octave::math::min (octave::math::min (dx, dy), dz);
6828
6829 if (xlimmode_is ("auto"))
6830 {
6831 dx = s * dx;
6832 xlimits(0) = 0.5 * (xlimits(0) + xlimits(1) - dx);
6833 xlimits(1) = xlimits(0) + dx;
6834 set_xlim (xlimits);
6835 set_xlimmode ("auto");
6836 }
6837
6838 if (ylimmode_is ("auto"))
6839 {
6840 dy = s * dy;
6841 ylimits(0) = 0.5 * (ylimits(0) + ylimits(1) - dy);
6842 ylimits(1) = ylimits(0) + dy;
6843 set_ylim (ylimits);
6844 set_ylimmode ("auto");
6845 }
6846
6847 if (zlimmode_is ("auto"))
6848 {
6849 dz = s * dz;
6850 zlimits(0) = 0.5 * (zlimits(0) + zlimits(1) - dz);
6851 zlimits(1) = zlimits(0) + dz;
6852 set_zlim (zlimits);
6853 set_zlimmode ("auto");
6854 }
6855 }
6856 else
6857 {
6858 normalized_aspectratios (pba, da, dx, dy, dz);
6859 m_plotboxaspectratio.set (pba, false);
6860 }
6861 }
6862}
6863
6864void
6865axes::properties::update_label_color (handle_property label,
6866 color_property col)
6867{
6868 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
6869
6870 gh_mgr.get_object (label.handle_value ()).set ("color", col.get ());
6871}
6872
6873void
6874axes::properties::update_font (std::string prop)
6875{
6876 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
6877
6878 if (! prop.empty ())
6879 {
6880 octave_value val = get (prop);
6881 octave_value tval = val;
6882 if (prop == "fontsize")
6883 {
6884 tval = octave_value (val.double_value () *
6885 get_titlefontsizemultiplier ());
6886 val = octave_value (val.double_value () *
6887 get_labelfontsizemultiplier ());
6888 }
6889 else if (prop == "fontweight")
6890 tval = get ("titlefontweight");
6891
6892 gh_mgr.get_object (get_xlabel ()).set (prop, val);
6893 gh_mgr.get_object (get_ylabel ()).set (prop, val);
6894 gh_mgr.get_object (get_zlabel ()).set (prop, val);
6895 gh_mgr.get_object (get_title ()).set (prop, tval);
6896 }
6897
6898 double dpr = device_pixel_ratio (get___myhandle__ ());
6899
6900 octave::autolock guard (gh_mgr.graphics_lock ());
6901
6902 m_txt_renderer.set_font (get ("fontname").string_value (),
6903 get ("fontweight").string_value (),
6904 get ("fontangle").string_value (),
6905 get ("__fontsize_points__").double_value () * dpr);
6906}
6907
6908// The INTERNAL flag defines whether position or outerposition is used.
6909
6910Matrix
6911axes::properties::get_boundingbox (bool internal,
6912 const Matrix& parent_pix_size) const
6913{
6914 Matrix pos = (internal ? get_position ().matrix_value ()
6915 : get_outerposition ().matrix_value ());
6916 Matrix parent_size (parent_pix_size);
6917
6918 if (parent_size.isempty ())
6919 {
6920 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
6921
6922 graphics_object go = gh_mgr.get_object (get_parent ());
6923
6924 if (go.valid_object ())
6925 parent_size
6926 = go.get_properties ().get_boundingbox (true).extract_n (0, 2, 1, 2);
6927 else
6928 parent_size = default_figure_position ();
6929 }
6930
6931 pos = convert_position (pos, get_units (), "pixels", parent_size);
6932
6933 pos(0)--;
6934 pos(1)--;
6935 pos(1) = parent_size(1) - pos(1) - pos(3);
6936
6937 return pos;
6938}
6939
6940Matrix
6941axes::properties::get_extent (bool with_text, bool only_text_height) const
6942{
6943 graphics_xform xform = get_transform ();
6944
6945 Matrix ext (1, 4, 0.0);
6946 ext(0) = ext(1) = octave::numeric_limits<double>::Inf ();
6947 ext(2) = ext(3) = -octave::numeric_limits<double>::Inf ();
6948 for (int i = 0; i <= 1; i++)
6949 for (int j = 0; j <= 1; j++)
6950 for (int k = 0; k <= 1; k++)
6951 {
6952 ColumnVector p = xform.transform (i ? m_xPlaneN : m_xPlane,
6953 j ? m_yPlaneN : m_yPlane,
6954 k ? m_zPlaneN : m_zPlane, false);
6955 ext(0) = std::min (ext(0), p(0));
6956 ext(1) = std::min (ext(1), p(1));
6957 ext(2) = std::max (ext(2), p(0));
6958 ext(3) = std::max (ext(3), p(1));
6959 }
6960
6961 if (with_text)
6962 {
6963 for (int i = 0; i < 4; i++)
6964 {
6965 graphics_handle htext;
6966 if (i == 0)
6967 htext = get_title ();
6968 else if (i == 1)
6969 htext = get_xlabel ();
6970 else if (i == 2)
6971 htext = get_ylabel ();
6972 else if (i == 3)
6973 htext = get_zlabel ();
6974
6975 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
6976
6977 text::properties& text_props
6978 = reinterpret_cast<text::properties&>
6979 (gh_mgr.get_object (htext).get_properties ());
6980
6981 Matrix text_pos = text_props.get_data_position ();
6982 text_pos = xform.transform (text_pos(0), text_pos(1), text_pos(2));
6983 if (text_props.get_string ().isempty ())
6984 {
6985 ext(0) = std::min (ext(0), text_pos(0));
6986 ext(1) = std::min (ext(1), text_pos(1));
6987 ext(2) = std::max (ext(2), text_pos(0));
6988 ext(3) = std::max (ext(3), text_pos(1));
6989 }
6990 else
6991 {
6992 Matrix text_ext = text_props.get_extent_matrix (true);
6993
6994 // The text extent is returned in device pixels. Unscale and
6995 // work with logical pixels
6996 double dpr = device_pixel_ratio (get___myhandle__ ());
6997 if (dpr != 1.0)
6998 for (int j = 0; j < 4; j++)
6999 text_ext(j) /= dpr;
7000
7001 bool ignore_horizontal = false;
7002 bool ignore_vertical = false;
7003 if (only_text_height)
7004 {
7005 double text_rotation = text_props.get_rotation ();
7006 if (text_rotation == 0. || text_rotation == 180.)
7007 ignore_horizontal = true;
7008 else if (text_rotation == 90. || text_rotation == 270.)
7009 ignore_vertical = true;
7010 }
7011
7012 if (! ignore_horizontal)
7013 {
7014 ext(0) = std::min (ext(0), text_pos(0)+text_ext(0));
7015 ext(2) = std::max (ext(2),
7016 text_pos(0)+text_ext(0)+text_ext(2));
7017 }
7018
7019 if (! ignore_vertical)
7020 {
7021 ext(1) = std::min (ext(1),
7022 text_pos(1)-text_ext(1)-text_ext(3));
7023 ext(3) = std::max (ext(3), text_pos(1)-text_ext(1));
7024 }
7025 }
7026 }
7027 }
7028
7029 ext(2) = ext(2) - ext(0);
7030 ext(3) = ext(3) - ext(1);
7031
7032 return ext;
7033}
7034
7035static octave_value
7036convert_ticklabel_string (const octave_value& val)
7037{
7038 octave_value retval = val;
7039
7040 if (val.iscellstr ())
7041 {
7042 // Always return a column vector for Matlab compatibility
7043 if (val.columns () > 1)
7044 retval = val.reshape (dim_vector (val.numel (), 1));
7045 }
7046 else
7047 {
7048 string_vector sv;
7049 if (val.isnumeric ())
7050 {
7051 NDArray data = val.array_value ();
7052 std::ostringstream oss;
7053 oss.precision (5);
7054 for (octave_idx_type i = 0; i < val.numel (); i++)
7055 {
7056 oss.str ("");
7057 // FIXME: Code should probably call out to display routines
7058 // within Octave, rather than hack things up with C++ library.
7059 // See FIXME in calc_ticklabels().
7060 if (std::abs (data(i)) < 1.0)
7061 oss.precision (4);
7062 else
7063 oss.precision (5);
7064 oss << data(i);
7065 sv.append (oss.str ());
7066 }
7067 }
7068 else if (val.is_string () && val.rows () == 1)
7069 {
7070 std::string valstr = val.string_value ();
7071 std::istringstream iss (valstr);
7072 std::string tmpstr;
7073
7074 // Split string with delimiter '|'
7075 while (std::getline (iss, tmpstr, '|'))
7076 sv.append (tmpstr);
7077
7078 // If string ends with '|' Matlab appends a null string
7079 if (*valstr.rbegin () == '|')
7080 sv.append (std::string (""));
7081 }
7082 else
7083 return retval;
7084
7085 charMatrix chmat (sv, ' ');
7086
7087 retval = octave_value (chmat);
7088 }
7089
7090 return retval;
7091}
7092
7093void
7094axes::properties::set_xticklabel (const octave_value& val)
7095{
7096 if (m_xticklabel.set (convert_ticklabel_string (val), false))
7097 {
7098 set_xticklabelmode ("manual");
7099 m_xticklabel.run_listeners (GCB_POSTSET);
7100 mark_modified ();
7101 }
7102 else
7103 set_xticklabelmode ("manual");
7104
7105 sync_positions ();
7106}
7107
7108void
7109axes::properties::set_yticklabel (const octave_value& val)
7110{
7111 if (m_yticklabel.set (convert_ticklabel_string (val), false))
7112 {
7113 set_yticklabelmode ("manual");
7114 m_yticklabel.run_listeners (GCB_POSTSET);
7115 mark_modified ();
7116 }
7117 else
7118 set_yticklabelmode ("manual");
7119
7120 sync_positions ();
7121}
7122
7123void
7124axes::properties::set_zticklabel (const octave_value& val)
7125{
7126 if (m_zticklabel.set (convert_ticklabel_string (val), false))
7127 {
7128 set_zticklabelmode ("manual");
7129 m_zticklabel.run_listeners (GCB_POSTSET);
7130 mark_modified ();
7131 }
7132 else
7133 set_zticklabelmode ("manual");
7134
7135 sync_positions ();
7136}
7137
7138// Almost identical to convert_ticklabel_string but it only accepts
7139// cellstr or string, not numeric input.
7140static octave_value
7141convert_linestyleorder_string (const octave_value& val)
7142{
7143 octave_value retval = val;
7144
7145 if (val.iscellstr ())
7146 {
7147 // Always return a column vector for Matlab Compatibility
7148 if (val.columns () > 1)
7149 retval = val.reshape (dim_vector (val.numel (), 1));
7150 }
7151 else
7152 {
7153 string_vector sv;
7154 if (val.is_string () && val.rows () == 1)
7155 {
7156 std::string valstr = val.string_value ();
7157 std::istringstream iss (valstr);
7158 std::string tmpstr;
7159
7160 // Split string with delimiter '|'
7161 while (std::getline (iss, tmpstr, '|'))
7162 sv.append (tmpstr);
7163
7164 // If string ends with '|' Matlab appends a null string
7165 if (*valstr.rbegin () == '|')
7166 sv.append (std::string (""));
7167 }
7168 else
7169 return retval;
7170
7171 charMatrix chmat (sv, ' ');
7172
7173 retval = octave_value (chmat);
7174 }
7175
7176 return retval;
7177}
7178
7179void
7180axes::properties::set_linestyleorder (const octave_value& val)
7181{
7182 m_linestyleorder.set (convert_linestyleorder_string (val), false);
7183}
7184
7185void
7186axes::properties::set_units (const octave_value& val)
7187{
7188 caseless_str old_units = get_units ();
7189
7190 if (m_units.set (val, true))
7191 {
7192 update_units (old_units);
7193 mark_modified ();
7194 }
7195}
7196
7197void
7198axes::properties::update_units (const caseless_str& old_units)
7199{
7200 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
7201
7202 graphics_object parent_go = gh_mgr.get_object (get_parent ());
7203
7204 Matrix parent_bb
7205 = parent_go.get_properties ().get_boundingbox (true).extract_n (0, 2, 1, 2);
7206
7207 caseless_str new_units = get_units ();
7208 m_position.set (octave_value
7209 (convert_position (get_position ().matrix_value (),
7210 old_units, new_units, parent_bb)),
7211 false);
7212 m_outerposition.set (octave_value
7213 (convert_position (get_outerposition ().matrix_value (),
7214 old_units, new_units, parent_bb)),
7215 false);
7216 m_tightinset.set (octave_value
7217 (convert_position (get_tightinset ().matrix_value (),
7218 old_units, new_units, parent_bb)),
7219 false);
7220 m_looseinset.set (octave_value
7221 (convert_position (get_looseinset ().matrix_value (),
7222 old_units, new_units, parent_bb)),
7223 false);
7224}
7225
7226void
7227axes::properties::set_fontunits (const octave_value& val)
7228{
7229 caseless_str old_fontunits = get_fontunits ();
7230
7231 if (m_fontunits.set (val, true))
7232 {
7233 update_fontunits (old_fontunits);
7234 mark_modified ();
7235 }
7236}
7237
7238void
7239axes::properties::update_fontunits (const caseless_str& old_units)
7240{
7241 caseless_str new_units = get_fontunits ();
7242 double parent_height = get_boundingbox (true).elem (3);
7243 double fontsz = get_fontsize ();
7244
7245 fontsz = convert_font_size (fontsz, old_units, new_units, parent_height);
7246
7247 set_fontsize (octave_value (fontsz));
7248}
7249
7250double
7251axes::properties::get___fontsize_points__ (double box_pix_height) const
7252{
7253 double fontsz = get_fontsize ();
7254 double parent_height = box_pix_height;
7255
7256 if (fontunits_is ("normalized") && parent_height <= 0)
7257 parent_height = get_boundingbox (true).elem (3);
7258
7259 return convert_font_size (fontsz, get_fontunits (), "points", parent_height);
7260}
7261
7263graphics_xform::xform_vector (double x, double y, double z)
7264{
7265 return octave::xform_vector (x, y, z);
7266}
7267
7268Matrix
7269graphics_xform::xform_eye ()
7270{
7271 return octave::xform_matrix ();
7272}
7273
7275graphics_xform::transform (double x, double y, double z, bool use_scale) const
7276{
7277 if (use_scale)
7278 {
7279 x = m_sx.scale (x);
7280 y = m_sy.scale (y);
7281 z = m_sz.scale (z);
7282 }
7283
7284 return octave::transform (m_xform, x, y, z);
7285}
7286
7288graphics_xform::untransform (double x, double y, double z,
7289 bool use_scale) const
7290{
7291 ColumnVector v = octave::transform (m_xform_inv, x, y, z);
7292
7293 if (use_scale)
7294 {
7295 v(0) = m_sx.unscale (v(0));
7296 v(1) = m_sy.unscale (v(1));
7297 v(2) = m_sz.unscale (v(2));
7298 }
7299
7300 return v;
7301}
7302
7304axes::get_default (const caseless_str& pname) const
7305{
7306 octave_value retval = m_default_properties.lookup (pname);
7307
7308 if (retval.is_undefined ())
7309 {
7310 graphics_handle parent_h = get_parent ();
7311
7312 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
7313
7314 graphics_object parent_go = gh_mgr.get_object (parent_h);
7315
7316 retval = parent_go.get_default (pname);
7317 }
7318
7319 return retval;
7320}
7321
7322// FIXME: remove.
7323// FIXME: maybe this should go into array_property class?
7324/*
7325static void
7326check_limit_vals (double& min_val, double& max_val,
7327 double& min_pos, double& max_neg,
7328 const array_property& data)
7329{
7330 double val = data.min_val ();
7331 if (octave::math::isfinite (val) && val < min_val)
7332 min_val = val;
7333 val = data.max_val ();
7334 if (octave::math::isfinite (val) && val > max_val)
7335 max_val = val;
7336 val = data.min_pos ();
7337 if (octave::math::isfinite (val) && val > 0 && val < min_pos)
7338 min_pos = val;
7339 val = data.max_neg ();
7340 if (octave::math::isfinite (val) && val < 0 && val > max_neg)
7341 max_neg = val;
7342}
7343*/
7344
7345static void
7346check_limit_vals (double& min_val, double& max_val,
7347 double& min_pos, double& max_neg,
7348 const octave_value& data)
7349{
7350 Matrix m;
7351
7352 if (data.is_matrix_type ())
7353 m = data.matrix_value ();
7354
7355 if (m.numel () != 4)
7356 {
7357 m = Matrix (1, 4, 0.0);
7358 m(2) = octave::numeric_limits<double>::Inf ();
7359 m(3) = -octave::numeric_limits<double>::Inf ();
7360 }
7361
7362 double val;
7363
7364 val = m(0);
7365 if (octave::math::isfinite (val) && val < min_val)
7366 min_val = val;
7367
7368 val = m(1);
7369 if (octave::math::isfinite (val) && val > max_val)
7370 max_val = val;
7371
7372 val = m(2);
7373 if (octave::math::isfinite (val) && val > 0 && val < min_pos)
7374 min_pos = val;
7375
7376 val = m(3);
7377 if (octave::math::isfinite (val) && val < 0 && val > max_neg)
7378 max_neg = val;
7379}
7380
7381// magform(x) Returns (a, b),
7382// where x = a * 10^b, abs (a) >= 1., and b is integer.
7383
7384static void
7385magform (double x, double& a, int& b)
7386{
7387 if (x == 0)
7388 {
7389 a = 0;
7390 b = 0;
7391 }
7392 else
7393 {
7394 b = static_cast<int> (std::floor (std::log10 (std::abs (x))));
7395 a = x / std::pow (10.0, b);
7396 }
7397}
7398
7399void
7400axes::properties::update_outerposition ()
7401{
7402 set_positionconstraint ("outerposition");
7403 caseless_str old_units = get_units ();
7404 set_units ("normalized");
7405
7406 Matrix outerbox = m_outerposition.get ().matrix_value ();
7407
7408 double outer_left = outerbox(0);
7409 double outer_bottom = outerbox(1);
7410 double outer_width = outerbox(2);
7411 double outer_height = outerbox(3);
7412
7413 double outer_right = outer_width + outer_left;
7414 double outer_top = outer_height + outer_bottom;
7415
7416 Matrix linset = m_looseinset.get ().matrix_value ();
7417 Matrix tinset = m_tightinset.get ().matrix_value ();
7418
7419 double left_margin = std::max (linset(0), tinset(0));
7420 double bottom_margin = std::max (linset(1), tinset(1));
7421 double right_margin = std::max (linset(2), tinset(2));
7422 double top_margin = std::max (linset(3), tinset(3));
7423
7424 double inner_left = outer_left;
7425 double inner_right = outer_right;
7426
7427 if ((left_margin + right_margin) < outer_width)
7428 {
7429 inner_left += left_margin;
7430 inner_right -= right_margin;
7431 }
7432
7433 double inner_bottom = outer_bottom;
7434 double inner_top = outer_top;
7435
7436 if ((bottom_margin + top_margin) < outer_height)
7437 {
7438 inner_bottom += bottom_margin;
7439 inner_top -= top_margin;
7440 }
7441
7442 double inner_width = inner_right - inner_left;
7443 double inner_height = inner_top - inner_bottom;
7444
7445 Matrix innerbox (1, 4);
7446
7447 innerbox(0) = inner_left;
7448 innerbox(1) = inner_bottom;
7449 innerbox(2) = inner_width;
7450 innerbox(3) = inner_height;
7451
7452 m_position = innerbox;
7453
7454 set_units (old_units);
7455 update_transform ();
7456}
7457
7458void
7459axes::properties::update_position ()
7460{
7461 set_positionconstraint ("innerposition");
7462 caseless_str old_units = get_units ();
7463 set_units ("normalized");
7464
7465 Matrix innerbox = m_position.get ().matrix_value ();
7466
7467 double inner_left = innerbox(0);
7468 double inner_bottom = innerbox(1);
7469 double inner_width = innerbox(2);
7470 double inner_height = innerbox(3);
7471
7472 double inner_right = inner_width + inner_left;
7473 double inner_top = inner_height + inner_bottom;
7474
7475 Matrix linset = m_looseinset.get ().matrix_value ();
7476 Matrix tinset = m_tightinset.get ().matrix_value ();
7477
7478 double left_margin = std::max (linset(0), tinset(0));
7479 double bottom_margin = std::max (linset(1), tinset(1));
7480 double right_margin = std::max (linset(2), tinset(2));
7481 double top_margin = std::max (linset(3), tinset(3));
7482
7483 // FIXME: do we need to place limits on any of these?
7484
7485 double outer_left = inner_left - left_margin;
7486 double outer_bottom = inner_bottom - bottom_margin;
7487 double outer_right = inner_right + right_margin;
7488 double outer_top = inner_top + top_margin;
7489
7490 double outer_width = outer_right - outer_left;
7491 double outer_height = outer_top - outer_bottom;
7492
7493 Matrix outerbox (1, 4);
7494
7495 outerbox(0) = outer_left;
7496 outerbox(1) = outer_bottom;
7497 outerbox(2) = outer_width;
7498 outerbox(3) = outer_height;
7499
7500 m_outerposition = outerbox;
7501
7502 set_units (old_units);
7503 update_transform ();
7504}
7505
7506void
7507axes::properties::update_looseinset ()
7508{
7509 caseless_str old_units = get_units ();
7510 set_units ("normalized");
7511
7512 Matrix linset = m_looseinset.get ().matrix_value ();
7513 Matrix tinset = m_tightinset.get ().matrix_value ();
7514
7515 double left_margin = std::max (linset(0), tinset(0));
7516 double bottom_margin = std::max (linset(1), tinset(1));
7517 double right_margin = std::max (linset(2), tinset(2));
7518 double top_margin = std::max (linset(3), tinset(3));
7519
7520 if (m_positionconstraint.is ("innerposition"))
7521 {
7522 Matrix innerbox = m_position.get ().matrix_value ();
7523
7524 double inner_left = innerbox(0);
7525 double inner_bottom = innerbox(1);
7526 double inner_width = innerbox(2);
7527 double inner_height = innerbox(3);
7528
7529 double inner_right = inner_width + inner_left;
7530 double inner_top = inner_height + inner_bottom;
7531
7532 // FIXME: do we need to place limits on any of these?
7533
7534 double outer_left = inner_left - left_margin;
7535 double outer_bottom = inner_bottom - bottom_margin;
7536 double outer_right = inner_right + right_margin;
7537 double outer_top = inner_top + top_margin;
7538
7539 double outer_width = outer_right - outer_left;
7540 double outer_height = outer_top - outer_bottom;
7541
7542 Matrix outerbox (1, 4);
7543
7544 outerbox(0) = outer_left;
7545 outerbox(1) = outer_bottom;
7546 outerbox(2) = outer_width;
7547 outerbox(3) = outer_height;
7548
7549 m_outerposition = outerbox;
7550 }
7551 else
7552 {
7553 Matrix outerbox = m_outerposition.get ().matrix_value ();
7554
7555 double outer_left = outerbox(0);
7556 double outer_bottom = outerbox(1);
7557 double outer_width = outerbox(2);
7558 double outer_height = outerbox(3);
7559
7560 double outer_right = outer_width + outer_left;
7561 double outer_top = outer_height + outer_bottom;
7562
7563 double inner_left = outer_left;
7564 double inner_right = outer_right;
7565
7566 if ((left_margin + right_margin) < outer_width)
7567 {
7568 inner_left += left_margin;
7569 inner_right -= right_margin;
7570 }
7571
7572 double inner_bottom = outer_bottom;
7573 double inner_top = outer_top;
7574
7575 if ((bottom_margin + top_margin) < outer_height)
7576 {
7577 inner_bottom += bottom_margin;
7578 inner_top -= top_margin;
7579 }
7580
7581 double inner_width = inner_right - inner_left;
7582 double inner_height = inner_top - inner_bottom;
7583
7584 Matrix innerbox (1, 4);
7585
7586 innerbox(0) = inner_left;
7587 innerbox(1) = inner_bottom;
7588 innerbox(2) = inner_width;
7589 innerbox(3) = inner_height;
7590
7591 m_position = innerbox;
7592 }
7593
7594 set_units (old_units);
7595 update_transform ();
7596}
7597
7598// A translation from Tom Holoryd's python code at
7599// http://kurage.nimh.nih.gov/tomh/tics.py
7600// FIXME: add log ticks
7601
7602double
7603axes::properties::calc_tick_sep (double lo, double hi)
7604{
7605 int ticint = 5;
7606
7607 // Reference: C. R. Lewart, "Algorithms SCALE1, SCALE2, and SCALE3 for
7608 // Determination of Scales on Computer Generated Plots", Communications of
7609 // the ACM, 10, pp. 639-640, 1973.
7610 // Also cited as ACM Algorithm 463.
7611
7612 double a;
7613 int b, x;
7614
7615 magform ((hi - lo) / ticint, a, b);
7616
7617 static const double SQRT_2 = sqrt (2.0);
7618 static const double SQRT_10 = sqrt (10.0);
7619 static const double SQRT_50 = sqrt (50.0);
7620
7621 if (a < SQRT_2)
7622 x = 1;
7623 else if (a < SQRT_10)
7624 x = 2;
7625 else if (a < SQRT_50)
7626 x = 5;
7627 else
7628 x = 10;
7629
7630 return x * std::pow (10.0, b);
7631}
7632
7633// Attempt to make "nice" limits from the actual max and min of the data.
7634// For log plots, we will also use the smallest strictly positive value.
7635
7636Matrix
7637axes::properties::get_axis_limits (double xmin, double xmax,
7638 double min_pos, double max_neg,
7639 const bool logscale,
7640 const std::string& method)
7641{
7642 Matrix retval;
7643
7644 double min_val = xmin;
7645 double max_val = xmax;
7646
7647 if (octave::math::isinf (min_val) && min_val > 0
7648 && octave::math::isinf (max_val) && max_val < 0)
7649 {
7650 retval = default_lim (logscale);
7651 return retval;
7652 }
7653 else if (! (octave::math::isinf (min_val) || octave::math::isinf (max_val)))
7654 {
7655 if (logscale)
7656 {
7657 if (octave::math::isinf (min_pos) && octave::math::isinf (max_neg))
7658 {
7659 // FIXME: max_neg is needed for "loglog ([0 -Inf])"
7660 // This is the *only* place where max_neg is needed.
7661 // Is there another way?
7662 retval = default_lim (logscale);
7663 return retval;
7664 }
7665 if (min_val <= 0)
7666 {
7667 if (max_val > 0)
7668 {
7669 warning_with_id ("Octave:negative-data-log-axis",
7670 "axis: omitting non-positive data in log plot");
7671 min_val = min_pos;
7672 }
7673 else if (max_val == 0)
7674 max_val = max_neg;
7675 }
7676 // FIXME: maybe this test should also be relative?
7677 if (std::abs (min_val - max_val)
7678 < sqrt (std::numeric_limits<double>::epsilon ()))
7679 {
7680 // Widen range when too small
7681 if (min_val >= 0)
7682 {
7683 min_val *= 0.9;
7684 max_val *= 1.1;
7685 }
7686 else
7687 {
7688 min_val *= 1.1;
7689 max_val *= 0.9;
7690 }
7691 }
7692
7693 if (method == "tickaligned")
7694 {
7695 if (min_val > 0)
7696 {
7697 // Log plots with all positive data
7698 min_val = std::pow (10, std::floor (log10 (min_val)));
7699 max_val = std::pow (10, std::ceil (log10 (max_val)));
7700 }
7701 else
7702 {
7703 // Log plots with all negative data
7704 min_val = -std::pow (10, std::ceil (log10 (-min_val)));
7705 max_val = -std::pow (10, std::floor (log10 (-max_val)));
7706 }
7707 }
7708 else if (method == "padded")
7709 {
7710 if (min_val > 0)
7711 {
7712 // Log plots with all positive data
7713 double pad = (log10 (max_val) - log10 (min_val)) * 0.07;
7714 min_val = std::pow (10, log10 (min_val) - pad);
7715 max_val = std::pow (10, log10 (max_val) + pad);
7716 }
7717 else
7718 {
7719 // Log plots with all negative data
7720 double pad = (log10 (-min_val) - log10 (-max_val)) * 0.07;
7721 min_val = -std::pow (10, log10 (-min_val) + pad);
7722 max_val = -std::pow (10, log10 (-max_val) - pad);
7723 }
7724 }
7725 }
7726 else
7727 {
7728 if (min_val == 0 && max_val == 0)
7729 {
7730 min_val = -1;
7731 max_val = 1;
7732 }
7733 // FIXME: maybe this test should also be relative?
7734 else if (std::abs (min_val - max_val)
7735 < sqrt (std::numeric_limits<double>::epsilon ()))
7736 {
7737 min_val -= 0.1 * std::abs (min_val);
7738 max_val += 0.1 * std::abs (max_val);
7739 }
7740
7741 if (method == "tickaligned")
7742 {
7743 double tick_sep = calc_tick_sep (min_val, max_val);
7744 double min_tick = std::floor (min_val / tick_sep);
7745 double max_tick = std::ceil (max_val / tick_sep);
7746 // Prevent round-off from cropping ticks
7747 min_val = std::min (min_val, tick_sep * min_tick);
7748 max_val = std::max (max_val, tick_sep * max_tick);
7749 }
7750 else if (method == "padded")
7751 {
7752 double pad = 0.07 * (max_val - min_val);
7753 min_val -= pad;
7754 max_val += pad;
7755 }
7756 }
7757 }
7758
7759 retval.resize (1, 2);
7760
7761 retval(0) = min_val;
7762 retval(1) = max_val;
7763
7764 return retval;
7765}
7766
7767void
7768axes::properties::check_axis_limits (Matrix& limits, const Matrix kids,
7769 const bool logscale, char& update_type)
7770{
7771 double min_val = octave::numeric_limits<double>::Inf ();
7772 double max_val = -octave::numeric_limits<double>::Inf ();
7773 double min_pos = octave::numeric_limits<double>::Inf ();
7774 double max_neg = -octave::numeric_limits<double>::Inf ();
7775 double eps = std::numeric_limits<double>::epsilon ();
7776 bool do_update = false;
7777 bool have_children_limits = false;
7778
7779 // check whether we need to get children limits
7780 if (! octave::math::isfinite (limits(0))
7781 || ! octave::math::isfinite (limits(1)))
7782 {
7783 get_children_limits (min_val, max_val, min_pos, max_neg, kids,
7784 update_type);
7785 have_children_limits = true;
7786 }
7787 if (! octave::math::isfinite (limits(0)))
7788 {
7789 limits(0) = min_val;
7790 do_update = true;
7791 }
7792 if (! octave::math::isfinite (limits(1)))
7793 {
7794 limits(1) = max_val;
7795 do_update = true;
7796 }
7797 if (limits(0) == 0 && limits(1) == 0)
7798 {
7799 limits = default_lim (logscale);
7800 do_update = true;
7801 }
7802 // FIXME: maybe this test should also be relative?
7803 else if (! logscale && (std::abs (limits(0) - limits(1)) < sqrt (eps)))
7804 {
7805 limits(0) -= 0.1 * std::abs (limits(0));
7806 limits(1) += 0.1 * std::abs (limits(1));
7807 do_update = true;
7808 }
7809 else if (logscale
7810 && (std::abs (std::log10 (limits(0) / limits(1))) < sqrt (eps)))
7811 {
7812 limits(0) = (limits(0) < 0 ? 10.0 * limits(0) : 0.1 * limits(0));
7813 limits(1) = (limits(1) < 0 ? 0.1 * limits(1) : 10.0 * limits(1));
7814 do_update = true;
7815 }
7816
7817 if (logscale && limits(0)*limits(1) <= 0)
7818 {
7819 if (! have_children_limits)
7820 get_children_limits (min_val, max_val, min_pos, max_neg, kids,
7821 update_type);
7822
7823 if (limits(1) > 0)
7824 {
7825 warning_with_id ("Octave:axis-non-positive-log-limits",
7826 "Non-positive limit for logarithmic axis ignored\n");
7827 if (octave::math::isfinite (min_pos))
7828 limits(0) = min_pos;
7829 else
7830 limits(0) = 0.1 * limits(1);
7831 }
7832 else
7833 {
7834 warning_with_id ("Octave:axis-non-negative-log-limits",
7835 "Non-negative limit for logarithmic axis ignored\n");
7836 if (octave::math::isfinite (max_neg))
7837 limits(1) = max_neg;
7838 else
7839 limits(1) = 0.1 * limits(0);
7840 }
7841 // FIXME: maybe this test should also be relative?
7842 if (std::abs (limits(0) - limits(1)) < sqrt (eps))
7843 {
7844 // Widen range when too small
7845 if (limits(0) > 0)
7846 {
7847 limits(0) *= 0.9;
7848 limits(1) *= 1.1;
7849 }
7850 else
7851 {
7852 limits(0) *= 1.1;
7853 limits(1) *= 0.9;
7854 }
7855 }
7856 do_update = true;
7857 }
7858
7859 if (! do_update)
7860 update_type = 0;
7861
7862}
7863
7864/*
7865## Test validation of auto and manual axis limits
7866%!test
7867%! hf = figure ("visible", "off");
7868%! unwind_protect
7869%! hax = axes ("parent", hf);
7870%! plot (0, pi);
7871%! assert (get (hax, "xlim"), [-1, 1]);
7872%! assert (get (hax, "xlimmode"), "auto");
7873%! assert (get (hax, "ylim"), [2.8, 3.5], 2*eps);
7874%! assert (get (hax, "ylimmode"), "auto");
7875%! set (hax, "xlim", [1, 1], "ylim", [0, 0]);
7876%! assert (get (hax, "xlim"), [0.9, 1.1]);
7877%! assert (get (hax, "xlimmode"), "manual");
7878%! assert (get (hax, "ylim"), [0, 1]);
7879%! assert (get (hax, "ylimmode"), "manual");
7880%! set (hax, "xlim", [-Inf, Inf], "ylim", [-Inf, Inf]);
7881%! ## Matlab does not update the properties
7882%! assert (get (hax, "xlim"), [0, 1]);
7883%! assert (get (hax, "ylim"), [0.9, 1.1]*pi, 2*eps);
7884%! unwind_protect_cleanup
7885%! delete (hf);
7886%! end_unwind_protect
7887
7888%!test
7889%! hf = figure ("visible", "off");
7890%! unwind_protect
7891%! hax = axes ("parent", hf);
7892%! plot ([0, exp(1)], [-pi, 3]);
7893%! assert (get (hax, "xlim"), [0, 3]);
7894%! assert (get (hax, "xlimmode"), "auto");
7895%! assert (get (hax, "ylim"), [-4, 3]);
7896%! assert (get (hax, "ylimmode"), "auto");
7897%! set (hax, "xlim", [-Inf, Inf], "ylim", [-Inf, Inf]);
7898%! ## Matlab does not update the properties but uses tight limits on screen
7899%! assert (get (hax, "xlim"), [0, exp(1)]);
7900%! assert (get (hax, "xlimmode"), "manual");
7901%! assert (get (hax, "ylim"), [-pi, 3]);
7902%! assert (get (hax, "ylimmode"), "manual");
7903%! unwind_protect_cleanup
7904%! delete (hf);
7905%! end_unwind_protect
7906
7907%!test
7908%! hf = figure ("visible", "off");
7909%! unwind_protect
7910%! hax = axes ("parent", hf);
7911%! loglog (0, pi);
7912%! assert (get (hax, "xlim"), [0.1, 1.0]);
7913%! assert (get (hax, "xlimmode"), "auto");
7914%! assert (get (hax, "ylim"), [1, 10]);
7915%! assert (get (hax, "ylimmode"), "auto");
7916%! set (hax, "xlim", [1, 1], "ylim", [0, 0]);
7917%! assert (get (hax, "xlim"), [0.1, 10]);
7918%! assert (get (hax, "xlimmode"), "manual");
7919%! assert (get (hax, "ylim"), [0.1, 1.0]);
7920%! assert (get (hax, "ylimmode"), "manual");
7921%! set (hax, "xlim", [-Inf, Inf], "ylim", [-Inf, Inf]);
7922%! ## Matlab does not update the properties
7923%! assert (get (hax, "xlim"), [0.1, 1.0]);
7924%! assert (get (hax, "ylim"), [0.1, 10]*pi);
7925%! unwind_protect_cleanup
7926%! delete (hf);
7927%! end_unwind_protect
7928
7929%!test
7930%! hf = figure ("visible", "off");
7931%! unwind_protect
7932%! hax = axes ("parent", hf);
7933%! loglog ([0 -1], [0 1]);
7934%! assert (get (hax, "xlim"), [-10, -0.1]);
7935%! assert (get (hax, "xlimmode"), "auto");
7936%! assert (get (hax, "ylim"), [0.1, 10]);
7937%! assert (get (hax, "ylimmode"), "auto");
7938%! set (hax, "xlim", [-Inf, Inf], "ylim", [-Inf, Inf]);
7939%! ## Matlab does not update the properties
7940%! assert (get (hax, "xlim"), [-1.1, -0.9]);
7941%! assert (get (hax, "ylim"), [0.9, 1.1]);
7942%! unwind_protect_cleanup
7943%! delete (hf);
7944%! end_unwind_protect
7945
7946%!test
7947%! hf = figure ("visible", "off");
7948%! unwind_protect
7949%! hax = axes ("parent", hf);
7950%! loglog ([1 -1], [1 pi]);
7951%! assert (get (hax, "xlim"), [0.1, 10]);
7952%! assert (get (hax, "xlimmode"), "auto");
7953%! assert (get (hax, "ylim"), [1, 10]);
7954%! assert (get (hax, "ylimmode"), "auto");
7955%! set (hax, "xlim", [-Inf, Inf], "ylim", [-Inf, Inf]);
7956%! ## Matlab does not update the properties but uses tight limits on screen
7957%! assert (get (hax, "xlim"), [0.9, 1.1]);
7958%! assert (get (hax, "ylim"), [1, pi]);
7959%! unwind_protect_cleanup
7960%! delete (hf);
7961%! end_unwind_protect
7962
7963## Check that graphics objects with hidden handle visibility are included in
7964## axis limit calculation.
7965%!test <*63095>
7966%! hf = figure ("visible", "off");
7967%! unwind_protect
7968%! hax = axes ("parent", hf);
7969%! plot (hax, [0, 1]);
7970%! assert (get (hax, "ylim"), [0, 1]);
7971%! hold (hax, "on");
7972%! plot (hax, [2, 0], "handlevisibility", "off");
7973%! assert (get (hax, "ylim"), [0, 2]);
7974%! unwind_protect_cleanup
7975%! delete (hf);
7976%! end_unwind_protect
7977
7978## Check automatically determined axes ticks with "tickaligned" (default) and
7979## "tight" xlimitmethod.
7980%!test <*63624>
7981%! hf = figure ("visible", "off");
7982%! unwind_protect
7983%! hax = axes ("parent", hf);
7984%! plot (hax, [1, 201], [0, 1]);
7985%! assert (get (hax, "xtick"), 0:50:250);
7986%! axis (hax, "tight");
7987%! assert (get (hax, "xtick"), 50:50:200);
7988%! unwind_protect_cleanup
7989%! delete (hf);
7990%! end_unwind_protect
7991*/
7992
7993void
7994axes::properties::calc_ticks_and_lims (array_property& lims,
7995 array_property& ticks,
7996 array_property& mticks,
7997 bool limmode_is_auto,
7998 bool tickmode_is_auto,
7999 bool minortickvaluesmode_is_auto,
8000 bool is_logscale,
8001 bool method_is_padded,
8002 bool method_is_tight)
8003{
8004 if (lims.get ().isempty ())
8005 return;
8006
8007 double lo = (lims.get ().matrix_value ())(0);
8008 double hi = (lims.get ().matrix_value ())(1);
8009
8010 double lo_lim = lo;
8011 double hi_lim = hi;
8012 bool is_negative = lo < 0 && hi < 0;
8013
8014 // FIXME: should this be checked for somewhere else? (i.e., set{x,y,z}lim)
8015 if (hi < lo)
8016 std::swap (hi, lo);
8017
8018 if (is_logscale)
8019 {
8020 if (is_negative)
8021 {
8022 double tmp = hi;
8023 hi = std::log10 (-lo);
8024 lo = std::log10 (-tmp);
8025 }
8026 else
8027 {
8028 hi = std::log10 (hi);
8029 lo = std::log10 (lo);
8030 }
8031 }
8032
8033 Matrix tmp_ticks;
8034 if (tickmode_is_auto)
8035 {
8036 double tick_sep;
8037
8038 if (is_logscale)
8039 {
8040 if (! (octave::math::isinf (hi) || octave::math::isinf (lo)))
8041 tick_sep = 1; // Tick is every order of magnitude (bug #39449)
8042 else
8043 tick_sep = 0;
8044 }
8045 else
8046 tick_sep = calc_tick_sep (lo, hi);
8047
8048 double i1 = std::floor (lo / tick_sep);
8049 double i2 = std::ceil (hi / tick_sep);
8050
8051 if (limmode_is_auto)
8052 {
8053 Matrix tmp_lims (1, 2);
8054
8055 if (! method_is_padded && ! method_is_tight)
8056 {
8057 // Adjust limits to include min and max ticks
8058 tmp_lims(0) = std::min (tick_sep * i1, lo);
8059 tmp_lims(1) = std::max (tick_sep * i2, hi);
8060 }
8061 else
8062 {
8063 tmp_lims(0) = lo;
8064 tmp_lims(1) = hi;
8065
8066 // adjust min and max ticks to be within limits
8067 if (i1*tick_sep < lo)
8068 i1++;
8069 if (i2*tick_sep > hi && i2 > i1)
8070 i2--;
8071 }
8072
8073 if (is_logscale)
8074 {
8075 tmp_lims(0) = std::pow (10., tmp_lims(0));
8076 tmp_lims(1) = std::pow (10., tmp_lims(1));
8077
8078 if (tmp_lims(0) <= 0)
8079 tmp_lims(0) = std::pow (10., lo);
8080
8081 if (is_negative)
8082 {
8083 double tmp = tmp_lims(0);
8084 tmp_lims(0) = -tmp_lims(1);
8085 tmp_lims(1) = -tmp;
8086 }
8087 }
8088
8089 lims = tmp_lims;
8090 }
8091 else
8092 {
8093 // adjust min and max ticks to be within limits
8094 if (i1*tick_sep < lo)
8095 i1++;
8096 if (i2*tick_sep > hi && i2 > i1)
8097 i2--;
8098 }
8099
8100 tmp_ticks = Matrix (1, i2-i1+1);
8101 for (int i = 0; i <= static_cast<int> (i2-i1); i++)
8102 {
8103 tmp_ticks(i) = tick_sep * (i+i1);
8104 if (is_logscale)
8105 tmp_ticks(i) = std::pow (10., tmp_ticks(i));
8106 }
8107 if (is_logscale && is_negative)
8108 {
8109 Matrix rev_ticks (1, i2-i1+1);
8110 rev_ticks = -tmp_ticks;
8111 for (int i = 0; i <= static_cast<int> (i2-i1); i++)
8112 tmp_ticks(i) = rev_ticks(i2-i1-i);
8113 }
8114
8115 ticks = tmp_ticks;
8116 }
8117 else
8118 tmp_ticks = ticks.get ().matrix_value ();
8119
8120 octave_idx_type n_ticks = tmp_ticks.numel ();
8121 if (n_ticks < 2)
8122 return;
8123
8124 // minor ticks between, above, and below min and max ticks
8125 if (minortickvaluesmode_is_auto)
8126 {
8127 const int MAX_MINOR_TICKS = 1000;
8128 int n = (is_logscale ? 8 : 4);
8129 double mult_below = (is_logscale ? tmp_ticks(1) / tmp_ticks(0) : 1);
8130 double mult_above = (is_logscale ?
8131 tmp_ticks(n_ticks-1) / tmp_ticks(n_ticks-2) : 1);
8132
8133 double d_below = (tmp_ticks(1) - tmp_ticks(0)) / mult_below / (n+1);
8134 int n_below = static_cast<int> (std::floor ((tmp_ticks(0)-lo_lim)
8135 / d_below));
8136 if (n_below < 0)
8137 n_below = 0;
8138 else if (n_below > MAX_MINOR_TICKS)
8139 n_below = MAX_MINOR_TICKS;
8140
8141 int n_between = n * (n_ticks - 1);
8142 double d_above = (tmp_ticks(n_ticks-1) - tmp_ticks(n_ticks-2))
8143 * mult_above
8144 / (n+1);
8145 int n_above = static_cast<int> (std::floor ((hi_lim-tmp_ticks(n_ticks-1))
8146 / d_above));
8147 if (n_above < 0)
8148 n_above = 0;
8149 else if (n_above > MAX_MINOR_TICKS)
8150 n_above = MAX_MINOR_TICKS;
8151
8152 Matrix tmp_mticks (1, n_below + n_between + n_above);
8153 for (int i = 0; i < n_below; i++)
8154 tmp_mticks(i) = tmp_ticks(0) - (n_below-i) * d_below;
8155 for (int i = 0; i < n_ticks-1; i++)
8156 {
8157 double d = (tmp_ticks(i+1) - tmp_ticks(i)) / (n + 1);
8158 for (int j = 0; j < n; j++)
8159 tmp_mticks(n_below+n*i+j) = tmp_ticks(i) + d * (j+1);
8160 }
8161 for (int i = 0; i < n_above; i++)
8162 tmp_mticks(n_below+n_between+i) = tmp_ticks(n_ticks-1) + (i + 1)
8163 * d_above;
8164
8165 mticks = tmp_mticks;
8166 }
8167}
8168
8169/*
8170%!test <*45356>
8171%! hf = figure ("visible", "off");
8172%! unwind_protect
8173%! plot (1:10);
8174%! xlim ([4.75, 8.5]);
8175%! tics = get (gca, "xtick");
8176%! assert (tics, [5 6 7 8]);
8177%! unwind_protect_cleanup
8178%! close (hf);
8179%! end_unwind_protect
8180*/
8181
8182void
8183axes::properties::calc_ticklabels (const array_property& ticks,
8184 any_property& labels, bool logscale,
8185 const bool is_origin,
8186 const int other_axislocation,
8187 const array_property& axis_lims)
8188{
8189 Matrix values = ticks.get ().matrix_value ();
8190 Matrix lims = axis_lims.get ().matrix_value ();
8191 Cell c (dim_vector (values.numel (), 1)); // column vector for ML compat.
8192 std::ostringstream os;
8193
8194 // omit tick labels depending on location of other axis
8195 ColumnVector omit_ticks (3, octave::numeric_limits<double>::NaN ());
8196 if (get_is2D () && is_origin)
8197 {
8198 if (other_axislocation == 0)
8199 {
8200 omit_ticks(0) = octave::math::max (octave::math::min (0., lims(1)),
8201 lims(0));
8202 }
8203 else if (other_axislocation == 1)
8204 omit_ticks(0) = lims(1);
8205 else if (other_axislocation == -1)
8206 omit_ticks(0) = lims(0);
8207 if (is_box ())
8208 {
8209 omit_ticks(1) = lims(0);
8210 omit_ticks(2) = lims(1);
8211 }
8212 }
8213
8214 if (logscale)
8215 {
8216 double significand;
8217 double exponent;
8218 bool is_2digit_exp = false;
8219
8220 for (int i = 0; i < values.numel (); i++)
8221 {
8222 double exp = std::abs (std::log10 (values(i)));
8223 if (exp >= 10.0)
8224 {
8225 is_2digit_exp = true;
8226 break;
8227 }
8228 }
8229
8230 for (int i = 0; i < values.numel (); i++)
8231 {
8232 bool omit_tick = false;
8233 for (int i_omit = 0; i_omit < omit_ticks.numel (); i_omit++)
8234 if (values(i) == omit_ticks(i_omit))
8235 omit_tick = true;
8236 if (omit_tick)
8237 {
8238 c(i) = "";
8239 continue;
8240 }
8241
8242 if (values(i) < 0.0)
8243 exponent = std::floor (std::log10 (-values(i)));
8244 else
8245 exponent = std::floor (std::log10 (values(i)));
8246 significand = values(i) * std::pow (10.0, -exponent);
8247
8248 os.precision (5);
8249 os.str ("");
8250 if ((std::abs (significand) - 1) >
8251 10*std::numeric_limits<double>::epsilon())
8252 os << significand << 'x';
8253 else if (significand < 0)
8254 os << '-';
8255
8256 os << "10^{";
8257
8258 if (exponent < 0.0)
8259 {
8260 os << '-';
8261 exponent = -exponent;
8262 }
8263 if (exponent < 10.0 && is_2digit_exp)
8264 os << '0';
8265 os << exponent << '}';
8266
8267 if (m_ticklabelinterpreter.is ("latex"))
8268 c(i) = "$" + os.str () + "$";
8269 else
8270 c(i) = os.str ();
8271 }
8272 }
8273 else
8274 {
8275 for (int i = 0; i < values.numel (); i++)
8276 {
8277 bool omit_tick = false;
8278 for (int i_omit = 0; i_omit < omit_ticks.numel (); i_omit++)
8279 if (values(i) == omit_ticks(i_omit))
8280 omit_tick = true;
8281 if (omit_tick)
8282 c(i) = "";
8283 else
8284 {
8285 os.str ("");
8286 // FIXME: Code should probably call out to display routines
8287 // within Octave, rather than hack things up with C++ library.
8288 // In particular, this fails for values much less than 1 where
8289 // 4 significant digits will be preceded by zeros making the
8290 // overall field length large. For example, pi/1000.
8291 if (std::abs (values(i)) < 1.0)
8292 os.precision (4);
8293 else
8294 os.precision (5);
8295 os << values(i);
8296 c(i) = os.str ();
8297 }
8298 }
8299 }
8300
8301 labels = c;
8302}
8303
8304Matrix
8305axes::properties::get_ticklabel_extents (const Matrix& ticks,
8306 const string_vector& ticklabels,
8307 const Matrix& limits)
8308{
8309 Matrix ext (1, 2, 0.0);
8310 double wmax, hmax;
8311 double dpr = device_pixel_ratio (get___myhandle__ ());
8312 wmax = hmax = 0.0;
8313 int n = std::min (ticklabels.numel (), ticks.numel ());
8314 for (int i = 0; i < n; i++)
8315 {
8316 double val = ticks(i);
8317 if (limits(0) <= val && val <= limits(1))
8318 {
8319 std::string label (ticklabels(i));
8320 label.erase (0, label.find_first_not_of (' '));
8321 label = label.substr (0, label.find_last_not_of (' ')+1);
8322
8323 if (m_txt_renderer.ok ())
8324 {
8325 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
8326
8327 octave::autolock guard (gh_mgr.graphics_lock ());
8328
8329 ext = m_txt_renderer.get_extent (label, 0.0,
8330 get_ticklabelinterpreter ());
8331
8332 wmax = std::max (wmax, ext(0) / dpr);
8333 hmax = std::max (hmax, ext(1) / dpr);
8334 }
8335 else
8336 {
8337 // FIXME: find a better approximation
8338 double fsize = get ("fontsize").double_value ();
8339 int len = label.length ();
8340
8341 wmax = std::max (wmax, 0.5*fsize*len);
8342 hmax = fsize;
8343 }
8344 }
8345 }
8346
8347 ext(0) = wmax;
8348 ext(1) = hmax;
8349 return ext;
8350}
8351
8352void
8353get_children_limits (double& min_val, double& max_val,
8354 double& min_pos, double& max_neg,
8355 const Matrix& kids, char limit_type)
8356{
8357 octave_idx_type n = kids.numel ();
8358
8359 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
8360
8361 switch (limit_type)
8362 {
8363 case 'x':
8364 for (octave_idx_type i = 0; i < n; i++)
8365 {
8366 graphics_object go = gh_mgr.get_object (kids(i));
8367
8368 if (go.is_xliminclude ())
8369 {
8370 octave_value lim = go.get_xlim ();
8371
8372 check_limit_vals (min_val, max_val, min_pos, max_neg, lim);
8373 }
8374 }
8375 break;
8376
8377 case 'y':
8378 for (octave_idx_type i = 0; i < n; i++)
8379 {
8380 graphics_object go = gh_mgr.get_object (kids(i));
8381
8382 if (go.is_yliminclude ())
8383 {
8384 octave_value lim = go.get_ylim ();
8385
8386 check_limit_vals (min_val, max_val, min_pos, max_neg, lim);
8387 }
8388 }
8389 break;
8390
8391 case 'z':
8392 for (octave_idx_type i = 0; i < n; i++)
8393 {
8394 graphics_object go = gh_mgr.get_object (kids(i));
8395
8396 if (go.is_zliminclude ())
8397 {
8398 octave_value lim = go.get_zlim ();
8399
8400 check_limit_vals (min_val, max_val, min_pos, max_neg, lim);
8401 }
8402 }
8403 break;
8404
8405 case 'c':
8406 for (octave_idx_type i = 0; i < n; i++)
8407 {
8408 graphics_object go = gh_mgr.get_object (kids(i));
8409
8410 if (go.is_climinclude ())
8411 {
8412 octave_value lim = go.get_clim ();
8413
8414 check_limit_vals (min_val, max_val, min_pos, max_neg, lim);
8415 }
8416 }
8417 break;
8418
8419 case 'a':
8420 for (octave_idx_type i = 0; i < n; i++)
8421 {
8422 graphics_object go = gh_mgr.get_object (kids(i));
8423
8424 if (go.is_aliminclude ())
8425 {
8426 octave_value lim = go.get_alim ();
8427
8428 check_limit_vals (min_val, max_val, min_pos, max_neg, lim);
8429 }
8430 }
8431 break;
8432
8433 default:
8434 break;
8435 }
8436}
8437
8438static std::set<double> updating_axis_limits;
8439
8440void
8441axes::update_axis_limits (const std::string& axis_type,
8442 const graphics_handle& h)
8443{
8444 if (updating_axis_limits.find (get_handle ().value ())
8445 != updating_axis_limits.end ())
8446 return;
8447
8448 Matrix kids = Matrix (1, 1, h.value ());
8449
8450 double min_val = octave::numeric_limits<double>::Inf ();
8451 double max_val = -octave::numeric_limits<double>::Inf ();
8452 double min_pos = octave::numeric_limits<double>::Inf ();
8453 double max_neg = -octave::numeric_limits<double>::Inf ();
8454
8455 char update_type = 0;
8456
8457 Matrix limits (1, 2);
8458 double val;
8459
8460#define FIX_LIMITS \
8461 val = limits(0); \
8462 if (octave::math::isfinite (val)) \
8463 min_val = val; \
8464 val = limits(1); \
8465 if (octave::math::isfinite (val)) \
8466 max_val = val;
8467
8468 if (axis_type == "xdata" || axis_type == "xscale"
8469 || axis_type == "xlimmode" || axis_type == "xliminclude"
8470 || axis_type == "xlim")
8471 {
8472 limits = m_properties.get_xlim ().matrix_value ();
8473 FIX_LIMITS;
8474
8475 update_type = 'x';
8476 if (m_properties.xlimmode_is ("auto"))
8477 {
8478 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'x');
8479
8480 std::string method = m_properties.get_xlimitmethod ();
8481 limits = m_properties.get_axis_limits (min_val, max_val,
8482 min_pos, max_neg,
8483 m_properties.xscale_is ("log"),
8484 method);
8485 }
8486 else
8487 m_properties.check_axis_limits (limits, kids,
8488 m_properties.xscale_is ("log"),
8489 update_type);
8490 }
8491 else if (axis_type == "ydata" || axis_type == "yscale"
8492 || axis_type == "ylimmode" || axis_type == "yliminclude"
8493 || axis_type == "ylim")
8494 {
8495 limits = m_properties.get_ylim ().matrix_value ();
8496 FIX_LIMITS;
8497
8498 update_type = 'y';
8499 if (m_properties.ylimmode_is ("auto"))
8500 {
8501 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'y');
8502
8503 std::string method = m_properties.get_ylimitmethod ();
8504 limits = m_properties.get_axis_limits (min_val, max_val,
8505 min_pos, max_neg,
8506 m_properties.yscale_is ("log"),
8507 method);
8508 }
8509 else
8510 m_properties.check_axis_limits (limits, kids,
8511 m_properties.yscale_is ("log"),
8512 update_type);
8513 }
8514 else if (axis_type == "zdata" || axis_type == "zscale"
8515 || axis_type == "zlimmode" || axis_type == "zliminclude"
8516 || axis_type == "zlim")
8517 {
8518 limits = m_properties.get_zlim ().matrix_value ();
8519 FIX_LIMITS;
8520
8521 update_type = 'z';
8522 if (m_properties.zlimmode_is ("auto"))
8523 {
8524 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'z');
8525
8526 m_properties.set_has3Dkids ((max_val - min_val) >
8527 std::numeric_limits<double>::epsilon ());
8528
8529 std::string method = m_properties.get_zlimitmethod ();
8530 limits = m_properties.get_axis_limits (min_val, max_val,
8531 min_pos, max_neg,
8532 m_properties.zscale_is ("log"),
8533 method);
8534 }
8535 else
8536 {
8537 // FIXME: get_children_limits is only needed here in order to know
8538 // if there are 3-D children. Is there a way to avoid this call?
8539 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'z');
8540
8541 m_properties.set_has3Dkids ((max_val - min_val) >
8542 std::numeric_limits<double>::epsilon ());
8543
8544 m_properties.check_axis_limits (limits, kids,
8545 m_properties.zscale_is ("log"),
8546 update_type);
8547 }
8548 }
8549 else if (axis_type == "cdata" || axis_type == "climmode"
8550 || axis_type == "cdatamapping" || axis_type == "climinclude"
8551 || axis_type == "clim")
8552 {
8553 if (m_properties.climmode_is ("auto"))
8554 {
8555 limits = m_properties.get_clim ().matrix_value ();
8556 FIX_LIMITS;
8557
8558 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'c');
8559
8560 if (min_val > max_val)
8561 {
8562 min_val = min_pos = 0;
8563 max_val = 1;
8564 }
8565 else if (min_val == max_val)
8566 {
8567 max_val = min_val + 1;
8568 min_val -= 1;
8569 }
8570
8571 limits(0) = min_val;
8572 limits(1) = max_val;
8573
8574 update_type = 'c';
8575 }
8576 }
8577 else if (axis_type == "alphadata" || axis_type == "alimmode"
8578 || axis_type == "alphadatamapping" || axis_type == "aliminclude"
8579 || axis_type == "alim")
8580 {
8581 if (m_properties.alimmode_is ("auto"))
8582 {
8583 limits = m_properties.get_alim ().matrix_value ();
8584 FIX_LIMITS;
8585
8586 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'a');
8587
8588 if (min_val > max_val)
8589 {
8590 min_val = min_pos = 0;
8591 max_val = 1;
8592 }
8593 else if (min_val == max_val)
8594 max_val = min_val + 1;
8595
8596 limits(0) = min_val;
8597 limits(1) = max_val;
8598
8599 update_type = 'a';
8600 }
8601 }
8602
8603#undef FIX_LIMITS
8604
8605 octave::unwind_protect_var<std::set<double>>
8606 restore_var (updating_axis_limits);
8607
8608 updating_axis_limits.insert (get_handle ().value ());
8609 bool is_auto;
8610
8611 switch (update_type)
8612 {
8613 case 'x':
8614 is_auto = m_properties.xlimmode_is ("auto");
8615 m_properties.set_xlim (limits);
8616 if (is_auto)
8617 m_properties.set_xlimmode ("auto");
8618 m_properties.update_xlim ();
8619 break;
8620
8621 case 'y':
8622 is_auto = m_properties.ylimmode_is ("auto");
8623 m_properties.set_ylim (limits);
8624 if (is_auto)
8625 m_properties.set_ylimmode ("auto");
8626 m_properties.update_ylim ();
8627 break;
8628
8629 case 'z':
8630 is_auto = m_properties.zlimmode_is ("auto");
8631 m_properties.set_zlim (limits);
8632 if (is_auto)
8633 m_properties.set_zlimmode ("auto");
8634 m_properties.update_zlim ();
8635 break;
8636
8637 case 'c':
8638 m_properties.set_clim (limits);
8639 m_properties.set_climmode ("auto");
8640 break;
8641
8642 case 'a':
8643 m_properties.set_alim (limits);
8644 m_properties.set_alimmode ("auto");
8645 break;
8646
8647 default:
8648 break;
8649 }
8650
8651 m_properties.update_transform ();
8652}
8653
8654// FIXME: This function is called repeatedly while the axes are being set up.
8655// There is probably some way to make this more efficient.
8656
8657void
8658axes::update_axis_limits (const std::string& axis_type)
8659{
8660 if ((updating_axis_limits.find (get_handle ().value ())
8661 != updating_axis_limits.end ())
8662 || (updating_aspectratios.find (get_handle ().value ())
8663 != updating_aspectratios.end ()))
8664 return;
8665
8666 Matrix kids = m_properties.get_all_children ();
8667
8668 double min_val = octave::numeric_limits<double>::Inf ();
8669 double max_val = -octave::numeric_limits<double>::Inf ();
8670 double min_pos = octave::numeric_limits<double>::Inf ();
8671 double max_neg = -octave::numeric_limits<double>::Inf ();
8672
8673 char update_type = 0;
8674
8675 Matrix limits;
8676
8677 if (axis_type == "xdata" || axis_type == "xscale"
8678 || axis_type == "xlimmode" || axis_type == "xliminclude"
8679 || axis_type == "xlim")
8680 {
8681 update_type = 'x';
8682 if (m_properties.xlimmode_is ("auto"))
8683 {
8684 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'x');
8685
8686 std::string method = m_properties.get_xlimitmethod ();
8687 limits = m_properties.get_axis_limits (min_val, max_val,
8688 min_pos, max_neg,
8689 m_properties.xscale_is ("log"),
8690 method);
8691 }
8692 else
8693 {
8694 limits = m_properties.get_xlim ().matrix_value ();
8695 m_properties.check_axis_limits (limits, kids,
8696 m_properties.xscale_is ("log"),
8697 update_type);
8698 if (axis_type == "xscale")
8699 update_type = 'x';
8700 }
8701 }
8702 else if (axis_type == "ydata" || axis_type == "yscale"
8703 || axis_type == "ylimmode" || axis_type == "yliminclude"
8704 || axis_type == "ylim")
8705 {
8706 update_type = 'y';
8707 if (m_properties.ylimmode_is ("auto"))
8708 {
8709 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'y');
8710
8711 std::string method = m_properties.get_ylimitmethod ();
8712 limits = m_properties.get_axis_limits (min_val, max_val,
8713 min_pos, max_neg,
8714 m_properties.yscale_is ("log"),
8715 method);
8716 }
8717 else
8718 {
8719 limits = m_properties.get_ylim ().matrix_value ();
8720 m_properties.check_axis_limits (limits, kids,
8721 m_properties.yscale_is ("log"),
8722 update_type);
8723 if (axis_type == "yscale")
8724 update_type = 'y';
8725 }
8726 }
8727 else if (axis_type == "zdata" || axis_type == "zscale"
8728 || axis_type == "zlimmode" || axis_type == "zliminclude"
8729 || axis_type == "zlim")
8730 {
8731 update_type = 'z';
8732 if (m_properties.zlimmode_is ("auto"))
8733 {
8734 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'z');
8735
8736 m_properties.set_has3Dkids ((max_val - min_val) >
8737 std::numeric_limits<double>::epsilon ());
8738
8739 // FIXME: How to correctly handle (positive or negative) log scale?
8740 if ((! octave::math::isfinite (min_val)
8741 || ! octave::math::isfinite (max_val))
8742 && ! m_properties.zscale_is ("log"))
8743 min_val = max_val = 0.;
8744
8745 std::string method = m_properties.get_zlimitmethod ();
8746 limits = m_properties.get_axis_limits (min_val, max_val,
8747 min_pos, max_neg,
8748 m_properties.zscale_is ("log"),
8749 method);
8750 }
8751 else
8752 {
8753 // FIXME: get_children_limits is only needed here in order to know
8754 // if there are 3-D children. Is there a way to avoid this call?
8755 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'z');
8756
8757 m_properties.set_has3Dkids ((max_val - min_val) >
8758 std::numeric_limits<double>::epsilon ());
8759
8760 limits = m_properties.get_zlim ().matrix_value ();
8761 m_properties.check_axis_limits (limits, kids,
8762 m_properties.zscale_is ("log"),
8763 update_type);
8764 if (axis_type == "zscale")
8765 update_type = 'z';
8766 }
8767 }
8768 else if (axis_type == "cdata" || axis_type == "climmode"
8769 || axis_type == "cdatamapping" || axis_type == "climinclude"
8770 || axis_type == "clim")
8771 {
8772 if (m_properties.climmode_is ("auto"))
8773 {
8774 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'c');
8775
8776 if (min_val > max_val)
8777 {
8778 min_val = min_pos = 0;
8779 max_val = 1;
8780 }
8781 else if (min_val == max_val)
8782 {
8783 max_val = min_val + 1;
8784 min_val -= 1;
8785 }
8786
8787 limits.resize (1, 2);
8788
8789 limits(0) = min_val;
8790 limits(1) = max_val;
8791
8792 update_type = 'c';
8793 }
8794
8795 }
8796 else if (axis_type == "alphadata" || axis_type == "alimmode"
8797 || axis_type == "alphadatamapping" || axis_type == "aliminclude"
8798 || axis_type == "alim")
8799 {
8800 if (m_properties.alimmode_is ("auto"))
8801 {
8802 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'a');
8803
8804 if (min_val > max_val)
8805 {
8806 min_val = min_pos = 0;
8807 max_val = 1;
8808 }
8809 else if (min_val == max_val)
8810 max_val = min_val + 1;
8811
8812 limits.resize (1, 2);
8813
8814 limits(0) = min_val;
8815 limits(1) = max_val;
8816
8817 update_type = 'a';
8818 }
8819
8820 }
8821
8822 octave::unwind_protect_var<std::set<double>>
8823 restore_var (updating_axis_limits);
8824
8825 updating_axis_limits.insert (get_handle ().value ());
8826 bool is_auto;
8827
8828 switch (update_type)
8829 {
8830 case 'x':
8831 is_auto = m_properties.xlimmode_is ("auto");
8832 m_properties.set_xlim (limits);
8833 if (is_auto)
8834 m_properties.set_xlimmode ("auto");
8835 m_properties.update_xlim ();
8836 break;
8837
8838 case 'y':
8839 is_auto = m_properties.ylimmode_is ("auto");
8840 m_properties.set_ylim (limits);
8841 if (is_auto)
8842 m_properties.set_ylimmode ("auto");
8843 m_properties.update_ylim ();
8844 break;
8845
8846 case 'z':
8847 is_auto = m_properties.zlimmode_is ("auto");
8848 m_properties.set_zlim (limits);
8849 if (is_auto)
8850 m_properties.set_zlimmode ("auto");
8851 m_properties.update_zlim ();
8852 break;
8853
8854 case 'c':
8855 m_properties.set_clim (limits);
8856 m_properties.set_climmode ("auto");
8857 break;
8858
8859 case 'a':
8860 m_properties.set_alim (limits);
8861 m_properties.set_alimmode ("auto");
8862 break;
8863
8864 default:
8865 break;
8866 }
8867
8868 m_properties.update_transform ();
8869}
8870
8871inline double
8872force_in_range (double x, double lower, double upper)
8873{
8874 if (x < lower)
8875 return lower;
8876 else if (x > upper)
8877 return upper;
8878 else
8879 return x;
8880}
8881
8882static Matrix
8883do_zoom (double val, double factor, const Matrix& lims, bool is_logscale)
8884{
8885 Matrix new_lims = lims;
8886
8887 double lo = lims(0);
8888 double hi = lims(1);
8889
8890 bool is_negative = lo < 0 && hi < 0;
8891
8892 if (is_logscale)
8893 {
8894 if (is_negative)
8895 {
8896 double tmp = hi;
8897 hi = std::log10 (-lo);
8898 lo = std::log10 (-tmp);
8899 val = std::log10 (-val);
8900 }
8901 else
8902 {
8903 hi = std::log10 (hi);
8904 lo = std::log10 (lo);
8905 val = std::log10 (val);
8906 }
8907 }
8908
8909 // Perform the zooming
8910 lo = val + (lo - val) / factor;
8911 hi = val + (hi - val) / factor;
8912
8913 if (is_logscale)
8914 {
8915 if (is_negative)
8916 {
8917 double tmp = -std::pow (10.0, hi);
8918 hi = -std::pow (10.0, lo);
8919 lo = tmp;
8920 }
8921 else
8922 {
8923 lo = std::pow (10.0, lo);
8924 hi = std::pow (10.0, hi);
8925 }
8926 }
8927
8928 new_lims(0) = lo;
8929 new_lims(1) = hi;
8930
8931 return new_lims;
8932}
8933
8934void
8935axes::properties::zoom_about_point (const std::string& mode,
8936 double x, double y, double factor,
8937 bool push_to_zoom_stack)
8938{
8939 // FIXME: Do we need error checking here?
8940 Matrix xlims = get_xlim ().matrix_value ();
8941 Matrix ylims = get_ylim ().matrix_value ();
8942
8943 // Get children axes limits
8944 Matrix kids = get_children ();
8945 double minx = octave::numeric_limits<double>::Inf ();
8946 double maxx = -octave::numeric_limits<double>::Inf ();
8947 double min_pos_x = octave::numeric_limits<double>::Inf ();
8948 double max_neg_x = -octave::numeric_limits<double>::Inf ();
8949 get_children_limits (minx, maxx, min_pos_x, max_neg_x, kids, 'x');
8950
8951 double miny = octave::numeric_limits<double>::Inf ();
8952 double maxy = -octave::numeric_limits<double>::Inf ();
8953 double min_pos_y = octave::numeric_limits<double>::Inf ();
8954 double max_neg_y = -octave::numeric_limits<double>::Inf ();
8955 get_children_limits (miny, maxy, min_pos_y, max_neg_y, kids, 'y');
8956
8957 xlims = do_zoom (x, factor, xlims, xscale_is ("log"));
8958 ylims = do_zoom (y, factor, ylims, yscale_is ("log"));
8959
8960 zoom (mode, xlims, ylims, push_to_zoom_stack);
8961}
8962
8963void
8964axes::properties::zoom (const std::string& mode, double factor,
8965 bool push_to_zoom_stack)
8966{
8967 // FIXME: Do we need error checking here?
8968 Matrix xlims = get_xlim ().matrix_value ();
8969 Matrix ylims = get_ylim ().matrix_value ();
8970
8971 double x = (xlims(0) + xlims(1)) / 2;
8972 double y = (ylims(0) + ylims(1)) / 2;
8973
8974 zoom_about_point (mode, x, y, factor, push_to_zoom_stack);
8975}
8976
8977void
8978axes::properties::push_zoom_stack ()
8979{
8980 if (m_zoom_stack.empty ())
8981 {
8982 m_zoom_stack.push_front (m_xlimmode.get ());
8983 m_zoom_stack.push_front (m_xlim.get ());
8984 m_zoom_stack.push_front (m_ylimmode.get ());
8985 m_zoom_stack.push_front (m_ylim.get ());
8986 m_zoom_stack.push_front (m_zlimmode.get ());
8987 m_zoom_stack.push_front (m_zlim.get ());
8988 m_zoom_stack.push_front (m_view.get ());
8989 }
8990}
8991
8992void
8993axes::properties::zoom (const std::string& mode,
8994 const Matrix& xl, const Matrix& yl,
8995 bool push_to_zoom_stack)
8996{
8997 if (xl(0) == xl(1) || yl(0) == yl(1))
8998 {
8999 warning ("invalid zoom region");
9000 return;
9001 }
9002
9003 if (push_to_zoom_stack)
9004 push_zoom_stack ();
9005
9006 if (mode == "horizontal" || mode == "both")
9007 {
9008 m_xlim = xl;
9009 m_xlimmode = "manual";
9010 }
9011
9012 if (mode == "vertical" || mode == "both")
9013 {
9014 m_ylim = yl;
9015 m_ylimmode = "manual";
9016 }
9017
9018 update_transform ();
9019
9020 if (mode == "horizontal" || mode == "both")
9021 update_xlim ();
9022
9023 if (mode == "vertical" || mode == "both")
9024 update_ylim ();
9025}
9026
9027static Matrix
9028do_translate (double x0, double x1, const Matrix& lims, bool is_logscale)
9029{
9030 Matrix new_lims = lims;
9031
9032 double lo = lims(0);
9033 double hi = lims(1);
9034
9035 bool is_negative = lo < 0 && hi < 0;
9036
9037 double delta;
9038
9039 if (is_logscale)
9040 {
9041 if (is_negative)
9042 {
9043 double tmp = hi;
9044 hi = std::log10 (-lo);
9045 lo = std::log10 (-tmp);
9046 x0 = -x0;
9047 x1 = -x1;
9048 }
9049 else
9050 {
9051 hi = std::log10 (hi);
9052 lo = std::log10 (lo);
9053 }
9054
9055 delta = std::log10 (x0) - std::log10 (x1);
9056 }
9057 else
9058 {
9059 delta = x0 - x1;
9060 }
9061
9062 // Perform the translation
9063 lo += delta;
9064 hi += delta;
9065
9066 if (is_logscale)
9067 {
9068 if (is_negative)
9069 {
9070 double tmp = -std::pow (10.0, hi);
9071 hi = -std::pow (10.0, lo);
9072 lo = tmp;
9073 }
9074 else
9075 {
9076 lo = std::pow (10.0, lo);
9077 hi = std::pow (10.0, hi);
9078 }
9079 }
9080
9081 new_lims(0) = lo;
9082 new_lims(1) = hi;
9083
9084 return new_lims;
9085}
9086
9087void
9088axes::properties::translate_view (const std::string& mode,
9089 double x0, double x1, double y0, double y1,
9090 bool push_to_zoom_stack)
9091{
9092 // FIXME: Do we need error checking here?
9093 Matrix xlims = get_xlim ().matrix_value ();
9094 Matrix ylims = get_ylim ().matrix_value ();
9095
9096 // Get children axes limits
9097 Matrix kids = get_children ();
9098 double minx = octave::numeric_limits<double>::Inf ();
9099 double maxx = -octave::numeric_limits<double>::Inf ();
9100 double min_pos_x = octave::numeric_limits<double>::Inf ();
9101 double max_neg_x = -octave::numeric_limits<double>::Inf ();
9102 get_children_limits (minx, maxx, min_pos_x, max_neg_x, kids, 'x');
9103
9104 double miny = octave::numeric_limits<double>::Inf ();
9105 double maxy = -octave::numeric_limits<double>::Inf ();
9106 double min_pos_y = octave::numeric_limits<double>::Inf ();
9107 double max_neg_y = -octave::numeric_limits<double>::Inf ();
9108 get_children_limits (miny, maxy, min_pos_y, max_neg_y, kids, 'y');
9109
9110 xlims = do_translate (x0, x1, xlims, xscale_is ("log"));
9111 ylims = do_translate (y0, y1, ylims, yscale_is ("log"));
9112
9113 zoom (mode, xlims, ylims, push_to_zoom_stack);
9114}
9115
9116void
9117axes::properties::pan (const std::string& mode, double factor,
9118 bool push_to_zoom_stack)
9119{
9120 // FIXME: Do we need error checking here?
9121 Matrix xlims = get_xlim ().matrix_value ();
9122 Matrix ylims = get_ylim ().matrix_value ();
9123
9124 double x0 = (xlims(0) + xlims(1)) / 2;
9125 double y0 = (ylims(0) + ylims(1)) / 2;
9126
9127 double x1 = x0 + (xlims(1) - xlims(0)) * factor;
9128 double y1 = y0 + (ylims(1) - ylims(0)) * factor;
9129
9130 translate_view (mode, x0, x1, y0, y1, push_to_zoom_stack);
9131}
9132
9133void
9134axes::properties::rotate3d (double x0, double x1, double y0, double y1,
9135 bool push_to_zoom_stack)
9136{
9137 if (push_to_zoom_stack)
9138 push_zoom_stack ();
9139
9140 Matrix bb = get_boundingbox (true);
9141 Matrix new_view = get_view ().matrix_value ();
9142
9143 // Compute new view angles
9144 new_view(0) += ((x0 - x1) * (180.0 / bb(2)));
9145 new_view(1) += ((y1 - y0) * (180.0 / bb(3)));
9146
9147 // Clipping
9148 new_view(1) = std::min (new_view(1), 90.0);
9149 new_view(1) = std::max (new_view(1), -90.0);
9150 if (new_view(0) > 180.0)
9151 new_view(0) -= 360.0;
9152 else if (new_view(0) < -180.0)
9153 new_view(0) += 360.0;
9154
9155 // Snapping
9156 double snapmargin = 1.0;
9157 for (int a = -90; a <= 90; a += 90)
9158 {
9159 if ((a - snapmargin) < new_view(1) && new_view(1) < (a + snapmargin))
9160 {
9161 new_view(1) = a;
9162 break;
9163 }
9164 }
9165
9166 for (int a = -180; a <= 180; a += 180)
9167 if ((a - snapmargin) < new_view(0) && new_view(0) < (a + snapmargin))
9168 {
9169 if (a == 180)
9170 new_view(0) = -180;
9171 else
9172 new_view(0) = a;
9173 break;
9174 }
9175
9176 // Update axes properties
9177 set_view (new_view);
9178}
9179
9180void
9181axes::properties::rotate_view (double delta_el, double delta_az,
9182 bool push_to_zoom_stack)
9183{
9184 if (push_to_zoom_stack)
9185 push_zoom_stack ();
9186
9187 Matrix v = get_view ().matrix_value ();
9188
9189 v(1) += delta_el;
9190
9191 if (v(1) > 90)
9192 v(1) = 90;
9193 if (v(1) < -90)
9194 v(1) = -90;
9195
9196 v(0) = fmod (v(0) - delta_az + 720, 360);
9197
9198 set_view (v);
9199
9200 update_transform ();
9201}
9202
9203void
9204axes::properties::unzoom ()
9205{
9206 if (m_zoom_stack.size () >= 7)
9207 {
9208 m_view = m_zoom_stack.front ();
9209 m_zoom_stack.pop_front ();
9210
9211 m_zlim = m_zoom_stack.front ();
9212 m_zoom_stack.pop_front ();
9213
9214 m_zlimmode = m_zoom_stack.front ();
9215 m_zoom_stack.pop_front ();
9216
9217 m_ylim = m_zoom_stack.front ();
9218 m_zoom_stack.pop_front ();
9219
9220 m_ylimmode = m_zoom_stack.front ();
9221 m_zoom_stack.pop_front ();
9222
9223 m_xlim = m_zoom_stack.front ();
9224 m_zoom_stack.pop_front ();
9225
9226 m_xlimmode = m_zoom_stack.front ();
9227 m_zoom_stack.pop_front ();
9228
9229 update_transform ();
9230
9231 update_xlim ();
9232 update_ylim ();
9233 update_zlim ();
9234
9235 update_view ();
9236 }
9237}
9238
9239void
9240axes::properties::update_handlevisibility ()
9241{
9242 if (! is_handle_visible ())
9243 {
9244 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
9245
9246 graphics_object go (gh_mgr.get_object (get___myhandle__ ()));
9247
9248 graphics_object fig (go.get_ancestor ("figure"));
9249 octave_value ca = fig.get ("currentaxes");
9250 if (! ca.isempty () && ca.double_value () == m___myhandle__)
9251 {
9252 octave::autolock guard (gh_mgr.graphics_lock ());
9253
9254 octave_value kids = fig.get ("children");
9255 if (kids.isempty ())
9256 fig.set ("currentaxes", Matrix ());
9257 else
9258 {
9259 NDArray kidsarray = kids.array_value ();
9260 fig.set ("currentaxes", kidsarray(0));
9261 }
9262 }
9263 }
9264
9265 base_properties::update_handlevisibility ();
9266}
9267
9268void
9269figure::properties::init_toolkit ()
9270{
9271 octave::gtk_manager& gtk_mgr = octave::__get_gtk_manager__ ();
9272
9273 m_toolkit = gtk_mgr.get_toolkit ();
9274}
9275
9276void
9277axes::properties::clear_zoom_stack (bool do_unzoom)
9278{
9279 std::size_t items_to_leave_on_stack = (do_unzoom ? 7 : 0);
9280
9281 while (m_zoom_stack.size () > items_to_leave_on_stack)
9282 m_zoom_stack.pop_front ();
9283
9284 if (do_unzoom)
9285 unzoom ();
9286}
9287
9288void
9289axes::properties::trigger_normals_calc ()
9290{
9291 // Find all patch (and surface) objects within axes
9292 std::list<graphics_object> children_list;
9293 std::list<graphics_object>::iterator children_list_iter;
9294 get_children_of_type ("patch", false, true, children_list);
9295 get_children_of_type ("surface", false, true, children_list);
9296
9297 // trigger normals calculation for these objects
9298 for (children_list_iter = children_list.begin ();
9299 children_list_iter != children_list.end (); children_list_iter++)
9300 {
9301 graphics_object kid = *children_list_iter;
9302 if (kid.isa ("patch"))
9303 {
9304 patch::properties& patch_props
9305 = dynamic_cast<patch::properties&> (kid.get_properties ());
9306 patch_props.update_normals (false);
9307 }
9308 else
9309 {
9310 surface::properties& surface_props
9311 = dynamic_cast<surface::properties&> (kid.get_properties ());
9312 surface_props.update_normals (false);
9313 }
9314 }
9315}
9316
9317void
9318axes::reset_default_properties ()
9319{
9320 // empty list of local defaults
9321 m_default_properties = property_list ();
9322
9323 // Save warning state of "Octave:deprecated-property"
9324 int state = toggle_warn ("Octave:deprecated-property", false);
9325
9326 // reset factory defaults
9327 remove_all_listeners ();
9328 set_defaults ("reset");
9329
9330 toggle_warn ("Octave:deprecated-property", true, state);
9331}
9332
9333void
9334axes::initialize (const graphics_object& go)
9335{
9336 base_graphics_object::initialize (go);
9337
9338 xinitialize (m_properties.get_title ());
9339 xinitialize (m_properties.get_xlabel ());
9340 xinitialize (m_properties.get_ylabel ());
9341 xinitialize (m_properties.get_zlabel ());
9342
9343 m_properties.sync_positions ();
9344}
9345
9346// ---------------------------------------------------------------------
9347
9348Matrix
9349line::properties::compute_xlim () const
9350{
9351 Matrix m (1, 4);
9352
9353 m(0) = m_xdata.min_val ();
9354 m(1) = m_xdata.max_val ();
9355 m(2) = m_xdata.min_pos ();
9356 m(3) = m_xdata.max_neg ();
9357
9358 return m;
9359}
9360
9361Matrix
9362line::properties::compute_ylim () const
9363{
9364 Matrix m (1, 4);
9365
9366 m(0) = m_ydata.min_val ();
9367 m(1) = m_ydata.max_val ();
9368 m(2) = m_ydata.min_pos ();
9369 m(3) = m_ydata.max_neg ();
9370
9371 return m;
9372}
9373
9374// ---------------------------------------------------------------------
9375
9376Matrix
9377text::properties::get_data_position () const
9378{
9379 Matrix pos = get_position ().matrix_value ();
9380
9381 if (! units_is ("data"))
9382 pos = convert_text_position (pos, *this, get_units (), "data");
9383
9384 return pos;
9385}
9386
9387Matrix
9388text::properties::get_extent_matrix (bool rotated) const
9389{
9390 // FIXME: Should this function also add the (x,y) base position?
9391 Matrix ext = m_extent.get ().matrix_value ();
9392
9393 if (rotated && get_rotation () != 0)
9394 {
9395 double rot = get_rotation () * 4.0 * atan (1.0) / 180;
9396 double x0 = ext(0) * cos (rot) - ext(1) * sin (rot);
9397 double x1 = x0;
9398 double y0 = ext(0) * sin (rot) + ext(1) * cos (rot);
9399 double y1 = y0;
9400
9401 double tmp = (ext(0)+ext(2)) * cos (rot) - ext(1) * sin (rot);
9402 x0 = std::min (x0, tmp);
9403 x1 = std::max (x1, tmp);
9404 tmp = (ext(0)+ext(2)) * sin (rot) + ext(1) * cos (rot);
9405 y0 = std::min (y0, tmp);
9406 y1 = std::max (y1, tmp);
9407
9408 tmp = (ext(0)+ext(2)) * cos (rot) - (ext(1)+ext(3)) * sin (rot);
9409 x0 = std::min (x0, tmp);
9410 x1 = std::max (x1, tmp);
9411 tmp = (ext(0)+ext(2)) * sin (rot) + (ext(1)+ext(3)) * cos (rot);
9412 y0 = std::min (y0, tmp);
9413 y1 = std::max (y1, tmp);
9414
9415 tmp = ext(0) * cos (rot) - (ext(1)+ext(3)) * sin (rot);
9416 x0 = std::min (x0, tmp);
9417 x1 = std::max (x1, tmp);
9418 tmp = ext(0) * sin (rot) + (ext(1)+ext(3)) * cos (rot);
9419 y0 = std::min (y0, tmp);
9420 y1 = std::max (y1, tmp);
9421
9422 ext(0) = x0;
9423 ext(1) = y0;
9424 ext(2) = x1 - x0;
9425 ext(3) = y1 - y0;
9426 }
9427
9428 return ext;
9429}
9430
9432text::properties::get_extent () const
9433{
9434 // FIXME: This doesn't work right for 3-D plots.
9435 // (It doesn't in Matlab either, at least not in version 6.5.)
9436 Matrix m = get_extent_matrix (true);
9437 Matrix pos = get_position ().matrix_value ();
9438 Matrix p = convert_text_position (pos, *this, get_units (), "pixels");
9439
9440 m(0) += p(0);
9441 m(1) += p(1);
9442
9443 Matrix bbox = convert_text_position (m, *this, "pixels", get_units ());
9444
9445 double dpr = device_pixel_ratio (get___myhandle__ ());
9446
9447 for (octave_idx_type ii = 0; ii < bbox.numel (); ii++)
9448 bbox(ii) = bbox(ii) / dpr;
9449
9450 return bbox;
9451}
9452
9453void
9454text::properties::set_fontunits (const octave_value& val)
9455{
9456 caseless_str old_fontunits = get_fontunits ();
9457
9458 if (m_fontunits.set (val, true))
9459 {
9460 update_fontunits (old_fontunits);
9461 mark_modified ();
9462 }
9463}
9464
9465void
9466text::properties::update_fontunits (const caseless_str& old_units)
9467{
9468 caseless_str new_units = get_fontunits ();
9469 double parent_height = 0;
9470 double fontsz = get_fontsize ();
9471
9472 if (new_units == "normalized" || old_units == "normalized")
9473 {
9474 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
9475
9476 graphics_object go (gh_mgr.get_object (get___myhandle__ ()));
9477
9478 graphics_object ax (go.get_ancestor ("axes"));
9479
9480 parent_height = ax.get_properties ().get_boundingbox (true).elem (3);
9481 }
9482
9483 fontsz = convert_font_size (fontsz, old_units, new_units, parent_height);
9484
9485 set_fontsize (octave_value (fontsz));
9486}
9487
9488void
9489text::properties::update_font ()
9490{
9491 double dpr = device_pixel_ratio (get___myhandle__ ());
9492
9493 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
9494
9495 octave::autolock guard (gh_mgr.graphics_lock ());
9496
9497 m_txt_renderer.set_font (get ("fontname").string_value (),
9498 get ("fontweight").string_value (),
9499 get ("fontangle").string_value (),
9500 get ("__fontsize_points__").double_value () * dpr);
9501
9502 m_txt_renderer.set_anti_aliasing (is_fontsmoothing ());
9503
9504 Matrix c = get_color_rgb ();
9505 if (! c.isempty ())
9506 m_txt_renderer.set_color (c);
9507
9508}
9509
9510void
9511text::properties::update_text_extent ()
9512{
9513 int halign = 0;
9514 int valign = 0;
9515
9516 if (horizontalalignment_is ("center"))
9517 halign = 1;
9518 else if (horizontalalignment_is ("right"))
9519 halign = 2;
9520
9521 if (verticalalignment_is ("middle"))
9522 valign = 1;
9523 else if (verticalalignment_is ("top"))
9524 valign = 2;
9525 else if (verticalalignment_is ("baseline"))
9526 valign = 3;
9527 else if (verticalalignment_is ("cap"))
9528 valign = 4;
9529
9530 Matrix bbox;
9531
9532 // FIXME: string should be parsed only when modified, for efficiency
9533
9534 octave_value string_prop = get_string ();
9535
9536 string_vector sv = string_prop.string_vector_value ();
9537
9538 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
9539
9540 octave::autolock guard (gh_mgr.graphics_lock ());
9541
9542 m_txt_renderer.text_to_pixels (sv.join ("\n"), m_pixels, bbox,
9543 halign, valign, 0.0, get_interpreter ());
9544 // The bbox is relative to the text's position. We'll leave it that
9545 // way, because get_position does not return valid results when the
9546 // text is first constructed. Conversion to proper coordinates is
9547 // performed in get_extent.
9548 set_extent (bbox);
9549
9550 if (__autopos_tag___is ("xlabel") || __autopos_tag___is ("ylabel")
9551 || __autopos_tag___is ("zlabel") || __autopos_tag___is ("title"))
9552 update_autopos ("sync");
9553}
9554
9555void
9556text::properties::request_autopos ()
9557{
9558 if (__autopos_tag___is ("xlabel") || __autopos_tag___is ("ylabel")
9559 || __autopos_tag___is ("zlabel") || __autopos_tag___is ("title"))
9560 update_autopos (get___autopos_tag__ ());
9561}
9562
9563void
9564text::properties::update_units ()
9565{
9566 if (! units_is ("data"))
9567 {
9568 set_xliminclude ("off");
9569 set_yliminclude ("off");
9570 set_zliminclude ("off");
9571 }
9572
9573 Matrix pos = get_position ().matrix_value ();
9574
9575 pos = convert_text_position (pos, *this, m_cached_units, get_units ());
9576
9577 // FIXME: if the current axes view is 2-D, then one should probably drop
9578 // the z-component of "pos" and leave "zliminclude" to "off".
9579
9580 bool autopos = positionmode_is ("auto");
9581
9582 set_position (pos);
9583
9584 if (autopos)
9585 set_positionmode ("auto");
9586
9587 if (units_is ("data"))
9588 {
9589 set_xliminclude ("on");
9590 set_yliminclude ("on");
9591 // FIXME: see above
9592 set_zliminclude ("off");
9593 }
9594
9595 m_cached_units = get_units ();
9596}
9597
9598double
9599text::properties::get___fontsize_points__ (double box_pix_height) const
9600{
9601 double fontsz = get_fontsize ();
9602 double parent_height = box_pix_height;
9603
9604 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
9605
9606 graphics_object go (gh_mgr.get_object (get___myhandle__ ()));
9607
9608 if (fontunits_is ("normalized") && parent_height <= 0)
9609 {
9610 graphics_object ax (go.get_ancestor ("axes"));
9611
9612 parent_height = ax.get_properties ().get_boundingbox (true).elem (3);
9613 }
9614
9615 return convert_font_size (fontsz, get_fontunits (), "points", parent_height);
9616}
9617
9618// ---------------------------------------------------------------------
9619
9621image::properties::get_color_data () const
9622{
9623 return convert_cdata (*this, get_cdata (), cdatamapping_is ("scaled"), 3);
9624}
9625
9626// ---------------------------------------------------------------------
9627
9628void
9629light::initialize (const graphics_object& go)
9630{
9631 base_graphics_object::initialize (go);
9632
9633 // trigger normals calculation for the respective children of this axes object
9634 axes::properties& parent_axes_prop
9635 = dynamic_cast<axes::properties&> (go.get_ancestor ("axes").get_properties ());
9636 parent_axes_prop.trigger_normals_calc ();
9637}
9638
9639void
9640light::properties::update_visible ()
9641{
9642 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
9643
9644 graphics_object go = gh_mgr.get_object (get___myhandle__ ());
9645
9646 axes::properties& ax_props = dynamic_cast<axes::properties&>
9647 (go.get_ancestor ("axes").get_properties ());
9648 if (is_visible ())
9649 ax_props.increase_num_lights ();
9650 else
9651 ax_props.decrease_num_lights ();
9652}
9653
9654// ---------------------------------------------------------------------
9655
9656bool
9657patch::properties::get_do_lighting () const
9658{
9659 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
9660
9661 graphics_object go = gh_mgr.get_object (get___myhandle__ ());
9662
9663 axes::properties& ax_props = dynamic_cast<axes::properties&>
9664 (go.get_ancestor ("axes").get_properties ());
9665
9666 return (ax_props.get_num_lights () > 0);
9667}
9668
9670patch::properties::get_color_data () const
9671{
9672 octave_value fvc = get_facevertexcdata ();
9673 if (fvc.is_undefined () || fvc.isempty ())
9674 return Matrix ();
9675 else
9676 return convert_cdata (*this, fvc, cdatamapping_is ("scaled"), 2);
9677}
9678
9679static bool updating_patch_data = false;
9680
9681void
9682patch::properties::update_fvc ()
9683{
9684 if (updating_patch_data)
9685 return;
9686
9687 Matrix xd = get_xdata ().matrix_value ();
9688 Matrix yd = get_ydata ().matrix_value ();
9689 Matrix zd = get_zdata ().matrix_value ();
9690 NDArray cd = get_cdata ().array_value ();
9691
9692 m_bad_data_msg = "";
9693 if (xd.dims () != yd.dims ()
9694 || (xd.dims () != zd.dims () && ! zd.isempty ()))
9695 {
9696 m_bad_data_msg = "x/y/zdata must have the same dimensions";
9697 return;
9698 }
9699
9700 octave_idx_type nv = xd.rows ();
9701 octave_idx_type nf = xd.columns ();
9702 octave_idx_type ncv = cd.rows ();
9703 octave_idx_type ncf = cd.columns ();
9704 if ((ncf > 1 || ncv > 1) && ((ncf != nf) || (ncv != 1 && ncv != nv)))
9705 {
9706 m_bad_data_msg = "cdata does not match number of faces "
9707 "or number of vertices per face";
9708 return;
9709 }
9710
9711 bool isRGB = false;
9712 if (cd.ndims () == 3)
9713 isRGB = true;
9714
9715 // Faces and Vertices
9716 dim_vector dv;
9717 bool is3D = false;
9718 if (nv == 1 && nf > 1)
9719 {
9720 nv = nf;
9721 nf = 1;
9722 xd = xd.as_column ();
9723 yd = yd.as_column ();
9724 zd = zd.as_column ();
9725 }
9726
9727 bool pervertex = false;
9728 if (ncv == nv)
9729 pervertex = true;
9730
9731 dv(0) = nv * nf;
9732 if (zd.isempty ())
9733 dv(1) = 2;
9734 else
9735 {
9736 dv(1) = 3;
9737 is3D = true;
9738 }
9739
9740 Matrix vert (dv);
9741 Matrix idx (nf, nv);
9742
9743 Matrix fvc;
9744 if (pervertex)
9745 {
9746 // "facevertexcdata" holds color data per vertex
9747 fvc.resize (nv * nf, (isRGB ? 3 : 1));
9748 }
9749 else if (! cd.isempty ())
9750 {
9751 // "facevertexcdata" holds color data per face
9752 dv(0) = ncf;
9753 dv(1) = (isRGB ? 3 : 1);
9754 fvc = cd.reshape (dv);
9755 }
9756
9757 // create list of vertices from x/y/zdata
9758 // FIXME: It might be possible to share vertices between adjacent faces.
9759 octave_idx_type kk = 0;
9760 for (octave_idx_type jj = 0; jj < nf; jj++)
9761 {
9762 for (octave_idx_type ii = 0; ii < nv; ii++)
9763 {
9764 vert(kk, 0) = xd(ii, jj);
9765 vert(kk, 1) = yd(ii, jj);
9766 if (is3D)
9767 vert(kk, 2) = zd(ii, jj);
9768
9769 if (pervertex)
9770 {
9771 fvc(kk, 0) = cd(ii, jj, 0);
9772 if (isRGB)
9773 {
9774 fvc(kk, 1) = cd(ii, jj, 1);
9775 fvc(kk, 2) = cd(ii, jj, 2);
9776 }
9777 }
9778
9779 idx(jj, ii) = static_cast<double> (kk+1);
9780
9781 kk++;
9782 }
9783 }
9784
9785 // FIXME: shouldn't we update facevertexalphadata here ?
9786
9787 octave::unwind_protect_var<bool> restore_var (updating_patch_data, true);
9788
9789 m_faces.set (idx);
9790 m_vertices.set (vert);
9791 m_facevertexcdata.set (fvc);
9792
9793 updating_patch_data = false;
9794 update_data ();
9795}
9796
9797// core coplanar tester
9798bool
9800{
9801 // Accuracy note: this test will also accept single precision input (although
9802 // stored in double precision). This is because the error threshold is
9803 // sqrt(tol) = 1.5e-7.
9804 double tol = 100 * std::numeric_limits<double>::epsilon ();
9805 EIG eig (cov, false, false, true);
9806 ColumnVector ev = real (eig.eigenvalues ());
9807 return ev.min () <= tol * ev.max ();
9808}
9809
9810std::vector<octave_idx_type>
9811coplanar_partition (const Matrix& vert, const Matrix& idx,
9813{
9814 std::vector<octave_idx_type> coplanar_ends;
9815
9816 Matrix plane_pivot = Matrix (1, 3, 0.0);
9817 for (octave_idx_type i = 0; i < 3; i++)
9818 plane_pivot(0, i) = vert(idx(0, jj)-1, i);
9819
9820 Matrix fc = Matrix (0, 3, 0.0); // face corner vertex coordinates
9821 Matrix fa = Matrix (1, 3, 0.0); // for append face corner
9822 Matrix coor_cov = Matrix (3, 3, 0.0);
9823
9824 if (nc >= 5)
9825 {
9826 // Coplanar test that involves all points.
9827 // For nc == 4, this initial test is not beneficial at all.
9828 // If the probability of coplanar input is more than half, for
9829 // the best average performance, we should use nc >= 5.
9830 // Higher threshold is meaningful only when input is known to be
9831 // non-coplanar and nc is small.
9832
9833 fc.resize (nc - 1, 3);
9834 for (octave_idx_type j = 1; j < nc; j++)
9835 for (octave_idx_type i = 0; i < 3; i++)
9836 fc(j-1, i) = vert(idx(j, jj)-1, i) - plane_pivot(i);
9837
9838 coor_cov = fc.transpose () * fc;
9839 if (is_coplanar (coor_cov))
9840 {
9841 coplanar_ends.push_back (nc - 1);
9842 return coplanar_ends;
9843 }
9844 }
9845
9846 fc.resize (3, 3);
9847 octave_idx_type i_start = 1;
9848 octave_idx_type i_end = 2;
9849
9850 // Split the polygon into coplanar segments.
9851 // The first point is common corner of all planes.
9852 while (i_start < nc - 1)
9853 {
9854 i_end = i_start + 2;
9855 if (i_end > nc - 1)
9856 {
9857 coplanar_ends.push_back (nc - 1);
9858 break;
9859 }
9860
9861 // Algorithm: Start from 3 points, keep adding points until the point set
9862 // is no more in a plane. Record the coplanar point set, then advance
9863 // i_start.
9864
9865 // Prepare 1+3 points for coplanar test.
9866 // The first point is implicitly included.
9867 for (octave_idx_type j = 0; j < 3; j++)
9868 for (octave_idx_type i = 0; i < 3; i++)
9869 fc(j, i) = vert(idx(j+i_start, jj)-1, i) - plane_pivot(i);
9870
9871 // covariance matrix between coordinates of vertices
9872 coor_cov = fc.transpose () * fc;
9873
9874 while (true)
9875 {
9876 // coplanar test
9877 if (! is_coplanar (coor_cov))
9878 break;
9879
9880 i_end++;
9881 if (i_end > nc - 1)
9882 break;
9883
9884 // add a point to plane
9885 for (octave_idx_type i = 0; i < 3; i++)
9886 fa(0, i) = vert(idx(i_end, jj)-1, i) - plane_pivot(i);
9887 coor_cov += fa.transpose () * fa;
9888 }
9889
9890 i_start = i_end - 1;
9891 coplanar_ends.push_back (i_start);
9892 }
9893 return coplanar_ends;
9894}
9895
9896void
9897patch::properties::update_data ()
9898{
9899 if (updating_patch_data)
9900 return;
9901
9902 Matrix idx = get_faces ().matrix_value ().transpose ();
9903 Matrix vert = get_vertices ().matrix_value ();
9904 NDArray fvc = get_facevertexcdata ().array_value ();
9905
9906 octave_idx_type nfaces = idx.columns ();
9907 octave_idx_type nvert = vert.rows ();
9908
9909 // Check all vertices in faces are defined
9910 m_bad_data_msg = "";
9911 if (static_cast<double> (nvert) < idx.row_max ().max ())
9912 {
9913 m_bad_data_msg = R"(some vertices in "faces" property are undefined)";
9914 return;
9915 }
9916
9917 // Check if number of color values matches number of faces or vertices
9918 octave_idx_type nfvc = fvc.rows ();
9919 if (nfvc > 1 && nfvc != nfaces && nfvc != nvert)
9920 {
9921 m_bad_data_msg = "number of facevertexcdata values matches "
9922 "neither number of faces nor number of vertices";
9923 return;
9924 }
9925
9926 // Replace NaNs
9927 if (idx.any_element_is_inf_or_nan ())
9928 {
9929 for (octave_idx_type jj = 0; jj < idx.columns (); jj++)
9930 {
9931 double valid_vert = idx(0, jj);
9932 bool turn_valid = false;
9933 for (octave_idx_type ii = 0; ii < idx.rows (); ii++)
9934 {
9935 if (octave::math::isnan (idx(ii, jj)) || turn_valid)
9936 {
9937 idx(ii, jj) = valid_vert;
9938 turn_valid = true;
9939 }
9940 else
9941 valid_vert = idx(ii, jj);
9942 }
9943 }
9944 }
9945
9946 // check coplanarity for 3-D-faces with more than 3 corners
9947 int fcmax = idx.rows ();
9948 if (fcmax > 3 && vert.columns () > 2
9949 && ! (facecolor_is ("none") && edgecolor_is ("none")))
9950 {
9951 m_coplanar_last_idx.resize (idx.columns ());
9952 for (octave_idx_type jj = 0; jj < idx.columns (); jj++)
9953 {
9954 if (octave::math::isnan (idx(3, jj)))
9955 continue;
9956
9957 // find first element that is NaN to get number of corners
9958 octave_idx_type nc = 3;
9959 while (nc < fcmax && ! octave::math::isnan (idx(nc, jj)))
9960 nc++;
9961
9962 // If any of the corners is NaN or Inf, skip coplanar test.
9963 // FIXME: Add support for non-coplanar faces with unclosed contour.
9964 bool is_unclosed = false;
9965 for (octave_idx_type j = 0; j < nc; j++)
9966 {
9967 const octave_idx_type k = idx(j, jj) - 1;
9968 if (! (octave::math::isfinite (vert(k, 0))
9969 && octave::math::isfinite (vert(k, 1))
9970 && octave::math::isfinite (vert(k, 2))))
9971 {
9972 is_unclosed = true;
9973 break;
9974 }
9975 }
9976 if (is_unclosed)
9977 continue;
9978
9979 m_coplanar_last_idx[jj] = coplanar_partition (vert, idx, nc, jj);
9980 }
9981 }
9982 else
9983 m_coplanar_last_idx.resize (0);
9984
9985 // Build cdata
9987 NDArray cd;
9988 bool pervertex = false;
9989
9990 if (fvc.rows () == nfaces || fvc.rows () == 1)
9991 {
9992 // "facevertexcdata" holds color data per face or same color for all
9993 dv(0) = 1;
9994 dv(1) = fvc.rows ();
9995 dv(2) = fvc.columns ();
9996 cd = fvc.reshape (dv);
9997 }
9998 else if (! fvc.isempty ())
9999 {
10000 // "facevertexcdata" holds color data per vertex
10001 dv(0) = idx.rows ();
10002 dv(1) = nfaces;
10003 dv(2) = fvc.columns ();
10004 cd.resize (dv);
10005 pervertex = true;
10006 }
10007
10008 // Build x,y,zdata and eventually per vertex cdata
10009 Matrix xd (idx.dims ());
10010 Matrix yd (idx.dims ());
10011 Matrix zd;
10012 bool has_zd = false;
10013 if (vert.columns () > 2)
10014 {
10015 zd = Matrix (idx.dims ());
10016 has_zd = true;
10017 }
10018
10019 for (octave_idx_type jj = 0; jj < nfaces; jj++)
10020 {
10021 for (octave_idx_type ii = 0; ii < idx.rows (); ii++)
10022 {
10023 octave_idx_type row = static_cast<octave_idx_type> (idx(ii, jj)-1);
10024 xd(ii, jj) = vert(row, 0);
10025 yd(ii, jj) = vert(row, 1);
10026
10027 if (has_zd)
10028 zd(ii, jj) = vert(row, 2);
10029
10030 if (pervertex)
10031 for (int kk = 0; kk < fvc.columns (); kk++)
10032 cd(ii, jj, kk) = fvc(row, kk);
10033 }
10034 }
10035
10036 // Update normals
10037 update_normals (true);
10038
10039 octave::unwind_protect_var<bool> restore_var (updating_patch_data, true);
10040
10041 set_xdata (xd);
10042 set_ydata (yd);
10043 set_zdata (zd);
10044 set_cdata (cd);
10045}
10046
10047inline void
10048cross_product (double x1, double y1, double z1,
10049 double x2, double y2, double z2,
10050 double& x, double& y, double& z)
10051{
10052 x += (y1 * z2 - z1 * y2);
10053 y += (z1 * x2 - x1 * z2);
10054 z += (x1 * y2 - y1 * x2);
10055}
10056
10057void
10058patch::properties::calc_face_normals (Matrix& fn)
10059{
10060 Matrix v = get_vertices ().matrix_value ();
10061 Matrix f = get_faces ().matrix_value ();
10062
10063 bool is_3D = (v.columns () == 3); // 2-D or 3-D patches
10064 octave_idx_type num_f = f.rows (); // number of faces
10065 octave_idx_type max_nc = f.columns (); // max. number of polygon corners
10066
10067 // In which cases can we skip updating the normals?
10068 if (max_nc < 3)
10069 {
10070 fn = Matrix ();
10071 return;
10072 }
10073
10074 // Calculate normals for all faces
10075 octave_idx_type i1, i2, i3;
10076 octave_idx_type j1, j2;
10077 for (octave_idx_type i = 0; i < num_f; i++)
10078 {
10079 bool is_coplanar = true;
10080 if (m_coplanar_last_idx.size () > 0 && m_coplanar_last_idx[i].size () > 1)
10081 is_coplanar = false;
10082
10083 // get number of corners
10084 octave_idx_type nc = 3;
10085 if (max_nc > 3)
10086 {
10087 while (nc < max_nc && ! octave::math::isnan (f(i, nc)))
10088 nc++;
10089 }
10090
10091 RowVector fnc (3, 0.0);
10092 double& nx = fnc(0);
10093 double& ny = fnc(1);
10094 double& nz = fnc(2);
10095
10096 if (is_coplanar)
10097 {
10098 // fast way for coplanar polygons
10099 i1 = f(i, 0) - 1; i2 = f(i, 1) - 1; i3 = f(i, nc-1) - 1;
10100
10101 if (is_3D)
10103 (v(i3, 0) - v(i1, 0), v(i3, 1) - v(i1, 1), v(i3, 2) - v(i1, 2),
10104 v(i2, 0) - v(i1, 0), v(i2, 1) - v(i1, 1), v(i2, 2) - v(i1, 2),
10105 nx, ny, nz);
10106 else
10107 {
10108 nz = (v(i2, 0) - v(i1, 0)) * (v(i3, 1) - v(i1, 1)) -
10109 (v(i2, 1) - v(i1, 1)) * (v(i3, 0) - v(i1, 0));
10110 // 2-D vertices always point towards +z
10111 nz = (nz < 0) ? -nz : nz;
10112 }
10113 }
10114 else
10115 {
10116 // more general for non-planar polygons
10117
10118 // calculate face normal with Newell's method
10119 // https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal#Newell.27s_Method
10120
10121 j1 = nc - 1; j2 = 0;
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 for (octave_idx_type j = 1; j < nc; j++)
10129 {
10130 j1 = j-1; j2 = j;
10131 i1 = f(i, j1) - 1; i2 = f(i, j2) - 1;
10132
10133 nx += (v(i2, 1) - v(i1, 1)) * (v(i1, 2) + v(i2, 2));
10134 ny += (v(i2, 2) - v(i1, 2)) * (v(i1, 0) + v(i2, 0));
10135 nz += (v(i2, 0) - v(i1, 0)) * (v(i1, 1) + v(i2, 1));
10136 }
10137 }
10138
10139 // normalize normal vector
10140 double n_len = sqrt (nx*nx+ny*ny+nz*nz);
10141
10142 // assign normal to current face
10143 if ( n_len < std::numeric_limits<double>::epsilon () )
10144 for (octave_idx_type j = 0; j < 3; j++)
10145 fn(i, j) = 0.0;
10146 else
10147 for (octave_idx_type j = 0; j < 3; j++)
10148 fn(i, j) = fnc(j) / n_len;
10149 }
10150}
10151
10152void
10153patch::properties::update_face_normals (bool reset, bool force)
10154{
10155 if (updating_patch_data || ! facenormalsmode_is ("auto"))
10156 return;
10157
10158 if (force || ((facelighting_is ("flat") || edgelighting_is ("flat"))
10159 && get_do_lighting ()))
10160 {
10161 Matrix f = get_faces ().matrix_value ();
10162
10163 octave_idx_type num_f = f.rows (); // number of faces
10164 Matrix fn (num_f, 3, 0.0);
10165
10166 calc_face_normals (fn);
10167 m_facenormals = fn;
10168 }
10169 else if (reset)
10170 m_facenormals = Matrix ();
10171}
10172
10173void
10174patch::properties::update_vertex_normals (bool reset, bool force)
10175{
10176 if (updating_patch_data || ! vertexnormalsmode_is ("auto"))
10177 return;
10178
10179 if (force || ((facelighting_is ("gouraud") || facelighting_is ("phong")
10180 || edgelighting_is ("gouraud") || edgelighting_is ("phong"))
10181 && get_do_lighting ()))
10182 {
10183 Matrix v = get_vertices ().matrix_value ();
10184 Matrix f = get_faces ().matrix_value ();
10185
10186 octave_idx_type num_v = v.rows (); // number of vertices
10187 octave_idx_type num_f = f.rows (); // number of faces
10188 octave_idx_type max_nc = f.columns (); // max. number of polygon corners
10189
10190 // In which cases can we skip updating the normals?
10191 if (max_nc < 3)
10192 return;
10193
10194 // First step: Calculate the normals for all faces
10195 Matrix fn = get_facenormals ().matrix_value ();
10196 if ( fn.isempty () )
10197 {
10198 // calculate facenormals here
10199 fn = Matrix (num_f, 3, 0.0);
10200 calc_face_normals (fn);
10201 }
10202
10203 // Second step: assign normals to the respective vertices
10204
10205 // The following code collects the face normals for all faces adjacent to
10206 // each vertex. For this, a std::vector of length NUM_V (which might be
10207 // very large) is used so that memory is allocated from the heap rather
10208 // than the stack. Each element of this vector corresponds to one vertex
10209 // of the patch. The element itself is a variable length std::vector.
10210 // This second vector contains the face normals (of type RowVector) of
10211 // the adjacent faces.
10212 std::vector<std::vector<RowVector>> vec_vn (num_v);
10213 for (octave_idx_type i = 0; i < num_f; i++)
10214 {
10215 // get number of corners
10216 octave_idx_type nc = 3;
10217 if (max_nc > 3)
10218 {
10219 while (nc < max_nc && ! octave::math::isnan (f(i, nc)))
10220 nc++;
10221 }
10222
10223 for (octave_idx_type j = 0; j < nc; j++)
10224 vec_vn[static_cast<octave_idx_type> (f(i, j) - 1)].push_back (fn.row (i));
10225 }
10226
10227 // Third step: Calculate the normal for the vertices taking the average
10228 // of the normals determined from all adjacent faces
10229 Matrix vn (num_v, 3, 0.0);
10230 for (octave_idx_type i = 0; i < num_v; i++)
10231 {
10232 std::vector<RowVector>::iterator it = vec_vn[i].begin ();
10233
10234 // The normal of unused vertices is NaN.
10235 RowVector vn0 (3, octave_NaN);
10236
10237 if (it != vec_vn[i].end ())
10238 {
10239 // FIXME: Currently, the first vector also determines the
10240 // direction of the normal. How to determine the inner and outer
10241 // faces of all parts of the patch and point the normals outwards?
10242 // (Necessary for correct lighting with "backfacelighting" set to
10243 // "lit" or "unlit".) Matlab does not seem to do it correctly
10244 // either. So should we bother?
10245
10246 vn0 = *it;
10247
10248 for (++it; it != vec_vn[i].end (); ++it)
10249 {
10250 RowVector vn1 = *it;
10251 // Use sign of dot product to point vectors in a similar
10252 // direction before taking the average.
10253 double dir = (vn0(0)*vn1(0) + vn0(1)*vn1(1) + vn0(2)*vn1(2) < 0) ? -1 : 1;
10254 for (octave_idx_type j = 0; j < 3; j++)
10255 vn0(j) += dir * vn1(j);
10256 }
10257
10258 // normalize normal vector
10259 double n_len = sqrt (vn0(0)*vn0(0)+vn0(1)*vn0(1)+vn0(2)*vn0(2));
10260
10261 // save normal in matrix
10262 for (octave_idx_type j = 0; j < 3; j++)
10263 vn(i, j) = vn0(j)/n_len;
10264 }
10265 }
10266
10267 m_vertexnormals = vn;
10268 }
10269 else if (reset)
10270 m_vertexnormals = Matrix ();
10271}
10272
10273void
10274patch::initialize (const graphics_object& go)
10275{
10276 base_graphics_object::initialize (go);
10277
10278 // calculate normals for default data
10279 // This is done because the normals for the default data do not match
10280 // get(0, "DefaultPatchVertexNormals") in Matlab.
10281 m_properties.update_normals (true);
10282}
10283
10284
10285void
10286patch::reset_default_properties ()
10287{
10288 // empty list of local defaults
10289 m_default_properties = property_list ();
10290 xreset_default_properties (get_handle (), m_properties.factory_defaults ());
10291
10292 // calculate normals for default data
10293 // This is done because the normals for the default data do not match
10294 // get(0, "DefaultPatchVertexNormals") in Matlab.
10295 m_properties.update_normals (true);
10296}
10297
10298// ---------------------------------------------------------------------
10299
10301scatter::properties::get_color_data () const
10302{
10303 octave_value c = get_cdata ();
10304 if (c.is_undefined () || c.isempty ())
10305 return Matrix ();
10306 else
10307 return convert_cdata (*this, c, c.columns () == 1, 2);
10308}
10309
10310void
10311scatter::properties::update_data ()
10312{
10313 Matrix xd = get_xdata ().matrix_value ();
10314 Matrix yd = get_ydata ().matrix_value ();
10315 Matrix zd = get_zdata ().matrix_value ();
10316 Matrix cd = get_cdata ().matrix_value ();
10317 Matrix sd = get_sizedata ().matrix_value ();
10318
10319 m_bad_data_msg = "";
10320 if (xd.dims () != yd.dims ()
10321 || (xd.dims () != zd.dims () && ! zd.isempty ()))
10322 {
10323 m_bad_data_msg = "x/y/zdata must have the same dimensions";
10324 return;
10325 }
10326
10327 octave_idx_type x_rows = xd.rows ();
10328 octave_idx_type c_cols = cd.columns ();
10329 octave_idx_type c_rows = cd.rows ();
10330
10331 if (! cd.isempty () && (c_rows != 1 || c_cols != 3)
10332 && (c_rows != x_rows || (c_cols != 1 && c_cols != 3)))
10333 {
10334 m_bad_data_msg = "cdata must be an rgb triplet or have the same number "
10335 "of rows as X and one or three columns";
10336 return;
10337 }
10338
10339 octave_idx_type s_rows = sd.rows ();
10340 if (s_rows != 1 && s_rows != x_rows)
10341 {
10342 m_bad_data_msg = "sizedata must be a scalar or a vector with the same "
10343 "dimensions as X";
10344 return;
10345 }
10346}
10347
10348static bool updating_scatter_cdata = false;
10349
10350void
10351scatter::properties::update_color ()
10352{
10353 if (updating_scatter_cdata)
10354 return;
10355
10356 Matrix series_idx = get_seriesindex ().matrix_value ();
10357 if (series_idx.isempty ())
10358 return;
10359
10360 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
10361
10362 graphics_object go = gh_mgr.get_object (get___myhandle__ ());
10363
10364 axes::properties& parent_axes_prop
10365 = dynamic_cast<axes::properties&>
10366 (go.get_ancestor ("axes").get_properties ());
10367
10368 Matrix color_order = parent_axes_prop.get_colororder ().matrix_value ();
10369 octave_idx_type s = (static_cast<octave_idx_type> (series_idx(0)) - 1)
10370 % color_order.rows ();
10371
10372 Matrix color = Matrix (1, 3, 0.);
10373 color(0) = color_order(s, 0);
10374 color(1) = color_order(s, 1);
10375 color(2) = color_order(s, 2);
10376
10377 octave::unwind_protect_var<bool> restore_var (updating_scatter_cdata, true);
10378
10379 set_cdata (color);
10380 set_cdatamode ("auto");
10381}
10382
10383void
10384scatter::initialize (const graphics_object& go)
10385{
10386 base_graphics_object::initialize (go);
10387
10388 Matrix series_idx = m_properties.get_seriesindex ().matrix_value ();
10389 if (series_idx.isempty ())
10390 {
10391 // Increment series index counter in parent axes
10392 axes::properties& parent_axes_prop
10393 = dynamic_cast<axes::properties&>
10394 (go.get_ancestor ("axes").get_properties ());
10395
10396 if (! parent_axes_prop.nextplot_is ("add"))
10397 parent_axes_prop.set_nextseriesindex (1);
10398
10399 series_idx.resize (1, 1);
10400 series_idx(0) = parent_axes_prop.get_nextseriesindex ();
10401 m_properties.set_seriesindex (series_idx);
10402
10403 parent_axes_prop.set_nextseriesindex
10404 (parent_axes_prop.get_nextseriesindex () + 1);
10405 }
10406
10407 if (m_properties.cdatamode_is ("auto"))
10408 m_properties.update_color ();
10409}
10410
10411// ---------------------------------------------------------------------
10412
10414surface::properties::get_color_data () const
10415{
10416 return convert_cdata (*this, get_cdata (), cdatamapping_is ("scaled"), 3);
10417}
10418
10419bool
10420surface::properties::get_do_lighting () const
10421{
10422 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
10423
10424 graphics_object go = gh_mgr.get_object (get___myhandle__ ());
10425
10426 axes::properties& ax_prop = dynamic_cast<axes::properties&>
10427 (go.get_ancestor ("axes").get_properties ());
10428
10429 return (ax_prop.get_num_lights () > 0);
10430}
10431
10432void
10433surface::properties::update_face_normals (bool reset, bool force)
10434{
10435 if (! facenormalsmode_is ("auto"))
10436 return;
10437
10438 if (force || ((facelighting_is ("flat") || edgelighting_is ("flat"))
10439 && get_do_lighting ()))
10440 {
10441 Matrix x = get_xdata ().matrix_value ();
10442 Matrix y = get_ydata ().matrix_value ();
10443 Matrix z = get_zdata ().matrix_value ();
10444
10445 int p = z.columns ();
10446 int q = z.rows ();
10447
10448 // FIXME: There might be a cleaner way to do this. When data is changed
10449 // the update_xdata, update_ydata, update_zdata routines are called in a
10450 // serial fashion. Until the final call to update_zdata the matrices
10451 // will be of mismatched dimensions which can cause an out-of-bound
10452 // indexing in the code below. This one-liner prevents calculating
10453 // normals until dimensions match.
10454 if (x.columns () != p || y.rows () != q)
10455 return;
10456
10457 bool x_mat = (x.rows () == q);
10458 bool y_mat = (y.columns () == p);
10459
10460 NDArray n (dim_vector (q-1, p-1, 3), 1);
10461
10462 int i1, i2, j1, j2;
10463 i1 = i2 = 0;
10464 j1 = j2 = 0;
10465 double x0, x1, x2, x3, y0, y1, y2, y3, z0, z1, z2, z3;
10466 double x1m0, x2m1, x3m2, x0m3, y1m0, y2m1, y3m2, y0m3;
10467 double x1p0, x2p1, x3p2, x0p3, y1p0, y2p1, y3p2, y0p3;
10468 x3m2 = y0m3 = -1;
10469 x2m1 = x0m3 = y1m0 = y3m2 = 0;
10470 x1m0 = y2m1 = 1;
10471 x0p3 = y1p0 = 0;
10472 x1p0 = x3p2 = y2p1 = y0p3 = 1;
10473 x2p1 = y3p2 = 2;
10474
10475 for (int i = 0; i < p-1; i++)
10476 {
10477 i1 = i;
10478 i2 = i + 1;
10479
10480 for (int j = 0; j < q-1; j++)
10481 {
10482 j1 = j;
10483 j2 = j + 1;
10484
10485 if (x_mat || y_mat)
10486 {
10487 x0 = x(x_mat?j1:0, y_mat?i1:0);
10488 x1 = x(x_mat?j1:0, y_mat?i2:0);
10489 x2 = x(x_mat?j2:0, y_mat?i2:0);
10490 x3 = x(x_mat?j2:0, y_mat?i1:0);
10491 x1m0 = x1 - x0;
10492 x2m1 = x2 - x1;
10493 x3m2 = x3 - x2;
10494 x0m3 = x0 - x3;
10495 x1p0 = x1 + x0;
10496 x2p1 = x2 + x1;
10497 x3p2 = x3 + x2;
10498 x0p3 = x0 + x3;
10499 y0 = y(x_mat?j1:0, y_mat?i1:0);
10500 y1 = y(x_mat?j1:0, y_mat?i2:0);
10501 y2 = y(x_mat?j2:0, y_mat?i2:0);
10502 y3 = y(x_mat?j2:0, y_mat?i1:0);
10503 y1m0 = y1 - y0;
10504 y2m1 = y2 - y1;
10505 y3m2 = y3 - y2;
10506 y0m3 = y0 - y3;
10507 y1p0 = y1 + y0;
10508 y2p1 = y2 + y1;
10509 y3p2 = y3 + y2;
10510 y0p3 = y0 + y3;
10511 }
10512
10513 double& nx = n(j, i, 0);
10514 double& ny = n(j, i, 1);
10515 double& nz = n(j, i, 2);
10516
10517 z0 = z(j1, i1);
10518 z1 = z(j1, i2);
10519 z2 = z(j2, i2);
10520 z3 = z(j2, i1);
10521
10522 // calculate face normal with Newell's method
10523 // https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal#Newell.27s_Method
10524
10525 nx = y1m0 * (z1 + z0) + y2m1 * (z2 + z1)
10526 + y3m2 * (z3 + z2) + y0m3 * (z0 + z3);
10527 ny = (z1 - z0) * x1p0 + (z2 - z1) * x2p1
10528 + (z3 - z2) * x3p2 + (z0 - z3) * x0p3;
10529 nz = x1m0 * y1p0 + x2m1 * y2p1 + x3m2 * y3p2 + x0m3 * y0p3;
10530
10531 double d = std::max (std::max (fabs (nx), fabs (ny)), fabs (nz));
10532
10533 nx /= d;
10534 ny /= d;
10535 nz /= d;
10536 }
10537 }
10538 m_facenormals = n;
10539 }
10540 else if (reset)
10541 m_facenormals = Matrix ();
10542}
10543
10544void
10545surface::properties::update_vertex_normals (bool reset, bool force)
10546{
10547 if (! vertexnormalsmode_is ("auto"))
10548 return;
10549
10550 if (force || ((facelighting_is ("gouraud") || facelighting_is ("phong")
10551 || edgelighting_is ("gouraud") || edgelighting_is ("phong"))
10552 && get_do_lighting ()))
10553 {
10554 Matrix x = get_xdata ().matrix_value ();
10555 Matrix y = get_ydata ().matrix_value ();
10556 Matrix z = get_zdata ().matrix_value ();
10557
10558 int p = z.columns ();
10559 int q = z.rows ();
10560
10561 // FIXME: There might be a cleaner way to do this. When data is changed
10562 // the update_xdata, update_ydata, update_zdata routines are called in a
10563 // serial fashion. Until the final call to update_zdata the matrices
10564 // will be of mismatched dimensions which can cause an out-of-bound
10565 // indexing in the code below. This one-liner prevents calculating
10566 // normals until dimensions match.
10567 if (x.columns () != p || y.rows () != q)
10568 return;
10569
10570 NDArray n (dim_vector (q, p, 3), 0.0);
10571
10572 bool x_mat = (x.rows () == q);
10573 bool y_mat = (y.columns () == p);
10574
10575 int i1, i2, i3, j1, j2, j3;
10576 i1 = i2 = i3 = 0;
10577 j1 = j2 = j3 = 0;
10578
10579 for (int i = 0; i < p; i++)
10580 {
10581 if (y_mat)
10582 {
10583 i1 = i - 1;
10584 i2 = i;
10585 i3 = i + 1;
10586 }
10587
10588 for (int j = 0; j < q; j++)
10589 {
10590 if (x_mat)
10591 {
10592 j1 = j - 1;
10593 j2 = j;
10594 j3 = j + 1;
10595 }
10596
10597 double& nx = n(j, i, 0);
10598 double& ny = n(j, i, 1);
10599 double& nz = n(j, i, 2);
10600
10601 if ((j > 0) && (i > 0))
10602 // upper left quadrangle
10604 (x(j1, i-1)-x(j2, i), y(j-1, i1)-y(j, i2), z(j-1, i-1)-z(j, i),
10605 x(j2, i-1)-x(j1, i), y(j, i1)-y(j-1, i2), z(j, i-1)-z(j-1, i),
10606 nx, ny, nz);
10607
10608 if ((j > 0) && (i < (p -1)))
10609 // upper right quadrangle
10611 (x(j1, i+1)-x(j2, i), y(j-1, i3)-y(j, i2), z(j-1, i+1)-z(j, i),
10612 x(j1, i)-x(j2, i+1), y(j-1, i2)-y(j, i3), z(j-1, i)-z(j, i+1),
10613 nx, ny, nz);
10614
10615 if ((j < (q - 1)) && (i > 0))
10616 // lower left quadrangle
10618 (x(j2, i-1)-x(j3, i), y(j, i1)-y(j+1, i2), z(j, i-1)-z(j+1, i),
10619 x(j3, i-1)-x(j2, i), y(j+1, i1)-y(j, i2), z(j+1, i-1)-z(j, i),
10620 nx, ny, nz);
10621
10622 if ((j < (q - 1)) && (i < (p -1)))
10623 // lower right quadrangle
10625 (x(j3, i)-x(j2, i+1), y(j+1, i2)-y(j, i3), z(j+1, i)-z(j, i+1),
10626 x(j3, i+1)-x(j2, i), y(j+1, i3)-y(j, i2), z(j+1, i+1)-z(j, i),
10627 nx, ny, nz);
10628
10629 double d = -std::max (std::max (fabs (nx), fabs (ny)), fabs (nz));
10630
10631 nx /= d;
10632 ny /= d;
10633 nz /= d;
10634 }
10635 }
10636 m_vertexnormals = n;
10637 }
10638 else if (reset)
10639 m_vertexnormals = Matrix ();
10640}
10641
10642DEFMETHOD (__update_normals__, interp, args, ,
10643 doc: /* -*- texinfo -*-
10644@deftypefn {} {} __update_normals__ (@var{h})
10645Update FaceNormals and VertexNormals of the patch or surface referred to by
10646@var{h}.
10647
10648@end deftypefn */)
10649{
10650 gh_manager& gh_mgr = interp.get_gh_manager ();
10651
10652 octave::autolock guard (gh_mgr.graphics_lock ());
10653
10654 if (args.length () != 1)
10655 print_usage ();
10656
10657 octave_value val = args(0);
10658
10659 graphics_object go = gh_mgr.get_object (val);
10660
10661 if (go.isa ("surface"))
10662 {
10663 surface::properties& props
10664 = dynamic_cast<surface::properties&> (go.get_properties ());
10665 props.update_normals (false, true);
10666 }
10667 else if (go.isa ("patch"))
10668 {
10669 patch::properties& props
10670 = dynamic_cast<patch::properties&> (go.get_properties ());
10671 props.update_normals (false, true);
10672 }
10673 else
10674 error ("__update_normals__: "
10675 "H must be a handle to a valid surface or patch object.");
10676
10677 return ovl ();
10678}
10679
10680/*
10681%!test
10682%! hf = figure ("visible", "off");
10683%! unwind_protect
10684%! Z = peaks ();
10685%! hs = surf (Z, "facelighting", "none");
10686%! assert (isempty (get (hs, "vertexnormals")));
10687%! assert (isempty (get (hs, "facenormals")));
10688%! __update_normals__ (hs);
10689%! assert (! isempty (get (hs, "vertexnormals")));
10690%! assert (! isempty (get (hs, "facenormals")));
10691%! unwind_protect_cleanup
10692%! close (hf);
10693%! end_unwind_protect
10694
10695%!test
10696%! hf = figure ("visible", "off");
10697%! unwind_protect
10698%! hp = patch ("facelighting", "none");
10699%! assert (isempty (get (hp, "vertexnormals")));
10700%! assert (isempty (get (hp, "facenormals")));
10701%! __update_normals__ (hp);
10702%! assert (! isempty (get (hp, "vertexnormals")));
10703%! assert (! isempty (get (hp, "facenormals")));
10704%! unwind_protect_cleanup
10705%! close (hf);
10706%! end_unwind_protect
10707*/
10708
10709// ---------------------------------------------------------------------
10710
10711void
10712hggroup::properties::remove_child (const graphics_handle& h, bool from_root)
10713{
10714 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
10715
10716 graphics_object go = gh_mgr.get_object (h);
10717
10718 if (! from_root && go.isa ("light") && go.get_properties ().is_visible ())
10719 {
10720 axes::properties& ax_props
10721 = dynamic_cast<axes::properties&> (go.get_ancestor ("axes").get_properties ());
10722 ax_props.decrease_num_lights ();
10723 }
10724 base_properties::remove_child (h, from_root);
10725 update_limits ();
10726}
10727
10728void
10729hggroup::properties::adopt (const graphics_handle& h)
10730{
10731 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
10732
10733 graphics_object go = gh_mgr.get_object (h);
10734
10735 if (go.isa ("light") && go.get_properties ().is_visible ())
10736 {
10737 axes::properties& ax_props
10738 = dynamic_cast<axes::properties&> (go.get_ancestor ("axes").get_properties ());
10739 ax_props.increase_num_lights ();
10740 }
10741 base_properties::adopt (h);
10742 update_limits (h);
10743}
10744
10745void
10746hggroup::properties::update_limits () const
10747{
10748 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
10749
10750 graphics_object go = gh_mgr.get_object (m___myhandle__);
10751
10752 if (go)
10753 {
10754 go.update_axis_limits ("xlim");
10755 go.update_axis_limits ("ylim");
10756 go.update_axis_limits ("zlim");
10757 go.update_axis_limits ("clim");
10758 go.update_axis_limits ("alim");
10759 }
10760}
10761
10762void
10763hggroup::properties::update_limits (const graphics_handle& h) const
10764{
10765 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
10766
10767 graphics_object go = gh_mgr.get_object (m___myhandle__);
10768
10769 if (go)
10770 {
10771 go.update_axis_limits ("xlim", h);
10772 go.update_axis_limits ("ylim", h);
10773 go.update_axis_limits ("zlim", h);
10774 go.update_axis_limits ("clim", h);
10775 go.update_axis_limits ("alim", h);
10776 }
10777}
10778
10779static bool updating_hggroup_limits = false;
10780
10781void
10782hggroup::update_axis_limits (const std::string& axis_type,
10783 const graphics_handle& h)
10784{
10785 if (updating_hggroup_limits)
10786 return;
10787
10788 Matrix kids = Matrix (1, 1, h.value ());
10789
10790 double min_val = octave::numeric_limits<double>::Inf ();
10791 double max_val = -octave::numeric_limits<double>::Inf ();
10792 double min_pos = octave::numeric_limits<double>::Inf ();
10793 double max_neg = -octave::numeric_limits<double>::Inf ();
10794
10795 Matrix limits;
10796 double val;
10797
10798 char update_type = 0;
10799
10800 if (axis_type == "xlim" || axis_type == "xliminclude")
10801 {
10802 limits = m_properties.get_xlim ().matrix_value ();
10803 update_type = 'x';
10804 }
10805 else if (axis_type == "ylim" || axis_type == "yliminclude")
10806 {
10807 limits = m_properties.get_ylim ().matrix_value ();
10808 update_type = 'y';
10809 }
10810 else if (axis_type == "zlim" || axis_type == "zliminclude")
10811 {
10812 limits = m_properties.get_zlim ().matrix_value ();
10813 update_type = 'z';
10814 }
10815 else if (axis_type == "clim" || axis_type == "climinclude")
10816 {
10817 limits = m_properties.get_clim ().matrix_value ();
10818 update_type = 'c';
10819 }
10820 else if (axis_type == "alim" || axis_type == "aliminclude")
10821 {
10822 limits = m_properties.get_alim ().matrix_value ();
10823 update_type = 'a';
10824 }
10825
10826 if (limits.numel () == 4)
10827 {
10828 val = limits(0);
10829 if (octave::math::isfinite (val))
10830 min_val = val;
10831 val = limits(1);
10832 if (octave::math::isfinite (val))
10833 max_val = val;
10834 val = limits(2);
10835 if (octave::math::isfinite (val))
10836 min_pos = val;
10837 val = limits(3);
10838 if (octave::math::isfinite (val))
10839 max_neg = val;
10840 }
10841 else
10842 {
10843 limits.resize (1, 4);
10844 limits(0) = min_val;
10845 limits(1) = max_val;
10846 limits(2) = min_pos;
10847 limits(3) = max_neg;
10848 }
10849
10850 get_children_limits (min_val, max_val, min_pos, max_neg, kids, update_type);
10851
10852 octave::unwind_protect_var<bool> restore_var (updating_hggroup_limits, true);
10853
10854 if (limits(0) != min_val || limits(1) != max_val
10855 || limits(2) != min_pos || limits(3) != max_neg)
10856 {
10857 limits(0) = min_val;
10858 limits(1) = max_val;
10859 limits(2) = min_pos;
10860 limits(3) = max_neg;
10861
10862 switch (update_type)
10863 {
10864 case 'x':
10865 m_properties.set_xlim (limits);
10866 break;
10867
10868 case 'y':
10869 m_properties.set_ylim (limits);
10870 break;
10871
10872 case 'z':
10873 m_properties.set_zlim (limits);
10874 break;
10875
10876 case 'c':
10877 m_properties.set_clim (limits);
10878 break;
10879
10880 case 'a':
10881 m_properties.set_alim (limits);
10882 break;
10883
10884 default:
10885 break;
10886 }
10887
10888 graphics_handle hg = m_properties.get___myhandle__ ();
10889 base_graphics_object::update_axis_limits (axis_type, hg);
10890 }
10891}
10892
10893void
10894hggroup::update_axis_limits (const std::string& axis_type)
10895{
10896 if (updating_hggroup_limits)
10897 return;
10898
10899 Matrix kids = m_properties.get_children ();
10900
10901 double min_val = octave::numeric_limits<double>::Inf ();
10902 double max_val = -octave::numeric_limits<double>::Inf ();
10903 double min_pos = octave::numeric_limits<double>::Inf ();
10904 double max_neg = -octave::numeric_limits<double>::Inf ();
10905
10906 char update_type = 0;
10907
10908 if (axis_type == "xlim" || axis_type == "xliminclude")
10909 {
10910 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'x');
10911
10912 update_type = 'x';
10913 }
10914 else if (axis_type == "ylim" || axis_type == "yliminclude")
10915 {
10916 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'y');
10917
10918 update_type = 'y';
10919 }
10920 else if (axis_type == "zlim" || axis_type == "zliminclude")
10921 {
10922 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'z');
10923
10924 update_type = 'z';
10925 }
10926 else if (axis_type == "clim" || axis_type == "climinclude")
10927 {
10928 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'c');
10929
10930 update_type = 'c';
10931 }
10932 else if (axis_type == "alim" || axis_type == "aliminclude")
10933 {
10934 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'a');
10935
10936 update_type = 'a';
10937 }
10938
10939 octave::unwind_protect_var<bool> restore_var (updating_hggroup_limits, true);
10940
10941 Matrix limits (1, 4);
10942
10943 limits(0) = min_val;
10944 limits(1) = max_val;
10945 limits(2) = min_pos;
10946 limits(3) = max_neg;
10947
10948 switch (update_type)
10949 {
10950 case 'x':
10951 m_properties.set_xlim (limits);
10952 break;
10953
10954 case 'y':
10955 m_properties.set_ylim (limits);
10956 break;
10957
10958 case 'z':
10959 m_properties.set_zlim (limits);
10960 break;
10961
10962 case 'c':
10963 m_properties.set_clim (limits);
10964 break;
10965
10966 case 'a':
10967 m_properties.set_alim (limits);
10968 break;
10969
10970 default:
10971 break;
10972 }
10973
10974 base_graphics_object::update_axis_limits (axis_type);
10975}
10976
10977// ---------------------------------------------------------------------
10978
10979void
10980uicontextmenu::properties::update_beingdeleted ()
10981{
10982 // Clear the uicontextmenu property of dependent objects
10983 if (m_beingdeleted.is ("on"))
10984 {
10985 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
10986
10987 std::list<graphics_handle> lst = get_dependent_obj_list ();
10988
10989 for (auto& hobj : lst)
10990 {
10991 graphics_object go = gh_mgr.get_object (hobj);
10992
10993 if (go.valid_object ()
10994 && go.get ("contextmenu") == get___myhandle__ ())
10995 go.set ("contextmenu", Matrix ());
10996 }
10997 }
10998}
10999
11000/*
11001## Test deletion/reset of uicontextmenu
11002%!test
11003%! hf = figure ("visible", "off");
11004%! hax = axes ("parent", hf);
11005%! unwind_protect
11006%! hctx1 = uicontextmenu ("parent", hf);
11007%! hctx2 = uicontextmenu ("parent", hf);
11008%! set (hf, "uicontextmenu", hctx2);
11009%! set (hax, "uicontextmenu", hctx2);
11010%! assert (get (hf, "uicontextmenu"), hctx2);
11011%! assert (get (hax, "uicontextmenu"), hctx2);
11012%! assert (get (hf, "children"), [hctx2; hctx1; hax]);
11013%! delete (hctx2);
11014%! assert (get (hf, "uicontextmenu"), []);
11015%! assert (get (hax, "uicontextmenu"), []);
11016%! assert (get (hf, "children"), [hctx1; hax]);
11017%! set (hf, "uicontextmenu", hctx1);
11018%! assert (get (hf, "uicontextmenu"), hctx1);
11019%! set (hf, "uicontextmenu", []);
11020%! assert (get (hf, "uicontextmenu"), []);
11021%! assert (get (hf, "children"), [hctx1; hax]);
11022%! unwind_protect_cleanup
11023%! close (hf);
11024%! end_unwind_protect
11025*/
11026
11027// ---------------------------------------------------------------------
11028
11030uicontrol::properties::get_extent () const
11031{
11032 Matrix m = m_extent.get ().matrix_value ();
11033
11034 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11035
11036 graphics_object parent_go = gh_mgr.get_object (get_parent ());
11037
11038 Matrix parent_bbox = parent_go.get_properties ().get_boundingbox (true);
11039 Matrix parent_size = parent_bbox.extract_n (0, 2, 1, 2);
11040
11041 return convert_position (m, "pixels", get_units (), parent_size);
11042}
11043
11044void
11045uicontrol::properties::update_text_extent ()
11046{
11047 // FIXME: support multiline text
11048
11049 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
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 ()
11058{
11059 Matrix pos = get_position ().matrix_value ();
11060
11061 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11062
11063 graphics_object parent_go = gh_mgr.get_object (get_parent ());
11064
11065 Matrix parent_bbox = parent_go.get_properties ().get_boundingbox (true);
11066 Matrix parent_size = parent_bbox.extract_n (0, 2, 1, 2);
11067
11068 pos = convert_position (pos, m_cached_units, get_units (), parent_size);
11069 set_position (pos);
11070
11071 m_cached_units = get_units ();
11072}
11073
11074void
11075uicontrol::properties::set_style (const octave_value& st)
11076{
11077 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11078
11079 graphics_object go_parent = gh_mgr.get_object (get_parent ());
11080 if (go_parent.valid_object () && go_parent.isa ("uibuttongroup"))
11081 {
11082 bool was_button = style_is ("radiobutton") || style_is ("togglebutton");
11083 m_style = st;
11084 bool now_button = style_is ("radiobutton") || style_is ("togglebutton");
11085 uibuttongroup::properties& props =
11086 dynamic_cast<uibuttongroup::properties&> (go_parent.get_properties ());
11087 // update selectedobject
11088 if (! was_button && now_button && ! props.get_selectedobject ().ok ())
11089 {
11090 props.set_selectedobject (get___myhandle__ ().value ());
11091 m_value.set (octave_value (1));
11092 }
11093 else if (was_button && ! now_button
11094 && (props.get_selectedobject ().value ()
11095 == get___myhandle__ ().value ()))
11096 props.set_selectedobject (Matrix ());
11097 }
11098
11099 // Don't notify the style change until the "value" property is fixed
11100 bool modified = m_style.set (st, true, false);
11101
11102 // Override "value" property for listbox and popupmenu.
11103 if (modified)
11104 {
11105 if (style_is ("listbox") || style_is ("popupmenu"))
11106 {
11107 Matrix v = m_value.get ().matrix_value ();
11108 if (v.numel () == 1 && v(0) == 0)
11109 m_value.set (octave_value (1), true, false);
11110 }
11111
11112 // Notify toolkit
11113
11114 graphics_object go = gh_mgr.get_object (get___myhandle__ ());
11115
11116 if (go)
11117 go.update (m_style.get_id ());
11118 }
11119}
11120
11121Matrix
11122uicontrol::properties::get_boundingbox (bool,
11123 const Matrix& parent_pix_size) const
11124{
11125 Matrix pos = get_position ().matrix_value ();
11126 Matrix parent_size (parent_pix_size);
11127
11128 if (parent_size.isempty ())
11129 {
11130 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11131
11132 graphics_object go = gh_mgr.get_object (get_parent ());
11133
11134 if (go.valid_object ())
11135 parent_size = go.get_properties ().get_boundingbox (true).extract_n (0, 2, 1, 2);
11136 else
11137 parent_size = default_figure_position ();
11138 }
11139
11140 pos = convert_position (pos, get_units (), "pixels", parent_size);
11141
11142 pos(0)--;
11143 pos(1)--;
11144 pos(1) = parent_size(1) - pos(1) - pos(3);
11145
11146 return pos;
11147}
11148
11149void
11150uicontrol::properties::set_fontunits (const octave_value& val)
11151{
11152 caseless_str old_fontunits = get_fontunits ();
11153
11154 if (m_fontunits.set (val, true))
11155 {
11156 update_fontunits (old_fontunits);
11157 mark_modified ();
11158 }
11159}
11160
11161void
11162uicontrol::properties::update_fontunits (const caseless_str& old_units)
11163{
11164 caseless_str new_units = get_fontunits ();
11165 double parent_height = get_boundingbox (false).elem (3);
11166 double fontsz = get_fontsize ();
11167
11168 fontsz = convert_font_size (fontsz, old_units, new_units, parent_height);
11169
11170 m_fontsize.set (octave_value (fontsz), true);
11171}
11172
11173double
11174uicontrol::properties::get___fontsize_points__ (double box_pix_height) const
11175{
11176 double fontsz = get_fontsize ();
11177 double parent_height = box_pix_height;
11178
11179 if (fontunits_is ("normalized") && parent_height <= 0)
11180 parent_height = get_boundingbox (false).elem (3);
11181
11182 return convert_font_size (fontsz, get_fontunits (), "points", parent_height);
11183}
11184
11185// ---------------------------------------------------------------------
11186
11187Matrix
11188uibuttongroup::properties::get_boundingbox (bool internal,
11189 const Matrix& parent_pix_size) const
11190{
11191 Matrix pos = get_position ().matrix_value ();
11192 Matrix parent_size (parent_pix_size);
11193
11194 if (parent_size.isempty ())
11195 {
11196 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11197
11198 graphics_object go = gh_mgr.get_object (get_parent ());
11199
11200 parent_size = go.get_properties ().get_boundingbox (true).extract_n (0, 2, 1, 2);
11201 }
11202
11203 pos = convert_position (pos, get_units (), "pixels", parent_size);
11204
11205 pos(0)--;
11206 pos(1)--;
11207 pos(1) = parent_size(1) - pos(1) - pos(3);
11208
11209 if (internal)
11210 {
11211 double outer_height = pos(3);
11212
11213 pos(0) = pos(1) = 0;
11214
11215 if (! bordertype_is ("none"))
11216 {
11217 double bw = get_borderwidth ();
11218 double mul = 1.0;
11219
11220 if (bordertype_is ("etchedin") || bordertype_is ("etchedout"))
11221 mul = 2.0;
11222
11223 pos(0) += mul * bw;
11224 pos(1) += mul * bw;
11225 pos(2) -= 2 * mul * bw;
11226 pos(3) -= 2 * mul * bw;
11227 }
11228
11229 if (! get_title ().empty ())
11230 {
11231 double fontsz = get_fontsize ();
11232
11233 if (! fontunits_is ("pixels"))
11234 {
11235 double res = xget (0, "screenpixelsperinch").double_value ();
11236
11237 if (fontunits_is ("points"))
11238 fontsz *= (res / 72.0);
11239 else if (fontunits_is ("inches"))
11240 fontsz *= res;
11241 else if (fontunits_is ("centimeters"))
11242 fontsz *= (res / 2.54);
11243 else if (fontunits_is ("normalized"))
11244 fontsz *= outer_height;
11245 }
11246
11247 if (titleposition_is ("lefttop") || titleposition_is ("centertop")
11248 || titleposition_is ("righttop"))
11249 pos(1) += (fontsz / 2);
11250 pos(3) -= (fontsz / 2);
11251 }
11252 }
11253
11254 return pos;
11255}
11256
11257void
11258uibuttongroup::properties::set_position (const octave_value& v)
11259{
11260 Matrix old_bb, new_bb;
11261 bool modified = false;
11262
11263 old_bb = get_boundingbox (true);
11264 modified = m_position.set (v, false);
11265 new_bb = get_boundingbox (true);
11266
11267 if (old_bb != new_bb)
11268 {
11269 if (old_bb(2) != new_bb(2) || old_bb(3) != new_bb(3))
11270 {
11271 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11272
11273 if (! get_resizefcn ().isempty ())
11274 gh_mgr.post_callback (m___myhandle__, "resizefcn");
11275
11276 if (! get_sizechangedfcn ().isempty ())
11277 gh_mgr.post_callback (m___myhandle__, "sizechangedfcn");
11278
11279 update_boundingbox ();
11280 }
11281 }
11282
11283 if (modified)
11284 {
11285 m_position.run_listeners (GCB_POSTSET);
11286 mark_modified ();
11287 }
11288}
11289
11290void
11291uibuttongroup::properties::set_units (const octave_value& val)
11292{
11293 caseless_str old_units = get_units ();
11294
11295 if (m_units.set (val, true))
11296 {
11297 update_units (old_units);
11298 mark_modified ();
11299 }
11300}
11301
11302void
11303uibuttongroup::properties::update_units (const caseless_str& old_units)
11304{
11305 Matrix pos = get_position ().matrix_value ();
11306
11307 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11308
11309 graphics_object parent_go = gh_mgr.get_object (get_parent ());
11310
11311 Matrix parent_bbox = parent_go.get_properties ().get_boundingbox (true);
11312 Matrix parent_size = parent_bbox.extract_n (0, 2, 1, 2);
11313
11314 pos = convert_position (pos, old_units, get_units (), parent_size);
11315 set_position (pos);
11316}
11317
11318void
11319uibuttongroup::properties::set_fontunits (const octave_value& val)
11320{
11321 caseless_str old_fontunits = get_fontunits ();
11322
11323 if (m_fontunits.set (val, true))
11324 {
11325 update_fontunits (old_fontunits);
11326 mark_modified ();
11327 }
11328}
11329
11330void
11331uibuttongroup::properties::update_fontunits (const caseless_str& old_units)
11332{
11333 caseless_str new_units = get_fontunits ();
11334 double parent_height = get_boundingbox (false).elem (3);
11335 double fontsz = get_fontsize ();
11336
11337 fontsz = convert_font_size (fontsz, old_units, new_units, parent_height);
11338
11339 set_fontsize (octave_value (fontsz));
11340}
11341
11342double
11343uibuttongroup::properties::get___fontsize_points__ (double box_pix_height) const
11344{
11345 double fontsz = get_fontsize ();
11346 double parent_height = box_pix_height;
11347
11348 if (fontunits_is ("normalized") && parent_height <= 0)
11349 parent_height = get_boundingbox (false).elem (3);
11350
11351 return convert_font_size (fontsz, get_fontunits (), "points", parent_height);
11352}
11353
11354void
11355uibuttongroup::properties::set_selectedobject (const octave_value& v)
11356{
11357 graphics_handle current_selectedobject = get_selectedobject();
11358 m_selectedobject = current_selectedobject;
11359 if (v.isempty ())
11360 {
11361 if (current_selectedobject.ok ())
11362 {
11363 m_selectedobject = graphics_handle ();
11364 mark_modified ();
11365 }
11366 return;
11367 }
11368
11369 graphics_handle val (v);
11370 if (val.ok ())
11371 {
11372 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11373
11374 graphics_object go (gh_mgr.get_object (val));
11375
11376 base_properties& gop = go.get_properties ();
11377
11378 if (go.valid_object ()
11379 && gop.get_parent () == get___myhandle__ ()
11380 && go.isa ("uicontrol"))
11381 {
11382 uicontrol::properties& cop
11383 = dynamic_cast<uicontrol::properties&> (go.get_properties ());
11384 const caseless_str& style = cop.get_style ();
11385 if (style.compare ("radiobutton") || style.compare ("togglebutton"))
11386 {
11387 m_selectedobject = val;
11388 mark_modified ();
11389 return;
11390 }
11391 }
11392 }
11393 err_set_invalid ("selectedobject");
11394}
11395
11396void
11397uibuttongroup::properties::remove_child (const graphics_handle& h,
11398 bool from_root)
11399{
11400 graphics_handle current_selected = get_selectedobject ();
11401 if (h.value () == current_selected.value ())
11402 set_selectedobject (Matrix ());
11403
11404 base_properties::remove_child (h, from_root);
11405}
11406
11407void
11408uibuttongroup::properties::adopt (const graphics_handle& h)
11409{
11410 base_properties::adopt (h);
11411
11412 graphics_handle current_selected = get_selectedobject ();
11413 bool has_selected = current_selected.ok ();
11414
11415 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11416
11417 graphics_object go = gh_mgr.get_object (h);
11418
11419 if (! has_selected && go.valid_object () && go.isa ("uicontrol"))
11420 {
11421 const uicontrol::properties& props =
11422 dynamic_cast<const uicontrol::properties&> (go.get_properties ());
11423 if (props.style_is ("radiobutton") || props.style_is ("togglebutton"))
11424 set_selectedobject (h.value ());
11425 }
11426}
11427
11428// ---------------------------------------------------------------------
11429
11430Matrix
11431uipanel::properties::get_boundingbox (bool internal,
11432 const Matrix& parent_pix_size) const
11433{
11434 Matrix pos = get_position ().matrix_value ();
11435 Matrix parent_size (parent_pix_size);
11436
11437 if (parent_size.isempty ())
11438 {
11439 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11440
11441 graphics_object go = gh_mgr.get_object (get_parent ());
11442
11443 parent_size = go.get_properties ().get_boundingbox (true).extract_n (0, 2, 1, 2);
11444 }
11445
11446 pos = convert_position (pos, get_units (), "pixels", parent_size);
11447
11448 pos(0)--;
11449 pos(1)--;
11450 pos(1) = parent_size(1) - pos(1) - pos(3);
11451
11452 if (internal)
11453 {
11454 double outer_height = pos(3);
11455
11456 pos(0) = pos(1) = 0;
11457
11458 if (! bordertype_is ("none"))
11459 {
11460 double bw = get_borderwidth ();
11461 double mul = 1.0;
11462
11463 if (bordertype_is ("etchedin") || bordertype_is ("etchedout"))
11464 mul = 2.0;
11465
11466 pos(0) += mul * bw;
11467 pos(1) += mul * bw;
11468 pos(2) -= 2 * mul * bw;
11469 pos(3) -= 2 * mul * bw;
11470 }
11471
11472 if (! get_title ().empty ())
11473 {
11474 double fontsz = get_fontsize ();
11475
11476 if (! fontunits_is ("pixels"))
11477 {
11478 double res = xget (0, "screenpixelsperinch").double_value ();
11479
11480 if (fontunits_is ("points"))
11481 fontsz *= (res / 72.0);
11482 else if (fontunits_is ("inches"))
11483 fontsz *= res;
11484 else if (fontunits_is ("centimeters"))
11485 fontsz *= (res / 2.54);
11486 else if (fontunits_is ("normalized"))
11487 fontsz *= outer_height;
11488 }
11489
11490 if (titleposition_is ("lefttop") || titleposition_is ("centertop")
11491 || titleposition_is ("righttop"))
11492 pos(1) += (fontsz / 2);
11493 pos(3) -= (fontsz / 2);
11494 }
11495 }
11496
11497 return pos;
11498}
11499
11500void
11501uipanel::properties::set_position (const octave_value& v)
11502{
11503 Matrix old_bb, new_bb;
11504 bool modified = false;
11505
11506 old_bb = get_boundingbox (true);
11507 modified = m_position.set (v, false);
11508 new_bb = get_boundingbox (true);
11509
11510 if (old_bb != new_bb)
11511 {
11512 if (old_bb(2) != new_bb(2) || old_bb(3) != new_bb(3))
11513 {
11514 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11515
11516 if (! get_resizefcn ().isempty ())
11517 gh_mgr.post_callback (m___myhandle__, "resizefcn");
11518
11519 if (! get_sizechangedfcn ().isempty ())
11520 gh_mgr.post_callback (m___myhandle__, "sizechangedfcn");
11521
11522 update_boundingbox ();
11523 }
11524 }
11525
11526 if (modified)
11527 {
11528 m_position.run_listeners (GCB_POSTSET);
11529 mark_modified ();
11530 }
11531}
11532
11533
11534void
11535uipanel::properties::set_units (const octave_value& val)
11536{
11537 caseless_str old_units = get_units ();
11538
11539 if (m_units.set (val, true))
11540 {
11541 update_units (old_units);
11542 mark_modified ();
11543 }
11544}
11545
11546void
11547uipanel::properties::update_units (const caseless_str& old_units)
11548{
11549 Matrix pos = get_position ().matrix_value ();
11550
11551 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11552
11553 graphics_object parent_go = gh_mgr.get_object (get_parent ());
11554
11555 Matrix parent_bbox = parent_go.get_properties ().get_boundingbox (true);
11556 Matrix parent_size = parent_bbox.extract_n (0, 2, 1, 2);
11557
11558 pos = convert_position (pos, old_units, get_units (), parent_size);
11559 set_position (pos);
11560}
11561
11562void
11563uipanel::properties::set_fontunits (const octave_value& val)
11564{
11565 caseless_str old_fontunits = get_fontunits ();
11566
11567 if (m_fontunits.set (val, true))
11568 {
11569 update_fontunits (old_fontunits);
11570 mark_modified ();
11571 }
11572}
11573
11574void
11575uipanel::properties::update_fontunits (const caseless_str& old_units)
11576{
11577 caseless_str new_units = get_fontunits ();
11578 double parent_height = get_boundingbox (false).elem (3);
11579 double fontsz = get_fontsize ();
11580
11581 fontsz = convert_font_size (fontsz, old_units, new_units, parent_height);
11582
11583 set_fontsize (octave_value (fontsz));
11584}
11585
11586double
11587uipanel::properties::get___fontsize_points__ (double box_pix_height) const
11588{
11589 double fontsz = get_fontsize ();
11590 double parent_height = box_pix_height;
11591
11592 if (fontunits_is ("normalized") && parent_height <= 0)
11593 parent_height = get_boundingbox (false).elem (3);
11594
11595 return convert_font_size (fontsz, get_fontunits (), "points", parent_height);
11596}
11597
11598// ---------------------------------------------------------------------
11599
11600Matrix
11601uitable::properties::get_boundingbox (bool,
11602 const Matrix& parent_pix_size) const
11603{
11604 Matrix pos = get_position ().matrix_value ();
11605 Matrix parent_size (parent_pix_size);
11606
11607 if (parent_size.isempty ())
11608 {
11609 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11610
11611 graphics_object go = gh_mgr.get_object (get_parent ());
11612
11613 parent_size = go.get_properties ().get_boundingbox (true).extract_n (0, 2, 1, 2);
11614 }
11615
11616 pos = convert_position (pos, get_units (), "pixels", parent_size);
11617
11618 pos(0)--;
11619 pos(1)--;
11620 pos(1) = parent_size(1) - pos(1) - pos(3);
11621
11622 return pos;
11623}
11624
11625void
11626uitable::properties::set_columnformat (const octave_value& val)
11627{
11628 /* Matlab only allows certain values for ColumnFormat. Here we only check the
11629 * structure of the argument. Values will be checked in Table.cc */
11630
11631 if (val.iscellstr ())
11632 {
11633 if (m_columnformat.set (val, true))
11634 mark_modified ();
11635 }
11636 else if (val.iscell ())
11637 {
11638 Cell cell_value = val.cell_value ();
11639
11640 for (int i = 0; i < cell_value.numel (); i++)
11641 {
11642 octave_value v = cell_value(i);
11643 if (v.iscell ())
11644 {
11645 /* We are in a pop-up menu selection.
11646 * Matlab only allows non-empty strings here. */
11647 Cell popup = v.cell_value ();
11648 for (int j = 0; j < popup.numel (); j++)
11649 {
11650 octave_value p = popup(j);
11651 if (! p.is_string () || p.isempty ())
11652 error ("set: pop-up menu definitions must be non-empty strings");
11653 }
11654 }
11655 else if (! (v.is_string () || v.isempty ()))
11656 {
11657 error ("set: columnformat definintions must be a cellstr of "
11658 "either 'char', 'short [e|g|eng]?', 'long [e|g|eng]?', "
11659 "'numeric', 'bank', '+', 'rat', 'logical', "
11660 "or a cellstr of non-empty pop-up menu definitions.");
11661 }
11662 }
11663
11664 if (m_columnformat.set (val, true))
11665 mark_modified ();
11666 }
11667 else if (val.isempty ())
11668 {
11669 if (m_columnformat.set (Cell (), true))
11670 mark_modified ();
11671 }
11672 else
11673 {
11674 error ("set: expecting cell of strings");
11675 }
11676}
11677
11678void
11679uitable::properties::set_columnwidth (const octave_value& val)
11680{
11681 bool isvalid = true;
11682
11683 if (val.is_string ())
11684 {
11685 std::string option = val.string_value (false);
11686 if (option != "auto" && option != "fit")
11687 isvalid = false;
11688 }
11689 else if (val.iscell ())
11690 {
11691 Cell cell_value = val.cell_value ();
11692 for (int i = 0; i < cell_value.numel (); i++)
11693 {
11694 octave_value v = cell_value(i);
11695 if (v.is_string ())
11696 {
11697 std::string option = v.string_value (false);
11698 if (option != "auto" && option != "fit")
11699 isvalid = false;
11700 }
11701 else if (! (v.isreal () && v.is_scalar_type ()))
11702 {
11703 isvalid = false;
11704 }
11705 }
11706 }
11707 else
11708 isvalid = false;
11709
11710 if (! isvalid)
11711 error ("set: expecting either 'auto', 'fit', or a cell array of pixel values, 'auto', or 'fit'");
11712
11713 if (m_columnwidth.set (val, true))
11714 mark_modified ();
11715}
11716
11717void
11718uitable::properties::set_data (const octave_value& val)
11719{
11720 if (! (val.iscell () || val.is_matrix_type () || val.is_scalar_type ()
11721 || val.is_range ()))
11722 error ("set: 'Data' must be an array or cell array");
11723
11724 if (m_data.set (val, true))
11725 mark_modified ();
11726}
11727
11728void
11729uitable::properties::set_units (const octave_value& val)
11730{
11731 caseless_str old_units = get_units ();
11732
11733 if (m_units.set (val, true))
11734 {
11735 update_units (old_units);
11736 mark_modified ();
11737 }
11738}
11739
11740void
11741uitable::properties::update_units (const caseless_str& old_units)
11742{
11743 Matrix pos = get_position ().matrix_value ();
11744
11745 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11746
11747 graphics_object parent_go = gh_mgr.get_object (get_parent ());
11748
11749 Matrix parent_bbox = parent_go.get_properties ().get_boundingbox (true);
11750 Matrix parent_size = parent_bbox.extract_n (0, 2, 1, 2);
11751
11752 pos = convert_position (pos, old_units, get_units (), parent_size);
11753 set_position (pos);
11754}
11755
11756void
11757uitable::properties::set_fontunits (const octave_value& val)
11758{
11759 caseless_str old_fontunits = get_fontunits ();
11760
11761 if (m_fontunits.set (val, true))
11762 {
11763 update_fontunits (old_fontunits);
11764 mark_modified ();
11765 }
11766}
11767
11768void
11769uitable::properties::update_fontunits (const caseless_str& old_units)
11770{
11771 caseless_str new_units = get_fontunits ();
11772 double parent_height = get_boundingbox (false).elem (3);
11773 double fontsz = get_fontsize ();
11774
11775 fontsz = convert_font_size (fontsz, old_units, new_units, parent_height);
11776
11777 set_fontsize (octave_value (fontsz));
11778}
11779
11780double
11781uitable::properties::get___fontsize_points__ (double box_pix_height) const
11782{
11783 double fontsz = get_fontsize ();
11784 double parent_height = box_pix_height;
11785
11786 if (fontunits_is ("normalized") && parent_height <= 0)
11787 parent_height = get_boundingbox (false).elem (3);
11788
11789 return convert_font_size (fontsz, get_fontunits (), "points", parent_height);
11790}
11791
11792double
11793uitable::properties::get_fontsize_pixels (double box_pix_height) const
11794{
11795 double fontsz = get_fontsize ();
11796 double parent_height = box_pix_height;
11797
11798 if (fontunits_is ("normalized") && parent_height <= 0)
11799 parent_height = get_boundingbox (false).elem (3);
11800
11801 return convert_font_size (fontsz, get_fontunits (), "pixels", parent_height);
11802}
11803
11804Matrix
11805uitable::properties::get_backgroundcolor_rgb ()
11806{
11807 Matrix bg = m_backgroundcolor.get ().matrix_value ();
11808 return bg.row (0);
11809}
11810
11811Matrix
11812uitable::properties::get_alternatebackgroundcolor_rgb ()
11813{
11814 int i = 0;
11815 Matrix bg = m_backgroundcolor.get ().matrix_value ();
11816 if (bg.rows () > 1)
11817 i = 1;
11818
11819 return bg.row (i);
11820}
11821
11822Matrix
11823uitable::properties::get_extent_matrix () const
11824{
11825 return m_extent.get ().matrix_value ();
11826}
11827
11829uitable::properties::get_extent () const
11830{
11831 // FIXME: Is it really acceptable to just let the toolkit update the extent?
11832 Matrix m = m_extent.get ().matrix_value ();
11833
11834 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11835
11836 graphics_object parent_go = gh_mgr.get_object (get_parent ());
11837
11838 if (parent_go)
11839 {
11840 Matrix parent_bbox = parent_go.get_properties ().get_boundingbox (true);
11841 Matrix parent_size = parent_bbox.extract_n (0, 2, 1, 2);
11842
11843 return convert_position (m, "pixels", get_units (), parent_size);
11844 }
11845
11846 return m;
11847}
11848
11849// ---------------------------------------------------------------------
11850
11852uitoolbar::get_default (const caseless_str& pname) const
11853{
11854 octave_value retval = m_default_properties.lookup (pname);
11855
11856 if (retval.is_undefined ())
11857 {
11858 graphics_handle parent_h = get_parent ();
11859
11860 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11861
11862 graphics_object parent_go = gh_mgr.get_object (parent_h);
11863
11864 retval = parent_go.get_default (pname);
11865 }
11866
11867 return retval;
11868}
11869
11870void
11871uitoolbar::reset_default_properties ()
11872{
11873 // empty list of local defaults
11874 m_default_properties = property_list ();
11875
11876 remove_all_listeners ();
11877 xreset_default_properties (get_handle (), m_properties.factory_defaults ());
11878}
11879
11880// ---------------------------------------------------------------------
11881
11883base_graphics_object::get_default (const caseless_str& pname) const
11884{
11885 graphics_handle parent_h = get_parent ();
11886
11887 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11888
11889 graphics_object parent_go = gh_mgr.get_object (parent_h);
11890
11891 return parent_go.get_default (type () + pname);
11892}
11893
11895base_graphics_object::get_factory_default (const caseless_str& name) const
11896{
11897 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11898
11899 graphics_object parent_go = gh_mgr.get_object (0);
11900
11901 return parent_go.get_factory_default (type () + name);
11902}
11903
11904class callback_event : public base_graphics_event
11905{
11906public:
11907 callback_event (const graphics_handle& h, const std::string& name,
11908 const octave_value& data = Matrix (),
11909 int busyaction = base_graphics_event::QUEUE)
11910 : base_graphics_event (busyaction), m_handle (h), m_callback_name (name),
11911 m_callback (), m_callback_data (data) { }
11912
11913 callback_event (const graphics_handle& h, const octave_value& cb,
11914 const octave_value& data = Matrix (),
11915 int busyaction = base_graphics_event::QUEUE)
11916 : base_graphics_event (busyaction), m_handle (h), m_callback_name (),
11917 m_callback (cb), m_callback_data (data) { }
11918
11919 OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (callback_event)
11920
11921 ~callback_event () = default;
11922
11923 void execute ()
11924 {
11925 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11926
11927 if (m_callback.is_defined ())
11928 gh_mgr.execute_callback (m_handle, m_callback, m_callback_data);
11929 else
11930 gh_mgr.execute_callback (m_handle, m_callback_name, m_callback_data);
11931 }
11932
11933private:
11934 graphics_handle m_handle;
11935 std::string m_callback_name;
11936 octave_value m_callback;
11937 octave_value m_callback_data;
11938};
11939
11940class mcode_event : public base_graphics_event
11941{
11942public:
11943 mcode_event (const graphics_handle& h, const std::string& cmd,
11944 int busyaction = base_graphics_event::QUEUE)
11945 : base_graphics_event (busyaction), m_handle (h), m_mcode (cmd)
11946 { }
11947
11948 OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (mcode_event)
11949
11950 ~mcode_event () = default;
11951
11952 void execute ()
11953 {
11954 if (! m_mcode.empty ())
11955 {
11956 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11957
11958 graphics_object go = gh_mgr.get_object (m_handle);
11959
11960 if (go.valid_object ())
11961 {
11962 octave_value cb (m_mcode);
11963 gh_mgr.execute_callback (m_handle, cb);
11964 }
11965 }
11966 }
11967
11968private:
11969 graphics_handle m_handle;
11970 std::string m_mcode;
11971};
11972
11973class function_event : public base_graphics_event
11974{
11975public:
11976
11977 function_event (graphics_event::event_fcn fcn, void *data = nullptr)
11978 : base_graphics_event (), m_function (fcn), m_function_data (data)
11979 { }
11980
11981 OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (function_event)
11982
11983 ~function_event () = default;
11984
11985 void execute ()
11986 {
11987 m_function (m_function_data);
11988 }
11989
11990private:
11991
11992 graphics_event::event_fcn m_function;
11993
11994 void *m_function_data;
11995};
11996
11997class set_event : public base_graphics_event
11998{
11999public:
12000 set_event (const graphics_handle& h, const std::string& name,
12001 const octave_value& value, bool do_notify_toolkit = true,
12002 bool redraw_figure = false)
12003 : base_graphics_event (), m_handle (h), m_property_name (name),
12004 m_property_value (value), m_notify_toolkit (do_notify_toolkit),
12005 m_redraw_figure (redraw_figure)
12006 { }
12007
12008 OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (set_event)
12009
12010 ~set_event () = default;
12011
12012 void execute ()
12013 {
12014 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
12015
12016 octave::autolock guard (gh_mgr.graphics_lock ());
12017
12018 graphics_object go = gh_mgr.get_object (m_handle);
12019
12020 if (go)
12021 {
12022 property p = go.get_properties ().get_property (m_property_name);
12023
12024 if (p.ok ())
12025 {
12026 // FIXME: figure position and outerposition properties set_xxx have
12027 // a signature that allows passing the notify_toolkit argument.
12028 // Should we change all set_xxx signatures and allow
12029 // base_properties::set to accept this also? This would allow for
12030 // the use of high level set_xxx instead of directly changing the
12031 // property value.
12032 if (go.isa ("figure") && m_property_name == "position")
12033 {
12034 figure::properties& fprops
12035 = dynamic_cast<figure::properties&> (go.get_properties ());
12036 fprops.set_position (m_property_value, m_notify_toolkit);
12037 }
12038 else if (go.isa ("figure") && m_property_name == "outerposition")
12039 {
12040 figure::properties& fprops
12041 = dynamic_cast<figure::properties&> (go.get_properties ());
12042 fprops.set_outerposition (m_property_value, m_notify_toolkit);
12043 }
12044 else
12045 p.set (m_property_value, true, m_notify_toolkit);
12046
12047 if (m_redraw_figure)
12048 {
12049 if (! go.isa ("figure"))
12050 go = go.get_ancestor ("figure");
12051
12052 if (go.valid_object ())
12053 {
12054 figure::properties& fprops
12055 = dynamic_cast<figure::properties&> (go.get_properties ());
12056 fprops.get_toolkit ().redraw_figure (go);
12057 }
12058 }
12059 }
12060 }
12061 }
12062
12063private:
12064 graphics_handle m_handle;
12065 std::string m_property_name;
12066 octave_value m_property_value;
12067 bool m_notify_toolkit;
12068 bool m_redraw_figure;
12069};
12070
12071graphics_event
12072graphics_event::create_callback_event (const graphics_handle& h,
12073 const std::string& name,
12074 const octave_value& data,
12075 int busyaction)
12076{
12077 return graphics_event (new callback_event (h, name, data, busyaction));
12078}
12079
12080graphics_event
12081graphics_event::create_callback_event (const graphics_handle& h,
12082 const octave_value& cb,
12083 const octave_value& data,
12084 int busyaction)
12085{
12086 return graphics_event (new callback_event (h, cb, data, busyaction));
12087}
12088
12089graphics_event
12090graphics_event::create_mcode_event (const graphics_handle& h,
12091 const std::string& cmd,
12092 int busyaction)
12093{
12094 return graphics_event (new mcode_event (h, cmd, busyaction));
12095}
12096
12097graphics_event
12098graphics_event::create_function_event (graphics_event::event_fcn fcn,
12099 void *data)
12100{
12101 return graphics_event (new function_event (fcn, data));
12102}
12103
12104graphics_event
12105graphics_event::create_set_event (const graphics_handle& h,
12106 const std::string& name,
12107 const octave_value& data,
12108 bool notify_toolkit, bool redraw_figure)
12109{
12110 return graphics_event (new set_event (h, name, data, notify_toolkit,
12111 redraw_figure));
12112}
12113
12114property_list::plist_map_type
12115root_figure::init_factory_properties ()
12116{
12117 property_list::plist_map_type plist_map;
12118
12119 plist_map["figure"] = figure::properties::factory_defaults ();
12120 plist_map["axes"] = axes::properties::factory_defaults ();
12121 plist_map["line"] = line::properties::factory_defaults ();
12122 plist_map["text"] = text::properties::factory_defaults ();
12123 plist_map["image"] = image::properties::factory_defaults ();
12124 plist_map["patch"] = patch::properties::factory_defaults ();
12125 plist_map["scatter"] = scatter::properties::factory_defaults ();
12126 plist_map["surface"] = surface::properties::factory_defaults ();
12127 plist_map["light"] = light::properties::factory_defaults ();
12128 plist_map["hggroup"] = hggroup::properties::factory_defaults ();
12129 plist_map["uimenu"] = uimenu::properties::factory_defaults ();
12130 plist_map["uicontrol"] = uicontrol::properties::factory_defaults ();
12131 plist_map["uibuttongroup"] = uibuttongroup::properties::factory_defaults ();
12132 plist_map["uipanel"] = uipanel::properties::factory_defaults ();
12133 plist_map["uicontextmenu"] = uicontextmenu::properties::factory_defaults ();
12134 plist_map["uitoolbar"] = uitoolbar::properties::factory_defaults ();
12135 plist_map["uipushtool"] = uipushtool::properties::factory_defaults ();
12136 plist_map["uitoggletool"] = uitoggletool::properties::factory_defaults ();
12137
12138 return plist_map;
12139}
12140
12141// ---------------------------------------------------------------------
12142
12143DEFMETHOD (ishghandle, interp, args, ,
12144 doc: /* -*- texinfo -*-
12145@deftypefn {} {@var{tf} =} ishghandle (@var{h})
12146Return true if @var{h} is a graphics handle and false otherwise.
12147
12148@var{h} may also be a matrix of handles in which case a logical array is
12149returned that is true where the elements of @var{h} are graphics handles and
12150false where they are not.
12151@seealso{isgraphics, isaxes, isfigure, ishandle}
12152@end deftypefn */)
12153{
12154 gh_manager& gh_mgr = interp.get_gh_manager ();
12155
12156 octave::autolock guard (gh_mgr.graphics_lock ());
12157
12158 if (args.length () != 1)
12159 print_usage ();
12160
12161 return ovl (ishghandle (args(0)));
12162}
12163
12164/*
12165%!test
12166%! hf = figure ("visible", "off");
12167%! unwind_protect
12168%! assert (ishghandle (hf));
12169%! assert (! ishghandle (-hf));
12170%! ax = gca ();
12171%! l = line ();
12172%! assert (ishghandle (ax));
12173%! assert (! ishghandle (-ax));
12174%! assert (ishghandle ([l, -1, ax, hf]), logical ([1, 0, 1, 1]));
12175%! assert (ishghandle ([l, -1, ax, hf]'), logical ([1, 0, 1, 1]'));
12176%! unwind_protect_cleanup
12177%! close (hf);
12178%! end_unwind_protect
12179
12180%!assert (ishghandle ([-1 0]), [false true])
12181*/
12182
12183static bool
12184is_handle_visible (const graphics_handle& h)
12185{
12186 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
12187
12188 return h.ok () && gh_mgr.is_handle_visible (h);
12189}
12190
12191static bool
12192is_handle_visible (double val)
12193{
12194 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
12195
12196 return is_handle_visible (gh_mgr.lookup (val));
12197}
12198
12199static octave_value
12200is_handle_visible (const octave_value& val)
12201{
12202 octave_value retval = false;
12203
12204 if (val.is_real_scalar () && is_handle_visible (val.double_value ()))
12205 retval = true;
12206 else if (val.isnumeric () && val.isreal ())
12207 {
12208 const NDArray handles = val.array_value ();
12209
12210 boolNDArray result (handles.dims ());
12211
12212 for (octave_idx_type i = 0; i < handles.numel (); i++)
12213 result.xelem (i) = is_handle_visible (handles(i));
12214
12215 retval = result;
12216 }
12217
12218 return retval;
12219}
12220
12221DEFUN (__is_handle_visible__, args, ,
12222 doc: /* -*- texinfo -*-
12223@deftypefn {} {@var{tf} =} __is_handle_visible__ (@var{h})
12224Undocumented internal function.
12225@end deftypefn */)
12226{
12227 if (args.length () != 1)
12228 print_usage ();
12229
12230 return ovl (is_handle_visible (args(0)));
12231}
12232
12233DEFMETHOD (reset, interp, args, ,
12234 doc: /* -*- texinfo -*-
12235@deftypefn {} {} reset (@var{h})
12236Reset the properties of the graphic object @var{h} to their default values.
12237
12238For figures, the properties @qcode{"position"}, @qcode{"units"},
12239@qcode{"windowstyle"}, and @qcode{"paperunits"} are not affected.
12240For axes, the properties @qcode{"position"} and @qcode{"units"} are
12241not affected.
12242
12243The input @var{h} may also be a vector of graphic handles in which case
12244each individual object will be reset.
12245@seealso{cla, clf, newplot}
12246@end deftypefn */)
12247{
12248 if (args.length () != 1)
12249 print_usage ();
12250
12251 // get vector of graphics handles
12252 ColumnVector hcv = args(0).xvector_value ("reset: H must be a graphics handle");
12253
12254 gh_manager& gh_mgr = interp.get_gh_manager ();
12255
12256 // loop over graphics objects
12257 for (octave_idx_type n = 0; n < hcv.numel (); n++)
12258 gh_mgr.get_object (hcv(n)).reset_default_properties ();
12259
12260 Vdrawnow_requested = true;
12261
12262 return ovl ();
12263}
12264
12265/*
12266
12267%!test # line object
12268%! hf = figure ("visible", "off");
12269%! unwind_protect
12270%! tol = 20 * eps;
12271%! hax = axes ("defaultlinelinewidth", 3);
12272%!
12273%! hli = line (1:10, 1:10, 1:10, "marker", "o",
12274%! "markerfacecolor", "b", "linestyle", ":");
12275%!
12276%! reset (hli);
12277%! assert (get (hli, "marker"), get (0, "defaultlinemarker"));
12278%! assert (get (hli, "markerfacecolor"),
12279%! get (0, "defaultlinemarkerfacecolor"));
12280%! assert (get (hli, "linestyle"), get (0, "defaultlinelinestyle"));
12281%! assert (get (hli, "linewidth"), 3, tol); # parent axes defaults
12282%!
12283%! unwind_protect_cleanup
12284%! close (hf);
12285%! end_unwind_protect
12286
12287%!test # patch object
12288%! hf = figure ("visible", "off");
12289%! unwind_protect
12290%! tol = 20 * eps;
12291%! t1 = (1/16:1/8:1)' * 2*pi;
12292%! t2 = ((1/16:1/16:1)' + 1/32) * 2*pi;
12293%! x1 = sin (t1) - 0.8;
12294%! y1 = cos (t1);
12295%! x2 = sin (t2) + 0.8;
12296%! y2 = cos (t2);
12297%! vert = [x1, y1; x2, y2];
12298%! fac = [1:8,NaN(1,8);9:24];
12299%! hpa = patch ("Faces",fac, "Vertices",vert, "FaceColor","r");
12300%!
12301%! reset (hpa);
12302%! assert (get (hpa, "faces"), get (0, "defaultpatchfaces"), tol);
12303%! assert (get (hpa, "vertices"), get (0, "defaultpatchvertices"), tol);
12304%! assert (get (hpa, "facevertexcdata"),
12305%! get (0, "defaultpatchfacevertexcdata"), tol);
12306%! unwind_protect_cleanup
12307%! close (hf);
12308%! end_unwind_protect
12309
12310%!test # surface object
12311%! hf = figure ("visible", "off");
12312%! unwind_protect
12313%! tol = 20 * eps;
12314%! hsu = surface (peaks, "edgecolor", "none");
12315%!
12316%! reset (hsu);
12317%! assert (get (hsu, "xdata"), get (0, "defaultsurfacexdata"), tol);
12318%! assert (get (hsu, "ydata"), get (0, "defaultsurfaceydata"), tol);
12319%! assert (get (hsu, "zdata"), get (0, "defaultsurfacezdata"), tol);
12320%! assert (get (hsu, "edgecolor"), get (0, "defaultsurfaceedgecolor"), tol);
12321%! unwind_protect_cleanup
12322%! close (hf);
12323%! end_unwind_protect
12324
12325%!test # image object
12326%! hf = figure ("visible", "off");
12327%! unwind_protect
12328%! tol = 20 * eps;
12329%! him = image (rand (10,10), "cdatamapping", "scaled");
12330%!
12331%! reset (him);
12332%! assert (get (him, "cdata"), get (0, "defaultimagecdata"), tol);
12333%! assert (get (him, "cdatamapping"),
12334%! get (0, "defaultimagecdatamapping"), tol);
12335%! unwind_protect_cleanup
12336%! close (hf);
12337%! end_unwind_protect
12338
12339%!test # text object
12340%! hf = figure ("visible", "off");
12341%! unwind_protect
12342%! tol = 20 * eps;
12343%! hte = text (5, 5, "Hi!", "fontsize", 20 ,"color", "r");
12344%!
12345%! reset (hte);
12346%! assert (get (hte, "position"), get (0, "defaulttextposition"), tol);
12347%! assert (get (hte, "fontsize"), get (0, "defaulttextfontsize"), tol);
12348%! assert (get (hte, "color"), get (0, "defaulttextcolor"), tol);
12349%! unwind_protect_cleanup
12350%! close (hf);
12351%! end_unwind_protect
12352
12353%!test # axes object
12354%! hf = figure ("visible", "off");
12355%! unwind_protect
12356%! tol = 20 * eps;
12357%! pos = get (0, "defaultaxesposition") * .5;
12358%! hax = axes ("linewidth", 2, "position", pos);
12359%! title ("Reset me, please!");
12360%!
12361%! reset (hax);
12362%! assert (get (hax, "linewidth"), get (0, "defaultaxeslinewidth"), tol);
12363%! assert (get (hax, "position"), pos, tol); # axes position is unchanged
12364%! assert (get (hax, "default"), struct ()); # no more axes' defaults
12365%! assert (get (get (hax, "title"), "string"), "");
12366%! unwind_protect_cleanup
12367%! close (hf);
12368%! end_unwind_protect
12369
12370%!test # figure object
12371%! set (0, "defaultfigurevisible", "off");
12372%! hf = figure ("visible", "off",
12373%! "units", "normalized",
12374%! "position", [0, 0, pi/10, e/10],
12375%! "paperunits", "normalized",
12376%! "paperposition", [0.1, 0.1, 0.9, 0.9],
12377%! "tag", "foobar");
12378%! unwind_protect
12379%! reset (hf);
12380%! ## Ordinary property is reset
12381%! assert (get (hf, "tag"), "");
12382%! ## Following 4 special properties are not reset
12383%! assert (get (hf, "units"), "normalized");
12384%! assert (get (hf, "position"), [0, 0, pi/10, e/10]);
12385%! assert (get (hf, "paperunits"), "normalized");
12386%! assert (get (hf, "paperposition"), [0.1, 0.1, 0.9, 0.9]);
12387%! unwind_protect_cleanup
12388%! close (hf);
12389%! set (0, "defaultfigurevisible", "remove");
12390%! end_unwind_protect
12391
12392*/
12393
12394DEFMETHOD (set, interp, args, nargout,
12395 doc: /* -*- texinfo -*-
12396@deftypefn {} {} set (@var{h}, @var{property}, @var{value}, @dots{})
12397@deftypefnx {} {} set (@var{h}, @{@var{properties}@}, @{@var{values}@})
12398@deftypefnx {} {} set (@var{h}, @var{pv})
12399@deftypefnx {} {@var{value_list} =} set (@var{h}, @var{property})
12400@deftypefnx {} {@var{all_value_list} =} set (@var{h})
12401Set named property values for the graphics handle (or vector of graphics
12402handles) @var{h}.
12403
12404There are three ways to give the property names and values:
12405
12406@itemize
12407@item as a comma-separated list of @var{property}, @var{value} pairs
12408
12409Each @var{property} is a string containing the property name, each @var{value}
12410is a value of the appropriate type for the property. When there are multiple
12411handles in @var{h}, each one is assigned the same @var{value}. For example:
12412
12413@example
12414@group
12415h = plot ([0, 1]);
12416set (h, 'color', 'green');
12417@end group
12418@end example
12419
12420@item as a cell array of strings @var{properties} containing property names
12421and a cell array @var{values} containing property values.
12422
12423In this case, the number of columns of @var{values} must match the number of
12424elements in @var{properties}. The first column of @var{values} contains
12425values for the first entry in @var{properties}, etc. The number of rows of
12426@var{values} must be 1 or match the number of elements of @var{h}. In the
12427first case, each handle in @var{h} will be assigned the same values. In the
12428second case, the first handle in @var{h} will be assigned the values from
12429the first row of @var{values} and so on. For example:
12430
12431@example
12432@group
12433h = plot ([0, 1; 1, 0]);
12434set (h, @{'color'@}, @{'green'; 'red'@});
12435@end group
12436@end example
12437
12438@item as a structure @var{pv}
12439
12440This is the same as the first case where the field names of @var{pv} represent
12441the property names, and the field values give the property values. As with
12442the first case, it is only possible to set one value for a property which will
12443be applied to all handles in @var{h}. For example:
12444
12445@example
12446@group
12447h = plot ([0, 1]);
12448props.color = 'green';
12449set (h, props);
12450@end group
12451@end example
12452
12453@end itemize
12454
12455The three syntaxes for setting properties may appear in any combination.
12456
12457@code{set} is also used to query the list of values a named property will
12458take. @code{@var{clist} = set (@var{h}, "property")} will return the list
12459of possible values for @qcode{"property"} in the cell list @var{clist}.
12460If no output variable is used then the list is formatted and printed to the
12461screen.
12462
12463If no property is specified (@code{@var{slist} = set (@var{h})}) then a
12464structure @var{slist} is returned where the fieldnames are the properties of
12465the object @var{h} and the fields are the list of possible values for each
12466property. If no output variable is used then the list is formatted and
12467printed to the screen.
12468
12469When querying properties only a single graphics handle @var{h} for a single
12470graphics object is permitted.
12471
12472Example Query
12473
12474@example
12475@group
12476hf = figure ();
12477set (hf, "paperorientation")
12478@xresult{} [ landscape | @{portrait@} ]
12479@end group
12480@end example
12481
12482@noindent
12483shows the paperorientation property can take two values with the default
12484being @qcode{"portrait"}.
12485@seealso{get}
12486@end deftypefn */)
12487{
12488 int nargin = args.length ();
12489
12490 if (nargin == 0)
12491 print_usage ();
12492
12493 // get vector of graphics handles
12494 ColumnVector hcv = args(0).xvector_value ("set: H must be a graphics handle");
12495
12496 gh_manager& gh_mgr = interp.get_gh_manager ();
12497
12498 octave::autolock guard (gh_mgr.graphics_lock ());
12499
12500 octave_value retval;
12501
12502 // Process requests for default value(s)
12503 if (nargin == 1)
12504 {
12505 if (hcv.numel () > 1)
12506 error ("set: H must be a single graphics handle when querying properties");
12507
12508 graphics_object go = gh_mgr.get_object (hcv(0));
12509 if (! go)
12510 error ("set: invalid handle (= %g)", hcv(0));
12511
12512 if (nargout > 0)
12513 retval = go.values_as_struct ();
12514 else
12515 {
12516 std::string s = go.values_as_string ();
12517
12518 octave_stdout << s;
12519 }
12520
12521 return retval;
12522 }
12523 else if (nargin == 2 && args(1).is_string ())
12524 {
12525 if (hcv.numel () > 1)
12526 error ("set: H must be a single graphics handle when querying properties");
12527
12528 std::string property = args(1).string_value ();
12529 std::transform (property.begin (), property.end (),
12530 property.begin (), tolower);
12531
12532 graphics_object go = gh_mgr.get_object (hcv(0));
12533
12534 if (! go)
12535 error ("set: invalid handle (= %g)", hcv(0));
12536
12537 octave_map pmap = go.values_as_struct ();
12538
12539 if (go.has_readonly_property (property))
12540 {
12541 if (nargout > 0)
12542 retval = Matrix ();
12543 else
12544 octave_stdout << "set: " << property << " is read-only"
12545 << std::endl;
12546 }
12547 else if (pmap.isfield (property))
12548 {
12549 if (nargout != 0)
12550 retval = pmap.getfield (property)(0);
12551 else
12552 {
12553 std::string s = go.value_as_string (property);
12554
12555 octave_stdout << s;
12556 }
12557 }
12558 else
12559 error (R"(set: unknown property "%s")", property.c_str ());
12560
12561 return retval;
12562 }
12563
12564 bool request_drawnow = false;
12565
12566 // Loop over graphics objects
12567 for (octave_idx_type n = 0; n < hcv.numel (); n++)
12568 {
12569 graphics_object go = gh_mgr.get_object (hcv(n));
12570
12571 if (! go)
12572 error ("set: invalid handle (= %g)", hcv(n));
12573
12574 // Loop over input arguments
12575 for (octave_idx_type i = 1; i < args.length (); )
12576 {
12577 if (args(i).is_string ())
12578 {
12579 if (i == nargin - 1)
12580 error ("set: PROPERTY name must be followed by a VALUE");
12581 const caseless_str pname = args(i).string_value ();
12582 const octave_value val = args(i+1);
12583 go.set_value_or_default (pname, val);
12584 i += 2;
12585 }
12586 else if (args(i).iscellstr ())
12587 {
12588 if ((i == nargin - 1) || ! args(i+1).iscell ())
12589 error ("set: cell array of PROPERTIES must be followed by cell array of VALUES");
12590 if (args(i+1).cell_value ().rows () == 1)
12591 go.set (args(i).cellstr_value (), args(i+1).cell_value (), 0);
12592 else if (hcv.numel () == args(i+1).cell_value ().rows ())
12593 go.set (args(i).cellstr_value (), args(i+1).cell_value (), n);
12594 else
12595 error ("set: number of graphics handles must match number of "
12596 "value rows (%" OCTAVE_IDX_TYPE_FORMAT " != "
12597 "%" OCTAVE_IDX_TYPE_FORMAT ")",
12598 hcv.numel (), args(i+1).cell_value ().rows ());
12599 i += 2;
12600 }
12601 else if (args(i).isstruct ())
12602 {
12603 go.set (args(i).map_value ());
12604 i += 1;
12605 }
12606 else
12607 error ("set: invalid syntax");
12608 }
12609
12610 request_drawnow = true;
12611 }
12612
12613 if (request_drawnow)
12614 Vdrawnow_requested = true;
12615
12616 return retval;
12617}
12618
12619/*
12620## test setting ticklabels for compatibility
12621%!test
12622%! hf = figure ("visible", "off");
12623%! set (gca (), "xticklabel", [0, 0.2, 0.4, 0.6, 0.8, 1]);
12624%! xticklabel = get (gca (), "xticklabel");
12625%! close (hf);
12626%! assert (class (xticklabel), "char");
12627%! assert (size (xticklabel), [6, 3]);
12628
12629%!test
12630%! hf = figure ("visible", "off");
12631%! set (gca (), "xticklabel", "0|0.2|0.4|0.6|0.8|1");
12632%! xticklabel = get (gca (), "xticklabel");
12633%! close (hf);
12634%! assert (class (xticklabel), "char");
12635%! assert (size (xticklabel), [6, 3]);
12636
12637%!test
12638%! hf = figure ("visible", "off");
12639%! set (gca (), "xticklabel", ["0 "; "0.2"; "0.4"; "0.6"; "0.8"; "1 "]);
12640%! xticklabel = get (gca (), "xticklabel");
12641%! close (hf);
12642%! assert (class (xticklabel), "char");
12643%! assert (size (xticklabel), [6, 3]);
12644
12645%!test
12646%! hf = figure ("visible", "off");
12647%! set (gca (), "xticklabel", {"0", "0.2", "0.4", "0.6", "0.8", "1"});
12648%! xticklabel = get (gca (), "xticklabel");
12649%! close (hf);
12650%! assert (class (xticklabel), "cell");
12651%! assert (size (xticklabel), [6, 1]);
12652*/
12653
12654static std::string
12655get_graphics_object_type (double val)
12656{
12657 std::string retval;
12658
12659 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
12660
12661 graphics_object go = gh_mgr.get_object (val);
12662
12663 if (! go)
12664 error ("get: invalid handle (= %g)", val);
12665
12666 return go.type ();
12667}
12668
12669DEFMETHOD (get, interp, args, ,
12670 doc: /* -*- texinfo -*-
12671@deftypefn {} {@var{val} =} get (@var{h})
12672@deftypefnx {} {@var{val} =} get (@var{h}, @var{p})
12673Return the value of the named property @var{p} from the graphics handle
12674@var{h}.
12675
12676If @var{p} is omitted, return the complete property list for @var{h}.
12677
12678If @var{h} is a vector, return a cell array including the property values or
12679lists respectively.
12680@seealso{set}
12681@end deftypefn */)
12682{
12683 gh_manager& gh_mgr = interp.get_gh_manager ();
12684
12685 octave::autolock guard (gh_mgr.graphics_lock ());
12686
12687 int nargin = args.length ();
12688
12689 if (nargin < 1 || nargin > 2)
12690 print_usage ();
12691
12692 if (args(0).isempty ())
12693 return ovl (Matrix ());
12694
12695 ColumnVector hcv = args(0).xvector_value ("get: H must be a graphics handle");
12696
12697 octave_idx_type hcv_len = hcv.numel ();
12698
12699 if (nargin == 1 && hcv_len > 1)
12700 {
12701 std::string typ0 = get_graphics_object_type (hcv(0));
12702
12703 for (octave_idx_type n = 1; n < hcv_len; n++)
12704 {
12705 std::string typ = get_graphics_object_type (hcv(n));
12706
12707 if (typ != typ0)
12708 error ("get: vector of handles must all have the same type");
12709 }
12710 }
12711
12712 octave_value retval;
12713 Cell vals;
12714 bool use_cell_format = false;
12715
12716 if (nargin > 1 && args(1).iscellstr ())
12717 {
12718 Array<std::string> plist = args(1).cellstr_value ();
12719
12720 octave_idx_type plen = plist.numel ();
12721
12722 use_cell_format = true;
12723
12724 vals.resize (dim_vector (hcv_len, plen));
12725
12726 for (octave_idx_type n = 0; n < hcv_len; n++)
12727 {
12728 graphics_object go = gh_mgr.get_object (hcv(n));
12729
12730 if (! go)
12731 error ("get: invalid handle (= %g)", hcv(n));
12732
12733 for (octave_idx_type m = 0; m < plen; m++)
12734 {
12735 caseless_str property = plist(m);
12736
12737 vals(n, m) = go.get (property);
12738 }
12739 }
12740 }
12741 else
12742 {
12743 caseless_str property;
12744
12745 if (nargin > 1)
12746 property = args(1).xstring_value ("get: second argument must be property name or cell array of property names");
12747
12748 vals.resize (dim_vector (hcv_len, 1));
12749
12750 for (octave_idx_type n = 0; n < hcv_len; n++)
12751 {
12752 graphics_object go = gh_mgr.get_object (hcv(n));
12753
12754 if (! go)
12755 error ("get: invalid handle (= %g)", hcv(n));
12756
12757 if (nargin == 1)
12758 vals(n) = go.get ();
12759 else
12760 vals(n) = go.get (property);
12761 }
12762 }
12763
12764 if (use_cell_format)
12765 retval = vals;
12766 else
12767 {
12768 octave_idx_type vals_len = vals.numel ();
12769
12770 if (vals_len == 0)
12771 retval = Matrix ();
12772 else if (vals_len == 1)
12773 retval = vals(0);
12774 else if (vals_len > 1 && nargin == 1)
12775 {
12776 OCTAVE_LOCAL_BUFFER (octave_scalar_map, tmp, vals_len);
12777
12778 for (octave_idx_type n = 0; n < vals_len; n++)
12779 tmp[n] = vals(n).scalar_map_value ();
12780
12781 retval = octave_map::cat (0, vals_len, tmp);
12782 }
12783 else
12784 retval = vals;
12785 }
12786
12787 return retval;
12788}
12789
12790/*
12791%!assert (get (findobj (0, "Tag", "nonexistenttag"), "nonexistentproperty"), [])
12792*/
12793
12794// Return all properties from the graphics handle @var{h}.
12795// If @var{h} is a vector, return a cell array including the
12796// property values or lists respectively.
12797
12798DEFMETHOD (__get__, interp, args, ,
12799 doc: /* -*- texinfo -*-
12800@deftypefn {} {@var{props} =} __get__ (@var{h})
12801Undocumented internal function.
12802@end deftypefn */)
12803{
12804 gh_manager& gh_mgr = interp.get_gh_manager ();
12805
12806 octave::autolock guard (gh_mgr.graphics_lock ());
12807
12808 if (args.length () != 1)
12809 print_usage ();
12810
12811 ColumnVector hcv = args(0).xvector_value ("get: H must be a graphics handle");
12812
12813 octave_idx_type hcv_len = hcv.numel ();
12814
12815 Cell vals (dim_vector (hcv_len, 1));
12816
12817// vals.resize (dim_vector (hcv_len, 1));
12818
12819 for (octave_idx_type n = 0; n < hcv_len; n++)
12820 {
12821 graphics_object go = gh_mgr.get_object (hcv(n));
12822
12823 if (! go)
12824 error ("get: invalid handle (= %g)", hcv(n));
12825
12826 // Disable "Octave:deprecated-property" warnings
12827 int state = toggle_warn ("Octave:deprecated-property", false);
12828
12829 vals(n) = go.get (true);
12830
12831 toggle_warn ("Octave:deprecated-property", true, state);
12832 }
12833
12834 octave_idx_type vals_len = vals.numel ();
12835
12836 if (vals_len > 1)
12837 return ovl (vals);
12838 else if (vals_len == 1)
12839 return ovl (vals(0));
12840 else
12841 return ovl ();
12842}
12843
12844static octave_value
12845make_graphics_object (const std::string& go_name,
12846 bool integer_figure_handle,
12847 const octave_value_list& args)
12848{
12849 octave_value retval;
12850
12851 double val = octave::numeric_limits<double>::NaN ();
12852
12853 octave_value_list xargs = args.splice (0, 1);
12854
12855 caseless_str p ("parent");
12856
12857 // Remove all "parent" property overrides of the first argument to function
12858 // and accept only the last one (bug #55322).
12859 for (int i = 0; i < xargs.length (); i += 2)
12860 {
12861 if (xargs(i).is_string () && p.compare (xargs(i).string_value ()))
12862 {
12863 if (i >= (xargs.length () - 1))
12864 error ("__go_%s__: missing value for parent property",
12865 go_name.c_str ());
12866
12867 val = xargs(i+1).double_value ();
12868
12869 xargs = xargs.splice (i, 2);
12870 i -= 2;
12871 }
12872 }
12873
12874 if (octave::math::isnan (val))
12875 val = args(0).xdouble_value ("__go_%s__: invalid parent", go_name.c_str ());
12876
12877 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
12878
12879 graphics_handle parent = gh_mgr.lookup (val);
12880
12881 if (! parent.ok ())
12882 error ("__go_%s__: invalid parent", go_name.c_str ());
12883
12885
12886 try
12887 {
12888 h = gh_mgr.make_graphics_handle (go_name, parent,
12889 integer_figure_handle, false, false);
12890 }
12891 catch (octave::execution_exception& ee)
12892 {
12893 error (ee, "__go_%s__: %s, unable to create graphics handle",
12894 go_name.c_str (), ee.message ().c_str ());
12895 }
12896
12897 try
12898 {
12899 xset (h, xargs);
12900 }
12901 catch (octave::execution_exception& ee)
12902 {
12904 error (ee, "__go_%s__: %s, unable to create graphics handle",
12905 go_name.c_str (), ee.message ().c_str ());
12906 }
12907
12908 adopt (parent, h);
12909
12910 xcreatefcn (h);
12911 xinitialize (h);
12912
12913 retval = h.value ();
12914
12915 Vdrawnow_requested = true;
12916
12917 return retval;
12918}
12919
12920DEFMETHOD (__go_figure__, interp, args, ,
12921 doc: /* -*- texinfo -*-
12922@deftypefn {} {@var{hfig} =} __go_figure__ (@var{fignum})
12923Undocumented internal function.
12924@end deftypefn */)
12925{
12926 gh_manager& gh_mgr = interp.get_gh_manager ();
12927
12928 octave::autolock guard (gh_mgr.graphics_lock ());
12929
12930 if (args.length () == 0)
12931 print_usage ();
12932
12933 double val = args(0).xdouble_value ("__go_figure__: figure number must be a double value");
12934
12935 octave_value retval;
12936
12937 if (isfigure (val))
12938 {
12939 graphics_handle h = gh_mgr.lookup (val);
12940
12941 xset (h, args.splice (0, 1));
12942
12943 retval = h.value ();
12944 }
12945 else
12946 {
12947 bool int_fig_handle = true;
12948
12949 octave_value_list xargs = args.splice (0, 1);
12950
12951 graphics_handle h = octave::numeric_limits<double>::NaN ();
12952
12953 if (octave::math::isnan (val))
12954 {
12955 caseless_str pname ("integerhandle");
12956
12957 for (int i = 0; i < xargs.length (); i++)
12958 {
12959 if (xargs(i).is_string ()
12960 && pname.compare (xargs(i).string_value ()))
12961 {
12962 if (i < (xargs.length () - 1))
12963 {
12964 std::string pval = xargs(i+1).string_value ();
12965
12966 caseless_str on ("on");
12967 int_fig_handle = on.compare (pval);
12968 xargs = xargs.splice (i, 2);
12969
12970 break;
12971 }
12972 }
12973 }
12974
12975 h = gh_mgr.make_graphics_handle ("figure", 0, int_fig_handle,
12976 false, false);
12977
12978 if (! int_fig_handle)
12979 {
12980 // We need to initialize the integerhandle property
12981 // without calling the set_integerhandle method,
12982 // because doing that will generate a new handle value...
12983 graphics_object go = gh_mgr.get_object (h);
12984 go.get_properties ().init_integerhandle ("off");
12985 }
12986 }
12987 else if (val > 0 && octave::math::is_integer (val))
12988 h = gh_mgr.make_figure_handle (val, false);
12989
12990 if (! h.ok ())
12991 error ("__go_figure__: failed to create figure handle");
12992
12993 try
12994 {
12995 xset (h, xargs);
12996 }
12997 catch (octave::execution_exception& ee)
12998 {
13000 error (ee, "__go_figure__: unable to create figure handle");
13001 }
13002
13003 adopt (0, h);
13004
13005 gh_mgr.push_figure (h);
13006
13007 xcreatefcn (h);
13008 xinitialize (h);
13009
13010 retval = h.value ();
13011 }
13012
13013 return retval;
13014}
13015
13016#define GO_BODY(TYPE) \
13017 gh_manager& gh_mgr = interp.get_gh_manager (); \
13018 \
13019 octave::autolock guard (gh_mgr.graphics_lock ()); \
13020 \
13021 if (args.length () == 0) \
13022 print_usage (); \
13023 \
13024 return octave_value (make_graphics_object (#TYPE, false, args)); \
13025
13026int
13027calc_dimensions (const graphics_object& go)
13028{
13029 int nd = 2;
13030
13031 if (go.isa ("surface"))
13032 nd = 3;
13033 else if ((go.isa ("line") || go.isa ("patch") || go.isa ("scatter"))
13034 && ! go.get ("zdata").isempty ())
13035 nd = 3;
13036 else
13037 {
13038 Matrix kids = go.get_properties ().get_children ();
13039
13040 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
13041
13042 for (octave_idx_type i = 0; i < kids.numel (); i++)
13043 {
13044 graphics_handle hkid = gh_mgr.lookup (kids(i));
13045
13046 if (hkid.ok ())
13047 {
13048 const graphics_object& kid = gh_mgr.get_object (hkid);
13049
13050 if (kid.valid_object ())
13051 nd = calc_dimensions (kid);
13052
13053 if (nd == 3)
13054 break;
13055 }
13056 }
13057 }
13058
13059 return nd;
13060}
13061
13062DEFMETHOD (__calc_dimensions__, interp, args, ,
13063 doc: /* -*- texinfo -*-
13064@deftypefn {} {@var{ndims} =} __calc_dimensions__ (@var{axes})
13065Internal function.
13066
13067Determine the number of dimensions in a graphics object, either 2 or 3.
13068@end deftypefn */)
13069{
13070 gh_manager& gh_mgr = interp.get_gh_manager ();
13071
13072 octave::autolock guard (gh_mgr.graphics_lock ());
13073
13074 if (args.length () != 1)
13075 print_usage ();
13076
13077 double h = args(0).xdouble_value ("__calc_dimensions__: first argument must be a graphics handle");
13078
13079 return ovl (calc_dimensions (gh_mgr.get_object (h)));
13080}
13081
13082DEFMETHOD (__go_axes__, interp, args, ,
13083 doc: /* -*- texinfo -*-
13084@deftypefn {} {@var{hax} =} __go_axes__ (@var{parent})
13085Undocumented internal function.
13086@end deftypefn */)
13087{
13088 GO_BODY (axes);
13089}
13090
13091DEFMETHOD (__go_line__, interp, args, ,
13092 doc: /* -*- texinfo -*-
13093@deftypefn {} {@var{hl} =} __go_line__ (@var{parent})
13094Undocumented internal function.
13095@end deftypefn */)
13096{
13097 GO_BODY (line);
13098}
13099
13100DEFMETHOD (__go_text__, interp, args, ,
13101 doc: /* -*- texinfo -*-
13102@deftypefn {} {@var{ht} =} __go_text__ (@var{parent})
13103Undocumented internal function.
13104@end deftypefn */)
13105{
13106 GO_BODY (text);
13107}
13108
13109DEFMETHOD (__go_image__, interp, args, ,
13110 doc: /* -*- texinfo -*-
13111@deftypefn {} {@var{hi} =} __go_image__ (@var{parent})
13112Undocumented internal function.
13113@end deftypefn */)
13114{
13115 GO_BODY (image);
13116}
13117
13118DEFMETHOD (__go_surface__, interp, args, ,
13119 doc: /* -*- texinfo -*-
13120@deftypefn {} {@var{hs} =} __go_surface__ (@var{parent})
13121Undocumented internal function.
13122@end deftypefn */)
13123{
13124 GO_BODY (surface);
13125}
13126
13127DEFMETHOD (__go_patch__, interp, args, ,
13128 doc: /* -*- texinfo -*-
13129@deftypefn {} {@var{hp} =} __go_patch__ (@var{parent})
13130Undocumented internal function.
13131@end deftypefn */)
13132{
13133 GO_BODY (patch);
13134}
13135
13136DEFMETHOD (__go_scatter__, interp, args, ,
13137 doc: /* -*- texinfo -*-
13138@deftypefn {} {@var{hs} =} __go_scatter__ (@var{parent})
13139Undocumented internal function.
13140@end deftypefn */)
13141{
13142 GO_BODY (scatter);
13143}
13144
13145DEFMETHOD (__go_light__, interp, args, ,
13146 doc: /* -*- texinfo -*-
13147@deftypefn {} {@var{hl} =} __go_light__ (@var{parent})
13148Undocumented internal function.
13149@end deftypefn */)
13150{
13151 GO_BODY (light);
13152}
13153
13154DEFMETHOD (__go_hggroup__, interp, args, ,
13155 doc: /* -*- texinfo -*-
13156@deftypefn {} {@var{hgg} =} __go_hggroup__ (@var{parent})
13157Undocumented internal function.
13158@end deftypefn */)
13159{
13160 GO_BODY (hggroup);
13161}
13162
13163DEFMETHOD (__go_uimenu__, interp, args, ,
13164 doc: /* -*- texinfo -*-
13165@deftypefn {} {@var{hui} =} __go_uimenu__ (@var{parent})
13166Undocumented internal function.
13167@end deftypefn */)
13168{
13169 GO_BODY (uimenu);
13170}
13171
13172DEFMETHOD (__go_uicontrol__, interp, args, ,
13173 doc: /* -*- texinfo -*-
13174@deftypefn {} {@var{hui} =} __go_uicontrol__ (@var{parent})
13175Undocumented internal function.
13176@end deftypefn */)
13177{
13178 GO_BODY (uicontrol);
13179}
13180
13181DEFMETHOD (__go_uibuttongroup__, interp, args, ,
13182 doc: /* -*- texinfo -*-
13183@deftypefn {} {@var{hui} =} __go_uibuttongroup__ (@var{parent})
13184Undocumented internal function.
13185@end deftypefn */)
13186{
13187 GO_BODY (uibuttongroup);
13188}
13189
13190DEFMETHOD (__go_uipanel__, interp, args, ,
13191 doc: /* -*- texinfo -*-
13192@deftypefn {} {@var{hui} =} __go_uipanel__ (@var{parent})
13193Undocumented internal function.
13194@end deftypefn */)
13195{
13196 GO_BODY (uipanel);
13197}
13198
13199DEFMETHOD (__go_uicontextmenu__, interp, args, ,
13200 doc: /* -*- texinfo -*-
13201@deftypefn {} {@var{hui} =} __go_uicontextmenu__ (@var{parent})
13202Undocumented internal function.
13203@end deftypefn */)
13204{
13205 GO_BODY (uicontextmenu);
13206}
13207
13208DEFMETHOD (__go_uitable__, interp, args, ,
13209 doc: /* -*- texinfo -*-
13210@deftypefn {} {@var{hui} =} __go_uitable__ (@var{parent})
13211Undocumented internal function.
13212@end deftypefn */)
13213{
13214 GO_BODY (uitable);
13215}
13216
13217DEFMETHOD (__go_uitoolbar__, interp, args, ,
13218 doc: /* -*- texinfo -*-
13219@deftypefn {} {@var{hui} =} __go_uitoolbar__ (@var{parent})
13220Undocumented internal function.
13221@end deftypefn */)
13222{
13223 GO_BODY (uitoolbar);
13224}
13225
13226DEFMETHOD (__go_uipushtool__, interp, args, ,
13227 doc: /* -*- texinfo -*-
13228@deftypefn {} {@var{hui} =} __go_uipushtool__ (@var{parent})
13229Undocumented internal function.
13230@end deftypefn */)
13231{
13232 GO_BODY (uipushtool);
13233}
13234
13235DEFMETHOD (__go_uitoggletool__, interp, args, ,
13236 doc: /* -*- texinfo -*-
13237@deftypefn {} {@var{hui} =} __go_uitoggletool__ (@var{parent})
13238Undocumented internal function.
13239@end deftypefn */)
13240{
13241 GO_BODY (uitoggletool);
13242}
13243
13244DEFMETHOD (__go_delete__, interp, args, ,
13245 doc: /* -*- texinfo -*-
13246@deftypefn {} {} __go_delete__ (@var{h})
13247Undocumented internal function.
13248@end deftypefn */)
13249{
13250 gh_manager& gh_mgr = interp.get_gh_manager ();
13251
13252 octave::autolock guard (gh_mgr.graphics_lock ());
13253
13254 if (args.length () != 1)
13255 print_usage ();
13256
13257 graphics_handle h = octave::numeric_limits<double>::NaN ();
13258
13259 const NDArray vals = args(0).xarray_value ("delete: invalid graphics object");
13260
13261 // Check all the handles to delete are valid first,
13262 // as callbacks might delete one of the handles we later want to delete.
13263 for (octave_idx_type i = 0; i < vals.numel (); i++)
13264 {
13265 h = gh_mgr.lookup (vals(i));
13266
13267 if (! h.ok ())
13268 error ("delete: invalid graphics object (= %g)", vals(i));
13269 }
13270
13272
13273 return ovl ();
13274}
13275
13276DEFMETHOD (__go_handles__, interp, args, ,
13277 doc: /* -*- texinfo -*-
13278@deftypefn {} {@var{hg_list} =} __go_handles__ (@var{show_hidden})
13279Undocumented internal function.
13280@end deftypefn */)
13281{
13282 gh_manager& gh_mgr = interp.get_gh_manager ();
13283
13284 octave::autolock guard (gh_mgr.graphics_lock ());
13285
13286 bool show_hidden = false;
13287
13288 if (args.length () > 0)
13289 show_hidden = args(0).bool_value ();
13290
13291 return ovl (gh_mgr.handle_list (show_hidden));
13292}
13293
13294DEFMETHOD (__go_figure_handles__, interp, args, ,
13295 doc: /* -*- texinfo -*-
13296@deftypefn {} {@var{hfig_list} =} __go_figure_handles__ (@var{show_hidden})
13297Undocumented internal function.
13298@end deftypefn */)
13299{
13300 gh_manager& gh_mgr = interp.get_gh_manager ();
13301
13302 octave::autolock guard (gh_mgr.graphics_lock ());
13303
13304 bool show_hidden = false;
13305
13306 if (args.length () > 0)
13307 show_hidden = args(0).bool_value ();
13308
13309 return ovl (gh_mgr.figure_handle_list (show_hidden));
13310}
13311
13312DEFMETHOD (__go_execute_callback__, interp, args, ,
13313 doc: /* -*- texinfo -*-
13314@deftypefn {} {} __go_execute_callback__ (@var{h}, @var{name})
13315@deftypefnx {} {} __go_execute_callback__ (@var{h}, @var{name}, @var{param})
13316Undocumented internal function.
13317@end deftypefn */)
13318{
13319 int nargin = args.length ();
13320
13321 if (nargin < 2 || nargin > 3)
13322 print_usage ();
13323
13324 const NDArray vals = args(0).xarray_value ("__go_execute_callback__: invalid graphics object");
13325
13326 std::string name = args(1).xstring_value ("__go_execute_callback__: invalid callback name");
13327
13328 gh_manager& gh_mgr = interp.get_gh_manager ();
13329
13330 for (octave_idx_type i = 0; i < vals.numel (); i++)
13331 {
13332 double val = vals(i);
13333
13334 graphics_handle h = gh_mgr.lookup (val);
13335
13336 if (! h.ok ())
13337 error ("__go_execute_callback__: invalid graphics object (= %g)", val);
13338
13339 if (nargin == 2)
13340 gh_mgr.execute_callback (h, name);
13341 else
13342 gh_mgr.execute_callback (h, name, args(2));
13343 }
13344
13345 return ovl ();
13346}
13347
13348DEFMETHOD (__go_post_callback__, interp, args, ,
13349 doc: /* -*- texinfo -*-
13350@deftypefn {} {} __go_post_callback__ (@var{h}, @var{name})
13351@deftypefnx {} {} __go_post_callback__ (@var{h}, @var{name}, @var{param})
13352Undocumented internal function.
13353@end deftypefn */)
13354{
13355 int nargin = args.length ();
13356
13357 if (nargin < 2 || nargin > 3)
13358 print_usage ();
13359
13360 const NDArray vals = args(0).xarray_value ("__go_post_callback__: invalid graphics object");
13361
13362 std::string name = args(1).xstring_value ("__go_post_callback__: invalid callback name");
13363
13364 gh_manager& gh_mgr = interp.get_gh_manager ();
13365
13366 for (octave_idx_type i = 0; i < vals.numel (); i++)
13367 {
13368 double val = vals(i);
13369
13370 graphics_handle h = gh_mgr.lookup (val);
13371
13372 if (! h.ok ())
13373 error ("__go_execute_callback__: invalid graphics object (= %g)", val);
13374
13375 if (nargin == 2)
13376 gh_mgr.post_callback (h, name);
13377 else
13378 gh_mgr.post_callback (h, name, args(2));
13379 }
13380
13381 return ovl ();
13382}
13383
13384DEFMETHOD (__image_pixel_size__, interp, args, ,
13385 doc: /* -*- texinfo -*-
13386@deftypefn {} {@var{sz} =} __image_pixel_size__ (@var{h})
13387Internal function: returns the pixel size of the image in normalized units.
13388@end deftypefn */)
13389{
13390 if (args.length () != 1)
13391 print_usage ();
13392
13393 gh_manager& gh_mgr = interp.get_gh_manager ();
13394
13395 double h = args(0).xdouble_value ("__image_pixel_size__: argument is not a handle");
13396
13397 graphics_object go = gh_mgr.get_object (h);
13398
13399 if (! go || ! go.isa ("image"))
13400 error ("__image_pixel_size__: object is not an image");
13401
13402 image::properties& ip
13403 = dynamic_cast<image::properties&> (go.get_properties ());
13404
13405 Matrix dp = Matrix (1, 2);
13406 dp(0) = ip.pixel_xsize ();
13407 dp(1) = ip.pixel_ysize ();
13408 return ovl (dp);
13409}
13410
13411DEFMETHOD (available_graphics_toolkits, interp, , ,
13412 doc: /* -*- texinfo -*-
13413@deftypefn {} {@var{toolkits} =} available_graphics_toolkits ()
13414Return a cell array of registered graphics toolkits.
13415@seealso{graphics_toolkit, register_graphics_toolkit}
13416@end deftypefn */)
13417{
13418 gh_manager& gh_mgr = interp.get_gh_manager ();
13419
13420 octave::autolock guard (gh_mgr.graphics_lock ());
13421
13422 octave::gtk_manager& gtk_mgr = interp.get_gtk_manager ();
13423
13424 return ovl (gtk_mgr.available_toolkits_list ());
13425}
13426
13427DEFMETHOD (register_graphics_toolkit, interp, args, ,
13428 doc: /* -*- texinfo -*-
13429@deftypefn {} {} register_graphics_toolkit ("@var{toolkit}")
13430List @var{toolkit} as an available graphics toolkit.
13431
13432Programming Note: No input validation is done on the input string; it is simply
13433added to the list of possible graphics toolkits.
13434@seealso{available_graphics_toolkits}
13435@end deftypefn */)
13436{
13437 gh_manager& gh_mgr = interp.get_gh_manager ();
13438
13439 octave::autolock guard (gh_mgr.graphics_lock ());
13440
13441 if (args.length () != 1)
13442 print_usage ();
13443
13444 std::string name = args(0).xstring_value ("register_graphics_toolkit: TOOLKIT must be a string");
13445
13446 octave::gtk_manager& gtk_mgr = interp.get_gtk_manager ();
13447
13448 gtk_mgr.register_toolkit (name);
13449
13450 return ovl ();
13451}
13452
13453DEFMETHOD (loaded_graphics_toolkits, interp, , ,
13454 doc: /* -*- texinfo -*-
13455@deftypefn {} {@var{toolkits} =} loaded_graphics_toolkits ()
13456Return a cell array of the currently loaded graphics toolkits.
13457@seealso{available_graphics_toolkits}
13458@end deftypefn */)
13459{
13460 gh_manager& gh_mgr = interp.get_gh_manager ();
13461
13462 octave::autolock guard (gh_mgr.graphics_lock ());
13463
13464 octave::gtk_manager& gtk_mgr = interp.get_gtk_manager ();
13465
13466 return ovl (gtk_mgr.loaded_toolkits_list ());
13467}
13468
13469DEFMETHOD (__show_figure__, interp, args, ,
13470 doc: /* -*- texinfo -*-
13471@deftypefn {} {} __show_figure__ (@var{n})
13472Undocumented internal function.
13473@end deftypefn */)
13474{
13475 if (args.length () != 1)
13476 print_usage ();
13477
13478 gh_manager& gh_mgr = interp.get_gh_manager ();
13479
13480 double h = args(0).xdouble_value ("__show_figure__: invalid handle H");
13481
13482 graphics_handle gh = gh_mgr.lookup (h);
13483
13484 if (! gh.ok ())
13485 error ("__show_figure__: invalid graphics object (= %g)", h);
13486
13487 graphics_object go = gh_mgr.get_object (gh);
13488
13489 figure::properties& fprops
13490 = dynamic_cast<figure::properties&> (go.get_properties ());
13491
13492 fprops.get_toolkit ().show_figure (go);
13493
13494 return ovl ();
13495}
13496
13497DEFMETHOD (drawnow, interp, args, ,
13498 doc: /* -*- texinfo -*-
13499@deftypefn {} {} drawnow ()
13500@deftypefnx {} {} drawnow ("expose")
13501@deftypefnx {} {} drawnow (@var{term}, @var{file}, @var{debug_file})
13502Update figure windows and their children.
13503
13504The event queue is flushed and any callbacks generated are executed.
13505
13506With the optional argument @qcode{"expose"}, only graphic objects are
13507updated and no other events or callbacks are processed.
13508
13509The third calling form of @code{drawnow} is for debugging and is
13510undocumented.
13511@seealso{refresh}
13512@end deftypefn */)
13513{
13514 if (args.length () > 3)
13515 print_usage ();
13516
13517 octave::unwind_protect_var<bool> restore_var (Vdrawnow_requested, false);
13518
13519 // Redraw unless we are in the middle of a deletion.
13520
13521 if (! delete_executing)
13522 {
13523 gh_manager& gh_mgr = interp.get_gh_manager ();
13524
13525 octave::autolock guard (gh_mgr.graphics_lock ());
13526
13527 if (args.length () <= 1)
13528 {
13529 // First process events so that the redraw happens when all
13530 // objects are in their definite state.
13531 bool do_events = true;
13532
13533 if (args.length () == 1)
13534 {
13535 caseless_str val (args(0).xstring_value ("drawnow: first argument must be a string"));
13536
13537 if (val.compare ("expose"))
13538 do_events = false;
13539 else
13540 error ("drawnow: invalid argument, 'expose' is only valid option");
13541 }
13542
13543 if (do_events)
13544 {
13545 gh_mgr.unlock ();
13546
13547 gh_mgr.process_events ();
13548
13549 gh_mgr.lock ();
13550 }
13551
13552 Matrix hlist = gh_mgr.figure_handle_list (true);
13553
13554 // Redraw modified figures
13555 for (int i = 0; i < hlist.numel (); i++)
13556 {
13557 graphics_handle h = gh_mgr.lookup (hlist(i));
13558
13559 if (h.ok () && h != 0)
13560 {
13561 graphics_object go = gh_mgr.get_object (h);
13562 figure::properties& fprops
13563 = dynamic_cast<figure::properties&> (go.get_properties ());
13564
13565 if (fprops.is_modified ())
13566 {
13567 if (fprops.is_visible ())
13568 {
13569 gh_mgr.unlock ();
13570
13571 fprops.get_toolkit ().redraw_figure (go);
13572
13573 gh_mgr.lock ();
13574 }
13575
13576 fprops.set_modified (false);
13577 }
13578 }
13579
13580 }
13581 }
13582 else if (args.length () >= 2 && args.length () <= 3)
13583 {
13584 std::string term, file, debug_file;
13585
13586 term = args(0).xstring_value ("drawnow: TERM must be a string");
13587
13588 file = args(1).xstring_value ("drawnow: FILE must be a string");
13589
13590 if (file.empty ())
13591 error ("drawnow: empty output ''");
13592 else if (file.length () == 1 && file[0] == '|')
13593 error ("drawnow: empty pipe '|'");
13594 else if (file[0] != '|')
13595 {
13596 std::size_t pos = file.find_last_of (octave::sys::file_ops::dir_sep_chars ());
13597
13598 if (pos != std::string::npos)
13599 {
13600 std::string dirname = file.substr (0, pos+1);
13601
13602 if (! octave::sys::dir_exists (dirname))
13603 error ("drawnow: nonexistent directory '%s'",
13604 dirname.c_str ());
13605
13606 }
13607 }
13608
13609 debug_file = (args.length () > 2 ? args(2).xstring_value ("drawnow: DEBUG_FILE must be a string") :
13610 "");
13611
13612 graphics_handle h = gcf ();
13613
13614 if (! h.ok ())
13615 error ("drawnow: nothing to draw");
13616
13617 graphics_object go = gh_mgr.get_object (h);
13618
13619 gh_mgr.unlock ();
13620
13621 go.get_toolkit ().print_figure (go, term, file, debug_file);
13622
13623 gh_mgr.lock ();
13624 }
13625 }
13626
13627 return ovl ();
13628}
13629
13630DEFMETHOD (addlistener, interp, args, ,
13631 doc: /* -*- texinfo -*-
13632@deftypefn {} {} addlistener (@var{h}, @var{prop}, @var{fcn})
13633Register @var{fcn} as listener for the property @var{prop} of the graphics
13634object @var{h}.
13635
13636Property listeners are executed (in order of registration) when the property
13637is set. The new value is already available when the listeners are executed.
13638
13639@var{prop} must be a string naming a valid property in @var{h}.
13640
13641@var{fcn} can be a function handle, a string or a cell array whose first
13642element is a function handle. If @var{fcn} is a function handle, the
13643corresponding function should accept at least 2 arguments, that will be
13644set to the object handle and the empty matrix respectively. If @var{fcn}
13645is a string, it must be any valid octave expression. If @var{fcn} is a cell
13646array, the first element must be a function handle with the same signature
13647as described above. The next elements of the cell array are passed
13648as additional arguments to the function.
13649
13650Example:
13651
13652@example
13653@group
13654function my_listener (h, dummy, p1)
13655 fprintf ("my_listener called with p1=%s\n", p1);
13656endfunction
13657
13658addlistener (gcf, "position", @{@@my_listener, "my string"@})
13659@end group
13660@end example
13661
13662@seealso{dellistener, addproperty, hggroup}
13663@end deftypefn */)
13664{
13665 gh_manager& gh_mgr = interp.get_gh_manager ();
13666
13667 octave::autolock guard (gh_mgr.graphics_lock ());
13668
13669 int nargin = args.length ();
13670
13671 if (nargin < 3 || nargin > 4)
13672 print_usage ();
13673
13674 double h = args(0).xdouble_value ("addlistener: invalid handle H");
13675
13676 std::string pname = args(1).xstring_value ("addlistener: PROP must be a string");
13677
13678 graphics_handle gh = gh_mgr.lookup (h);
13679
13680 if (! gh.ok ())
13681 error ("addlistener: invalid graphics object (= %g)", h);
13682
13683 graphics_object go = gh_mgr.get_object (gh);
13684
13685 go.add_property_listener (pname, args(2), GCB_POSTSET);
13686
13687 if (args.length () == 4)
13688 {
13689 caseless_str persistent = args(3).string_value ();
13690 if (persistent.compare ("persistent"))
13691 go.add_property_listener (pname, args(2), GCB_PERSISTENT);
13692 }
13693
13694 return ovl ();
13695}
13696
13697DEFMETHOD (dellistener, interp, args, ,
13698 doc: /* -*- texinfo -*-
13699@deftypefn {} {} dellistener (@var{h}, @var{prop}, @var{fcn})
13700Remove the registration of @var{fcn} as a listener for the property
13701@var{prop} of the graphics object @var{h}.
13702
13703The function @var{fcn} must be the same variable (not just the same value),
13704as was passed to the original call to @code{addlistener}.
13705
13706If @var{fcn} is not defined then all listener functions of @var{prop}
13707are removed.
13708
13709Example:
13710
13711@example
13712@group
13713function my_listener (h, dummy, p1)
13714 fprintf ("my_listener called with p1=%s\n", p1);
13715endfunction
13716
13717c = @{@@my_listener, "my string"@};
13718addlistener (gcf, "position", c);
13719dellistener (gcf, "position", c);
13720@end group
13721@end example
13722
13723@seealso{addlistener}
13724@end deftypefn */)
13725{
13726 gh_manager& gh_mgr = interp.get_gh_manager ();
13727
13728 octave::autolock guard (gh_mgr.graphics_lock ());
13729
13730 if (args.length () < 2 || args.length () > 3)
13731 print_usage ();
13732
13733 double h = args(0).xdouble_value ("dellistener: invalid handle");
13734
13735 std::string pname = args(1).xstring_value ("dellistener: PROP must be a string");
13736
13737 graphics_handle gh = gh_mgr.lookup (h);
13738
13739 if (! gh.ok ())
13740 error ("dellistener: invalid graphics object (= %g)", h);
13741
13742 graphics_object go = gh_mgr.get_object (gh);
13743
13744 if (args.length () == 2)
13745 go.delete_property_listener (pname, octave_value (), GCB_POSTSET);
13746 else
13747 {
13748 if (args(2).is_string ()
13749 && args(2).string_value () == "persistent")
13750 {
13751 go.delete_property_listener (pname, octave_value (),
13752 GCB_PERSISTENT);
13753 go.delete_property_listener (pname, octave_value (),
13754 GCB_POSTSET);
13755 }
13756 else
13757 go.delete_property_listener (pname, args(2), GCB_POSTSET);
13758 }
13759
13760 return ovl ();
13761}
13762
13763DEFMETHOD (addproperty, interp, args, ,
13764 doc: /* -*- texinfo -*-
13765@deftypefn {} {} addproperty (@var{name}, @var{h}, @var{type})
13766@deftypefnx {} {} addproperty (@var{name}, @var{h}, @var{type}, @var{arg}, @dots{})
13767Create a new property named @var{name} in graphics object @var{h}.
13768
13769@var{type} determines the type of the property to create. @var{args}
13770usually contains the default value of the property, but additional
13771arguments might be given, depending on the type of the property.
13772
13773The supported property types are:
13774
13775@table @code
13776@item string
13777A string property. @var{arg} contains the default string value.
13778
13779@item any
13780An @nospell{un-typed} property. This kind of property can hold any octave
13781value. @var{args} contains the default value.
13782
13783@item radio
13784A string property with a limited set of accepted values. The first
13785argument must be a string with all accepted values separated by
13786a vertical bar ('|'). The default value can be marked by enclosing
13787it with a '@{' '@}' pair. The default value may also be given as
13788an optional second string argument.
13789
13790@item boolean
13791A boolean property. This property type is equivalent to a radio
13792property with "on|off" as accepted values. @var{arg} contains
13793the default property value.
13794
13795@item double
13796A scalar double property. @var{arg} contains the default value.
13797
13798@item handle
13799A handle property. This kind of property holds the handle of a
13800graphics object. @var{arg} contains the default handle value.
13801When no default value is given, the property is initialized to
13802the empty matrix.
13803
13804@item data
13805A data (matrix) property. @var{arg} contains the default data
13806value. When no default value is given, the data is initialized to
13807the empty matrix.
13808
13809@item color
13810A color property. @var{arg} contains the default color value.
13811When no default color is given, the property is set to black.
13812An optional second string argument may be given to specify an
13813additional set of accepted string values (like a radio property).
13814@end table
13815
13816@var{type} may also be the concatenation of a core object type and
13817a valid property name for that object type. The property created
13818then has the same characteristics as the referenced property (type,
13819possible values, hidden state@dots{}). This allows one to clone an
13820existing property into the graphics object @var{h}.
13821
13822Examples:
13823
13824@example
13825@group
13826addproperty ("my_property", gcf, "string", "a string value");
13827addproperty ("my_radio", gcf, "radio", "val_1|val_2|@{val_3@}");
13828addproperty ("my_style", gcf, "linelinestyle", "--");
13829@end group
13830@end example
13831
13832@seealso{addlistener, hggroup}
13833@end deftypefn */)
13834{
13835 gh_manager& gh_mgr = interp.get_gh_manager ();
13836
13837 octave::autolock guard (gh_mgr.graphics_lock ());
13838
13839 if (args.length () < 3)
13840 print_usage ();
13841
13842 std::string name = args(0).xstring_value ("addproperty: NAME must be a string");
13843
13844 double h = args(1).xdouble_value ("addproperty: invalid handle H");
13845
13846 graphics_handle gh = gh_mgr.lookup (h);
13847
13848 if (! gh.ok ())
13849 error ("addproperty: invalid graphics object (= %g)", h);
13850
13851 graphics_object go = gh_mgr.get_object (gh);
13852
13853 std::string type = args(2).xstring_value ("addproperty: TYPE must be a string");
13854
13855 if (go.get_properties ().has_property (name))
13856 error ("addproperty: a '%s' property already exists in the graphics object",
13857 name.c_str ());
13858
13859 property p = property::create (name, gh, type, args.splice (0, 3));
13860
13861 go.get_properties ().insert_property (name, p);
13862
13863 return ovl ();
13864}
13865
13867get_property_from_handle (double handle, const std::string& property,
13868 const std::string& fcn)
13869{
13870 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
13871
13872 octave::autolock guard (gh_mgr.graphics_lock ());
13873
13874 graphics_object go = gh_mgr.get_object (handle);
13875
13876 if (! go)
13877 error ("%s: invalid handle (= %g)", fcn.c_str (), handle);
13878
13879 return go.get (caseless_str (property));
13880}
13881
13882bool
13883set_property_in_handle (double handle, const std::string& property,
13884 const octave_value& arg, const std::string& fcn)
13885{
13886 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
13887
13888 octave::autolock guard (gh_mgr.graphics_lock ());
13889
13890 graphics_object go = gh_mgr.get_object (handle);
13891
13892 if (! go)
13893 error ("%s: invalid handle (= %g)", fcn.c_str (), handle);
13894
13895 go.set (caseless_str (property), arg);
13896
13897 return true;
13898}
13899
13900static bool
13901compare_property_values (octave::interpreter& interp,
13902 const octave_value& ov1, const octave_value& ov2)
13903{
13904 octave_value_list args(2);
13905
13906 args(0) = ov1;
13907 args(1) = ov2;
13908
13909 octave_value_list result = interp.feval ("isequal", args, 1);
13910
13911 if (result.length () > 0)
13912 return result(0).bool_value ();
13913
13914 return false;
13915}
13916
13917static std::map<uint32_t, bool> waitfor_results;
13918
13919static void
13920cleanup_waitfor_id (uint32_t id)
13921{
13922 waitfor_results.erase (id);
13923}
13924
13925static void
13926do_cleanup_waitfor_listener (const octave_value& listener,
13927 listener_mode mode = GCB_POSTSET)
13928{
13929 Cell c = listener.cell_value ();
13930
13931 if (c.numel () >= 4)
13932 {
13933 double h = c(2).double_value ();
13934
13935 caseless_str pname = c(3).string_value ();
13936
13937 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
13938
13939 octave::autolock guard (gh_mgr.graphics_lock ());
13940
13941 graphics_handle gh = gh_mgr.lookup (h);
13942
13943 if (gh.ok ())
13944 {
13945 graphics_object go = gh_mgr.get_object (gh);
13946
13947 if (go.get_properties ().has_property (pname))
13948 {
13949 go.get_properties ().delete_listener (pname, listener, mode);
13950
13951 if (mode == GCB_POSTSET)
13952 go.get_properties ().delete_listener (pname, listener,
13953 GCB_PERSISTENT);
13954 }
13955 }
13956 }
13957}
13958
13959static void
13960cleanup_waitfor_postset_listener (const octave_value& listener)
13961{ do_cleanup_waitfor_listener (listener, GCB_POSTSET); }
13962
13963static void
13964cleanup_waitfor_predelete_listener (const octave_value& listener)
13965{ do_cleanup_waitfor_listener (listener, GCB_PREDELETE); }
13966
13967DECLARE_STATIC_METHODX (waitfor_listener, interp, args, )
13968{
13969 if (args.length () > 3)
13970 {
13971 uint32_t id = args(2).uint32_scalar_value ().value ();
13972
13973 if (args.length () > 5)
13974 {
13975 double h = args(0).double_value ();
13976
13977 caseless_str pname = args(4).string_value ();
13978
13979 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
13980
13981 octave::autolock guard (gh_mgr.graphics_lock ());
13982
13983 graphics_handle gh = gh_mgr.lookup (h);
13984
13985 if (gh.ok ())
13986 {
13987 graphics_object go = gh_mgr.get_object (gh);
13988 octave_value pvalue = go.get (pname);
13989
13990 if (compare_property_values (interp, pvalue, args(5)))
13991 waitfor_results[id] = true;
13992 }
13993 }
13994 else
13995 waitfor_results[id] = true;
13996 }
13997
13998 return ovl ();
13999}
14000
14001DECLARE_STATIC_FUNX (waitfor_del_listener, args, )
14002{
14003 if (args.length () > 2)
14004 {
14005 uint32_t id = args(2).uint32_scalar_value ().value ();
14006
14007 waitfor_results[id] = true;
14008 }
14009
14010 return ovl ();
14011}
14012
14013DEFMETHOD (waitfor, interp, args, ,
14014 doc: /* -*- texinfo -*-
14015@deftypefn {} {} waitfor (@var{h})
14016@deftypefnx {} {} waitfor (@var{h}, @var{prop})
14017@deftypefnx {} {} waitfor (@var{h}, @var{prop}, @var{value})
14018@deftypefnx {} {} waitfor (@dots{}, "timeout", @var{timeout})
14019Suspend the execution of the current program until a condition is
14020satisfied on the graphics handle @var{h}.
14021
14022While the program is suspended graphics events are still processed normally,
14023allowing callbacks to modify the state of graphics objects. This function
14024is reentrant and can be called from a callback, while another @code{waitfor}
14025call is pending at the top-level.
14026
14027In the first form, program execution is suspended until the graphics object
14028@var{h} is destroyed. If the graphics handle is invalid or if @var{h} is
14029the root graphics handle and no property @var{prop} was provided, the function
14030returns immediately.
14031
14032In the second form, execution is suspended until the graphics object is
14033destroyed or the property named @var{prop} is modified. If the graphics
14034handle is invalid or the property does not exist, the function returns
14035immediately.
14036
14037In the third form, execution is suspended until the graphics object is
14038destroyed or the property named @var{prop} is set to @var{value}. The
14039function @code{isequal} is used to compare property values. If the graphics
14040handle is invalid, the property does not exist or the property is already
14041set to @var{value}, the function returns immediately.
14042
14043An optional timeout can be specified using the property @qcode{"timeout"}.
14044This timeout value is the number of seconds to wait for the condition to be
14045true. @var{timeout} must be at least 1. If a smaller value is specified, a
14046warning is issued and a value of 1 is used instead. If the timeout value is
14047not an integer, it is truncated towards 0.
14048
14049To define a condition on a property named @qcode{"timeout"}, use the string
14050@qcode{'@backslashchar{}timeout'} instead.
14051
14052In all cases, typing CTRL-C stops program execution immediately.
14053@seealso{waitforbuttonpress, isequal}
14054@end deftypefn */)
14055{
14056 if (args.length () == 0)
14057 print_usage ();
14058
14059 // return immediately if the graphics handle is invalid
14060 if (args(0).isempty ())
14061 return ovl ();
14062
14063 double h = args(0).xdouble_value ("waitfor: invalid handle value");
14064
14065 if (! ishghandle (h) || (h == 0 && args.length () == 1))
14066 return ovl ();
14067
14068 caseless_str pname;
14069
14070 octave::unwind_action cleanup_waitfor_id_action;
14071 octave::unwind_action cleanup_waitfor_postset_listener_action;
14072 octave::unwind_action cleanup_waitfor_predelete_listener_action;
14073
14074 static uint32_t id_counter = 0;
14075 uint32_t id = 0;
14076
14077 int max_arg_index = 0;
14078 int timeout_index = -1;
14079
14080 double timeout = 0;
14081
14082 gh_manager& gh_mgr = interp.get_gh_manager ();
14083
14084 if (args.length () > 1)
14085 {
14086 pname = args(1).xstring_value ("waitfor: PROP must be a string");
14087
14088 if (pname.empty ())
14089 error ("waitfor: PROP must be a non-empty string");
14090
14091 if (pname != "timeout")
14092 {
14093 if (pname.compare (R"(\timeout)"))
14094 pname = "timeout";
14095
14096 static octave_value wf_listener;
14097
14098 if (! wf_listener.is_defined ())
14099 wf_listener
14100 = octave_value (new octave_builtin (waitfor_listener,
14101 "waitfor_listener"));
14102
14103 max_arg_index++;
14104 if (args.length () > 2)
14105 {
14106 if (args(2).is_string ())
14107 {
14108 caseless_str s = args(2).string_value ();
14109
14110 if (s.compare ("timeout"))
14111 timeout_index = 2;
14112 else
14113 max_arg_index++;
14114 }
14115 else
14116 max_arg_index++;
14117 }
14118
14119 Cell listener (1, max_arg_index >= 2 ? 5 : 4);
14120
14121 id = id_counter++;
14122 cleanup_waitfor_id_action.set (cleanup_waitfor_id, id);
14123 waitfor_results[id] = false;
14124
14125 listener(0) = wf_listener;
14126 listener(1) = octave_uint32 (id);
14127 listener(2) = h;
14128 listener(3) = pname;
14129
14130 if (max_arg_index >= 2)
14131 listener(4) = args(2);
14132
14133 octave_value ov_listener (listener);
14134
14135 octave::autolock guard (gh_mgr.graphics_lock ());
14136
14137 graphics_handle gh = gh_mgr.lookup (h);
14138
14139 if (gh.ok ())
14140 {
14141 graphics_object go = gh_mgr.get_object (gh);
14142
14143 if (max_arg_index >= 2
14144 && compare_property_values (interp, go.get (pname), args(2)))
14145 waitfor_results[id] = true;
14146 else
14147 {
14148 cleanup_waitfor_postset_listener_action.set
14149 (cleanup_waitfor_postset_listener, ov_listener);
14150
14151 go.add_property_listener (pname, ov_listener, GCB_POSTSET);
14152 go.add_property_listener (pname, ov_listener, GCB_PERSISTENT);
14153
14154 if (go.get_properties ().has_dynamic_property (pname))
14155 {
14156 static octave_value wf_del_listener;
14157
14158 if (! wf_del_listener.is_defined ())
14159 wf_del_listener
14161 (waitfor_del_listener,
14162 "waitfor_del_listener"));
14163
14164 Cell del_listener (1, 4);
14165
14166 del_listener(0) = wf_del_listener;
14167 del_listener(1) = octave_uint32 (id);
14168 del_listener(2) = h;
14169 del_listener(3) = pname;
14170
14171 octave_value ov_del_listener (del_listener);
14172
14173 cleanup_waitfor_predelete_listener_action.set
14174 (cleanup_waitfor_predelete_listener, ov_del_listener);
14175
14176 go.add_property_listener (pname, ov_del_listener,
14177 GCB_PREDELETE);
14178 }
14179 }
14180 }
14181 }
14182 }
14183
14184 if (timeout_index < 0 && args.length () > (max_arg_index + 1))
14185 {
14186 caseless_str s = args(max_arg_index +
14187 1).xstring_value ("waitfor: invalid parameter, expected 'timeout'");
14188
14189 if (! s.compare ("timeout"))
14190 error ("waitfor: invalid parameter '%s'", s.c_str ());
14191
14192 timeout_index = max_arg_index + 1;
14193 }
14194
14195 if (timeout_index >= 0)
14196 {
14197 if (args.length () <= (timeout_index + 1))
14198 error ("waitfor: missing TIMEOUT value");
14199
14200 timeout = args(timeout_index + 1).xscalar_value ("waitfor: TIMEOUT must be a scalar >= 1");
14201
14202 if (timeout < 1)
14203 {
14204 warning ("waitfor: TIMEOUT value must be >= 1, using 1 instead");
14205 timeout = 1;
14206 }
14207 }
14208
14209 // FIXME: There is still a "hole" in the following loop. The code
14210 // assumes that an object handle is unique, which is a fair
14211 // assumption, except for figures. If a figure is destroyed
14212 // then recreated with the same figure ID, within the same
14213 // run of event hooks, then the figure destruction won't be
14214 // caught and the loop will not stop. This is an unlikely
14215 // possibility in practice, though.
14216 //
14217 // Using deletefcn callback is also unreliable as it could be
14218 // modified during a callback execution and the waitfor loop
14219 // would not stop.
14220 //
14221 // The only "good" implementation would require object
14222 // listeners, similar to property listeners.
14223
14224 octave::sys::time start;
14225
14226 if (timeout > 0)
14227 start.stamp ();
14228
14229 while (true)
14230 {
14231 if (true)
14232 {
14233 octave::autolock guard (gh_mgr.graphics_lock ());
14234
14235 graphics_handle gh = gh_mgr.lookup (h);
14236
14237 if (gh.ok ())
14238 {
14239 if (! pname.empty () && waitfor_results[id])
14240 break;
14241 }
14242 else
14243 break;
14244 }
14245
14246 octave::sleep (0.1); // FIXME: really needed?
14247
14248 octave_quit ();
14249
14250 octave::command_editor::run_event_hooks ();
14251
14252 if (timeout > 0)
14253 {
14254 octave::sys::time now;
14255
14256 if (start + timeout < now)
14257 break;
14258 }
14259 }
14260
14261 return ovl ();
14262}
14263
14264DEFMETHOD (__zoom__, interp, args, ,
14265 doc: /* -*- texinfo -*-
14266@deftypefn {} {} __zoom__ (@var{axes}, @var{mode}, @var{factor})
14267@deftypefnx {} {} __zoom__ (@var{axes}, "out")
14268@deftypefnx {} {} __zoom__ (@var{axes}, "reset")
14269Undocumented internal function.
14270@end deftypefn */)
14271{
14272 int nargin = args.length ();
14273
14274 if (nargin != 2 && nargin != 3)
14275 print_usage ();
14276
14277 double h = args(0).double_value ();
14278
14279 gh_manager& gh_mgr = interp.get_gh_manager ();
14280
14281 octave::autolock guard (gh_mgr.graphics_lock ());
14282
14283 graphics_handle handle = gh_mgr.lookup (h);
14284
14285 if (! handle.ok ())
14286 error ("__zoom__: invalid handle");
14287
14288 graphics_object ax = gh_mgr.get_object (handle);
14289
14290 axes::properties& ax_props
14291 = dynamic_cast<axes::properties&> (ax.get_properties ());
14292
14293 if (nargin == 2)
14294 {
14295 std::string opt = args(1).string_value ();
14296
14297 if (opt == "out" || opt == "reset")
14298 {
14299 if (opt == "out")
14300 {
14301 ax_props.clear_zoom_stack ();
14302 Vdrawnow_requested = true;
14303 }
14304 else
14305 ax_props.clear_zoom_stack (false);
14306 }
14307 }
14308 else
14309 {
14310 std::string mode = args(1).string_value ();
14311 double factor = args(2).scalar_value ();
14312
14313 ax_props.zoom (mode, factor);
14314 Vdrawnow_requested = true;
14315 }
14316
14317 return ovl ();
14318}
14319
14320DEFMETHOD (__get_frame__, interp, args, ,
14321 doc: /* -*- texinfo -*-
14322@deftypefn {} {@var{cdata} =} __get_frame__ (@var{hfig})
14323Internal function.
14324
14325Return the pixel cdata of figure hfig in the form of a height-by-width-by-3
14326uint8 array.
14327@end deftypefn */)
14328{
14329 if (args.length () != 1)
14330 print_usage ();
14331
14332 double h = args(0).xdouble_value ("__get_frame__: HFIG is not a handle");
14333
14334 gh_manager& gh_mgr = interp.get_gh_manager ();
14335
14336 graphics_object go = gh_mgr.get_object (h);
14337
14338 if (! go || ! go.isa ("figure"))
14339 error ("__get_frame__: HFIG is not a figure");
14340
14341 // For Matlab compatibility, getframe must flush the event queue.
14342 gh_mgr.process_events ();
14343
14344 return ovl (go.get_toolkit ().get_pixels (go));
14345}
14346
14347DEFMETHOD (__get_position__, interp, args, ,
14348 doc: /* -*- texinfo -*-
14349@deftypefn {} {@var{pos} =} __get_position__ (@var{h}, @var{units})
14350Internal function.
14351
14352Return the position of the graphics object @var{h} in the specified
14353@var{units}.
14354@end deftypefn */)
14355{
14356 if (args.length () != 2)
14357 print_usage ();
14358
14359 double h
14360 = args(0).xdouble_value ("__get_position__: H must be a graphics handle");
14361
14362 std::string units
14363 = args(1).xstring_value ("__get_position__: UNITS must be a string");
14364
14365 gh_manager& gh_mgr = interp.get_gh_manager ();
14366
14367 graphics_object go = gh_mgr.get_object (h);
14368
14369 if (h == 0 || ! go)
14370 error ("__get_position__: H must be a handle to a valid graphics object");
14371
14372 graphics_object parent_go = gh_mgr.get_object (go.get_parent ());
14373 Matrix bbox = parent_go.get_properties ().get_boundingbox (true)
14374 .extract_n (0, 2, 1, 2);
14375
14376 Matrix pos = convert_position (go.get ("position").matrix_value (),
14377 go.get ("units").string_value (),
14378 units, bbox);
14379
14380 return ovl (pos);
14381}
14382
14383DEFUN (__get_system_fonts__, args, ,
14384 doc: /* -*- texinfo -*-
14385@deftypefn {} {@var{font_struct} =} __get_system_fonts__ ()
14386Internal function.
14387@end deftypefn */)
14388{
14389 if (args.length () != 0)
14390 print_usage ();
14391
14392 octave::text_renderer txt_renderer;
14393
14394 return ovl (txt_renderer.get_system_fonts ());
14395}
14396
14397OCTAVE_END_NAMESPACE(octave)
N Dimensional Array with copy-on-write semantics.
Definition Array-base.h:130
const dim_vector & dims() const
Return a const-reference so that dims ()(i) works efficiently.
Definition Array-base.h:529
T & elem(octave_idx_type n)
Size of the specified dimension.
Definition Array-base.h:585
int ndims() const
Size of the specified dimension.
Definition Array-base.h:701
octave_idx_type rows() const
Definition Array-base.h:485
void resize(const dim_vector &dv, const T &rfv)
Size of the specified dimension.
Array< T, Alloc > as_column() const
Return the array as a column vector.
Definition Array-base.h:446
octave_idx_type columns() const
Definition Array-base.h:497
bool isempty() const
Size of the specified dimension.
Definition Array-base.h:674
const T * data() const
Size of the specified dimension.
Definition Array-base.h:687
T * rwdata()
Size of the specified dimension.
octave_idx_type lookup(const T &value, sortmode mode=UNSORTED) const
Do a binary lookup in a sorted array.
octave_idx_type numel() const
Number of elements in the array.
Definition Array-base.h:440
Definition Cell.h:41
void resize(octave_idx_type n, const double &rfv=0)
Definition dColVector.h:112
double min() const
RowVector transpose() const
double max() const
ColumnVector extract_n(octave_idx_type r1, octave_idx_type n) const
Definition EIG.h:39
ComplexColumnVector eigenvalues() const
Definition EIG.h:119
MArray< T > reshape(const dim_vector &new_dims) const
Definition MArray.h:91
RowVector row(octave_idx_type i) const
Definition dMatrix.cc:417
Matrix transpose() const
Definition dMatrix.h:138
Matrix extract_n(octave_idx_type r1, octave_idx_type c1, octave_idx_type nr, octave_idx_type nc) const
Definition dMatrix.cc:408
ColumnVector row_min() const
Definition dMatrix.cc:2435
void resize(octave_idx_type nr, octave_idx_type nc, double rfv=0)
Definition dMatrix.h:156
ColumnVector row_max() const
Definition dMatrix.cc:2490
bool any_element_is_inf_or_nan() const
Definition dNDArray.cc:324
bool compare(const std::string &s, std::size_t limit=std::string::npos) const
Vector representing the dimensions (size) of an Array.
Definition dim-vector.h:92
static dim_vector alloc(int n)
Definition dim-vector.h:208
octave_idx_type ndims() const
Number of dimensions.
Definition dim-vector.h:263
graphics_handle current_figure() const
Definition gh-manager.h:94
Matrix handle_list(bool show_hidden=false)
Definition gh-manager.h:107
octave::mutex graphics_lock()
Definition gh-manager.h:207
graphics_object get_object(double val) const
Definition gh-manager.h:68
void renumber_figure(const graphics_handle &old_gh, const graphics_handle &new_gh)
void execute_listener(const graphics_handle &h, const octave_value &l)
void unlock()
Definition gh-manager.h:129
void push_figure(const graphics_handle &h)
bool is_handle_visible(const graphics_handle &h) const
Definition gh-manager.h:189
Matrix figure_handle_list(bool show_hidden=false)
Definition gh-manager.h:131
graphics_handle lookup(double val) const
Definition gh-manager.h:54
void free(const graphics_handle &h, bool from_root=false)
Definition gh-manager.cc:91
void lock()
Definition gh-manager.h:125
void pop_figure(const graphics_handle &h)
void post_callback(const graphics_handle &h, const std::string &name, const octave_value &data=Matrix())
graphics_handle make_graphics_handle(const std::string &go_name, const graphics_handle &p, bool integer_figure_handle=false, bool call_createfcn=true, bool notify_toolkit=true)
void execute_callback(const graphics_handle &h, const std::string &name, const octave_value &data=Matrix())
Definition gh-manager.h:150
int process_events(bool force=false)
graphics_handle make_figure_handle(double val, bool notify_toolkit=true)
graphics_handle get_handle(bool integer_figure_handle)
Definition gh-manager.cc:50
double value() const
Definition oct-handle.h:78
bool ok() const
Definition oct-handle.h:113
octave_value as_octave_value() const
Definition oct-handle.h:80
Cell getfield(const std::string &key) const
Definition oct-map.cc:275
octave_idx_type nfields() const
Definition oct-map.h:323
string_vector keys() const
Definition oct-map.h:335
const Cell & contents(const_iterator p) const
Definition oct-map.h:310
bool isfield(const std::string &name) const
Definition oct-map.h:326
octave_idx_type numel() const
Definition oct-map.h:368
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:230
octave_value getfield(const std::string &key) const
Definition oct-map.cc:183
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
octave_idx_type length() const
Definition ovl.h:111
bool is_function_handle() const
Definition ov.h:766
bool is_undefined() const
Definition ov.h:593
bool is_uint32_type() const
Definition ov.h:722
std::string class_name() const
Definition ov.h:1360
bool is_real_scalar() const
Definition ov.h:608
Cell cell_value() const
octave_idx_type rows() const
Definition ov.h:543
bool is_scalar_type() const
Definition ov.h:742
bool isreal() const
Definition ov.h:736
bool is_string() const
Definition ov.h:635
bool isnumeric() const
Definition ov.h:748
bool is_int8_type() const
Definition ov.h:704
bool is_range() const
Definition ov.h:644
bool is_single_type() const
Definition ov.h:696
bool is_defined() const
Definition ov.h:590
double xdouble_value(const char *fmt,...) const
bool isempty() const
Definition ov.h:599
bool is_uint8_type() const
Definition ov.h:716
bool is_uint64_type() const
Definition ov.h:725
bool is_uint16_type() const
Definition ov.h:719
bool is_int16_type() const
Definition ov.h:707
octave_value reshape(const dim_vector &dv) const
Definition ov.h:569
bool iscell() const
Definition ov.h:602
octave_idx_type numel() const
Definition ov.h:557
std::string string_value(bool force=false) const
Definition ov.h:981
bool is_int64_type() const
Definition ov.h:713
bool is_matrix_type() const
Definition ov.h:745
bool iscomplex() const
Definition ov.h:739
string_vector string_vector_value(bool pad=false) const
Definition ov.h:984
NDArray array_value(bool frc_str_conv=false) const
Definition ov.h:863
bool is_double_type() const
Definition ov.h:693
bool is_int32_type() const
Definition ov.h:710
bool is_bool_scalar() const
Definition ov.h:620
std::string type_name() const
Definition ov.h:1358
bool iscellstr() const
Definition ov.h:605
octave_idx_type columns() const
Definition ov.h:545
Matrix matrix_value(bool frc_str_conv=false) const
Definition ov.h:857
double double_value(bool frc_str_conv=false) const
Definition ov.h:845
bool islogical() const
Definition ov.h:733
dim_vector dims() const
Definition ov.h:539
string_vector & append(const std::string &s)
Definition str-vec.cc:110
octave_idx_type numel() const
Definition str-vec.h:98
std::string join(const std::string &sep="") const
Definition str-vec.cc:137
ColumnVector real(const ComplexColumnVector &a)
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value daspk_fcn
T eps(const T &x)
Definition data.cc:6843
#define DECLARE_STATIC_FUNX(name, args_name, nargout_name)
Definition defun-int.h:161
#define DECLARE_STATIC_METHODX(name, interp_name, args_name, nargout_name)
Definition defun-int.h:165
void print_usage()
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 warning(const char *fmt,...)
Definition error.cc:1083
void warning_with_id(const char *id, const char *fmt,...)
Definition error.cc:1098
void error(const char *fmt,...)
Definition error.cc:1008
std::string dirname(const std::string &path)
Definition file-ops.cc:369
octave_handle graphics_handle
void xset(const graphics_handle &h, const caseless_str &pname, const octave_value &val)
bool delete_executing
bool isfigure(double val)
void delete_graphics_object(const graphics_handle &h, bool from_root)
octave_value xget(const graphics_handle &h, const caseless_str &pname)
void delete_graphics_objects(const NDArray vals, bool from_root)
ColumnVector xform_vector()
Definition graphics.cc:5687
Matrix xform_matrix()
Definition graphics.cc:5676
void cross_product(double x1, double y1, double z1, double x2, double y2, double z2, double &x, double &y, double &z)
double dot(const ColumnVector &v1, const ColumnVector &v2)
Definition graphics.cc:5782
void translate(Matrix &m, double x, double y, double z)
Definition graphics.cc:5747
bool set_property_in_handle(double handle, const std::string &property, const octave_value &arg, const std::string &fcn)
#define CONVERT_CDATA_1(ARRAY_T, VAL_FN, IS_REAL)
ColumnVector cross(const ColumnVector &v1, const ColumnVector &v2)
Definition graphics.cc:5794
std::vector< octave_idx_type > coplanar_partition(const Matrix &vert, const Matrix &idx, octave_idx_type nc, octave_idx_type jj)
Definition graphics.cc:9811
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:8353
#define FIX_LIMITS
double norm(const ColumnVector &v)
Definition graphics.cc:5788
void xform(ColumnVector &v, const Matrix &m)
Definition graphics.cc:5753
void normalize(ColumnVector &v)
Definition graphics.cc:5775
base_graphics_object * make_graphics_object_from_type(const caseless_str &type, const graphics_handle &h, const graphics_handle &p)
Definition graphics.cc:1409
Matrix xform_scale(double x, double y, double z)
Definition graphics.cc:5715
ColumnVector cam2xform(const Array< double > &m)
Definition graphics.cc:5827
int calc_dimensions(const graphics_object &go)
RowVector xform2cam(const ColumnVector &v)
Definition graphics.cc:5837
graphics_handle gcf()
Definition graphics.cc:3001
#define GO_BODY(TYPE)
void scale(Matrix &m, double x, double y, double z)
Definition graphics.cc:5741
Matrix xform_translate(double x, double y, double z)
Definition graphics.cc:5728
ColumnVector transform(const Matrix &m, double x, double y, double z)
Definition graphics.cc:5709
Matrix unit_cube()
Definition graphics.cc:5806
graphics_handle gca()
Definition graphics.cc:3011
#define CHECK_ARRAY_EQUAL(T, F, A)
bool is_coplanar(const Matrix &cov)
Definition graphics.cc:9799
double force_in_range(double x, double lower, double upper)
Definition graphics.cc:8872
octave_value get_property_from_handle(double handle, const std::string &property, const std::string &fcn)
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:1188
bool Vdrawnow_requested
Definition input.cc:89
#define octave_NaN
Definition lo-ieee.h:46
Complex atan(const Complex &x)
Definition mappers.h:83
octave_int< uint32_t > octave_uint32
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
const octave_char_matrix & v2
octave_value_list ovl(const OV_Args &... args)
Construct an octave_value_list with less typing.
Definition ovl.h:217
#define octave_stdout
Definition pager.h:301
template int8_t abs(int8_t)
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
F77_RET_T len
Definition xerbla.cc:61