GNU Octave 10.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-2025 The Octave Project Developers
4//
5// See the file COPYRIGHT.md in the top-level directory of this
6// distribution or <https://octave.org/copyright/>.
7//
8// This file is part of Octave.
9//
10// Octave is free software: you can redistribute it and/or modify it
11// under the terms of the GNU General Public License as published by
12// the Free Software Foundation, either version 3 of the License, or
13// (at your option) any later version.
14//
15// Octave is distributed in the hope that it will be useful, but
16// WITHOUT ANY WARRANTY; without even the implied warranty of
17// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18// GNU General Public License for more details.
19//
20// You should have received a copy of the GNU General Public License
21// along with Octave; see the file COPYING. If not, see
22// <https://www.gnu.org/licenses/>.
23//
24////////////////////////////////////////////////////////////////////////
25
26#if defined (HAVE_CONFIG_H)
27# include "config.h"
28#endif
29
30#include <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 "lo-sysdep.h"
48#include "oct-locbuf.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 retval;
2091}
2092
2093void
2094children_property::do_delete_children (bool clear, bool from_root)
2095{
2096 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
2097
2098 if (from_root)
2099 {
2100 for (graphics_handle hchild : m_children_list)
2101 {
2102 graphics_object go = gh_mgr.get_object (hchild);
2103 if (go.valid_object ()
2104 && ! go.get_properties ().is_beingdeleted ())
2105 gh_mgr.free (hchild, from_root);
2106 }
2107 m_children_list.clear ();
2108 }
2109 else
2110 while (! m_children_list.empty ())
2111 {
2112 // gh_mgr.free removes hchild from children_list
2113 graphics_handle hchild = m_children_list.front ();
2114 graphics_object go = gh_mgr.get_object (hchild);
2115 if (go.valid_object ()
2116 && ! go.get_properties ().is_beingdeleted ())
2117 gh_mgr.free (hchild, from_root);
2118 }
2119
2120 // FIXME: children_list should be clear anyway at this point.
2121 if (clear)
2122 m_children_list.clear ();
2123}
2124
2125bool
2126callback_property::validate (const octave_value& v) const
2127{
2128 // case 1: empty matrix
2129 // case 2: function handle
2130 // case 3: string corresponding to known function name
2131 // case 4: string that can be eval()'ed
2132 // case 5: cell array with first element being a function handle
2133
2134 if (v.isempty ())
2135 return true;
2136 else if (v.is_function_handle ())
2137 return true;
2138 else if (v.is_string ())
2139 // complete validation will be done at execution-time
2140 return true;
2141 else if (v.iscell () && (v.rows () == 1 || v.columns () == 1)
2142 && v.cell_value ()(0).is_function_handle ())
2143 return true;
2144
2145 return false;
2146}
2147
2148class callback_props
2149{
2150public:
2151
2152 callback_props () : m_set () { }
2153
2154 OCTAVE_DISABLE_COPY_MOVE (callback_props)
2155
2156 ~callback_props () = default;
2157
2158 bool empty () const { return m_set.empty (); }
2159
2160 void insert (const callback_property *ptr)
2161 {
2162 m_set.insert (reinterpret_cast<intptr_t> (ptr));
2163 }
2164
2165 void erase (const callback_property *ptr)
2166 {
2167 m_set.erase (reinterpret_cast<intptr_t> (ptr));
2168 }
2169
2170 bool contains (const callback_property *ptr) const
2171 {
2172 return m_set.find (reinterpret_cast<intptr_t> (ptr)) != m_set.end ();
2173 }
2174
2175private:
2176
2177 std::set<intptr_t> m_set;
2178};
2179
2180// Elements of this set are pointers to currently executing
2181// callback_property objects. Used to determine handle visibility
2182// inside callback functions.
2183
2184static callback_props executing_callbacks;
2185
2186void
2187callback_property::execute (const octave_value& data) const
2188{
2189 // We are executing a callback function, so allow handles that have
2190 // their handlevisibility property set to "callback" to be visible.
2191
2192 octave::unwind_action executing_callbacks_cleanup
2193 ([this] () { executing_callbacks.erase (this); });
2194
2195 if (! executing_callbacks.contains (this))
2196 {
2197 executing_callbacks.insert (this);
2198
2199 if (m_callback.is_defined () && ! m_callback.isempty ())
2200 {
2201 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
2202
2203 gh_mgr.execute_callback (get_parent (), m_callback, data);
2204 }
2205 }
2206}
2207
2208// Used to cache dummy graphics objects from which dynamic properties can be
2209// cloned.
2210static std::map<caseless_str, graphics_object> dprop_obj_map;
2211
2212property
2213property::create (const std::string& name, const graphics_handle& h,
2214 const caseless_str& type, const octave_value_list& args)
2215{
2216 property retval;
2217
2218 if (type.compare ("string"))
2219 {
2220 std::string sv = (args.length () > 0 ? args(0).string_value () : "");
2221
2222 retval = property (new string_property (name, h, sv));
2223 }
2224 else if (type.compare ("any"))
2225 {
2226 octave_value ov = (args.length () > 0 ? args(0)
2227 : octave_value (Matrix ()));
2228
2229 retval = property (new any_property (name, h, ov));
2230 }
2231 else if (type.compare ("radio"))
2232 {
2233 if (args.length () < 1)
2234 error ("addproperty: missing possible values for radio property");
2235
2236 std::string sv = args(0).xstring_value ("addproperty: argument for radio property must be a string");
2237
2238 retval = property (new radio_property (name, h, sv));
2239
2240 if (args.length () > 1)
2241 retval.set (args(1));
2242 }
2243 else if (type.compare ("double"))
2244 {
2245 double dv = (args.length () > 0 ? args(0).double_value () : 0.0);
2246
2247 retval = property (new double_property (name, h, dv));
2248 }
2249 else if (type.compare ("handle"))
2250 {
2251 double hv = (args.length () > 0 ? args(0).double_value ()
2252 : octave::numeric_limits<double>::NaN ());
2253
2254 graphics_handle gh (hv);
2255
2256 retval = property (new handle_property (name, h, gh));
2257 }
2258 else if (type.compare ("boolean"))
2259 {
2260 retval = property (new bool_property (name, h, false));
2261
2262 if (args.length () > 0)
2263 retval.set (args(0));
2264 }
2265 else if (type.compare ("data"))
2266 {
2267 retval = property (new array_property (name, h, Matrix ()));
2268
2269 if (args.length () > 0)
2270 {
2271 retval.set (args(0));
2272 // FIXME: additional argument could define constraints,
2273 // but is this really useful?
2274 }
2275 }
2276 else if (type.compare ("color"))
2277 {
2278 color_values cv (0, 0, 0);
2279 radio_values rv;
2280
2281 if (args.length () > 1)
2282 rv = radio_values (args(1).string_value ());
2283
2284 retval = property (new color_property (name, h, cv, rv));
2285
2286 if (args.length () > 0 && ! args(0).isempty ())
2287 retval.set (args(0));
2288 else
2289 retval.set (rv.default_value ());
2290 }
2291 else
2292 {
2293 caseless_str go_name, go_rest;
2294
2295 if (! lookup_object_name (type, go_name, go_rest))
2296 error ("addproperty: unsupported type for dynamic property (= %s)",
2297 type.c_str ());
2298
2299 graphics_object go;
2300
2301 std::map<caseless_str, graphics_object>::const_iterator it
2302 = dprop_obj_map.find (go_name);
2303
2304 if (it == dprop_obj_map.end ())
2305 {
2306 base_graphics_object *bgo = make_graphics_object_from_type (go_name);
2307
2308 if (bgo)
2309 {
2310 go = graphics_object (bgo);
2311
2312 dprop_obj_map[go_name] = go;
2313 }
2314 }
2315 else
2316 go = it->second;
2317
2318 if (! go.valid_object ())
2319 error ("addproperty: invalid object type (= %s)",
2320 go_name.c_str ());
2321
2322 property prop = go.get_properties ().get_property (go_rest);
2323
2324 retval = prop.clone ();
2325
2326 retval.set_parent (h);
2327 retval.set_name (name);
2328
2329 if (args.length () > 0)
2330 retval.set (args(0));
2331 }
2332
2333 return retval;
2334}
2335
2336static void
2337finalize_r (const graphics_handle& h)
2338{
2339 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
2340
2341 graphics_object go = gh_mgr.get_object (h);
2342
2343 if (go)
2344 {
2345 Matrix children = go.get_properties ().get_all_children ();
2346
2347 for (int k = 0; k < children.numel (); k++)
2348 finalize_r (children(k));
2349
2350 go.finalize ();
2351 }
2352}
2353
2354static void
2355initialize_r (const graphics_handle& h)
2356{
2357 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
2358
2359 graphics_object go = gh_mgr.get_object (h);
2360
2361 if (go)
2362 {
2363 Matrix children = go.get_properties ().get_all_children ();
2364
2365 go.initialize ();
2366
2367 for (int k = 0; k < children.numel (); k++)
2368 initialize_r (children(k));
2369 }
2370}
2371
2372void
2373figure::properties::set_toolkit (const octave::graphics_toolkit& b)
2374{
2375 if (m_toolkit)
2376 finalize_r (get___myhandle__ ());
2377
2378 m_toolkit = b;
2379 m___graphics_toolkit__ = b.get_name ();
2380 m___plot_stream__ = Matrix ();
2381
2382 if (m_toolkit)
2383 initialize_r (get___myhandle__ ());
2384
2385 mark_modified ();
2386}
2387
2388void
2389figure::properties::set___mouse_mode__ (const octave_value& val_arg)
2390{
2391 std::string direction = "in";
2392
2393 octave_value val = val_arg;
2394
2395 if (val.is_string ())
2396 {
2397 std::string modestr = val.string_value ();
2398
2399 if (modestr == "zoom in")
2400 {
2401 val = modestr = "zoom";
2402 direction = "in";
2403 }
2404 else if (modestr == "zoom out")
2405 {
2406 val = modestr = "zoom";
2407 direction = "out";
2408 }
2409
2410 if (m___mouse_mode__.set (val, true))
2411 {
2412 std::string mode = m___mouse_mode__.current_value ();
2413
2414 octave_scalar_map pm = get___pan_mode__ ().scalar_map_value ();
2415 pm.setfield ("Enable", mode == "pan" ? "on" : "off");
2416 set___pan_mode__ (pm);
2417
2418 octave_scalar_map rm = get___rotate_mode__ ().scalar_map_value ();
2419 rm.setfield ("Enable", mode == "rotate" ? "on" : "off");
2420 set___rotate_mode__ (rm);
2421
2422 octave_scalar_map zm = get___zoom_mode__ ().scalar_map_value ();
2423 zm.setfield ("Enable", mode == "zoom" ? "on" : "off");
2424 zm.setfield ("Direction", direction);
2425 set___zoom_mode__ (zm);
2426
2427 mark_modified ();
2428 }
2429 else if (modestr == "zoom")
2430 {
2431 octave_scalar_map zm = get___zoom_mode__ ().scalar_map_value ();
2432 std::string curr_direction
2433 = zm.getfield ("Direction").string_value ();
2434
2435 if (direction != curr_direction)
2436 {
2437 zm.setfield ("Direction", direction);
2438 set___zoom_mode__ (zm);
2439
2440 mark_modified ();
2441 }
2442 }
2443 }
2444}
2445
2446void
2447figure::properties::update_handlevisibility ()
2448{
2449 if (! is_handle_visible ())
2450 {
2451 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
2452
2453 octave_value cf = gh_mgr.get_object (0).get ("currentfigure");
2454
2455 if (! cf.isempty () && cf.double_value () == m___myhandle__)
2456 {
2457 octave::autolock guard (gh_mgr.graphics_lock ());
2458
2459 octave_value kids = gh_mgr.get_object (0).get ("children");
2460
2461 if (kids.isempty ())
2462 gh_mgr.get_object (0).set ("currentfigure", Matrix ());
2463 else
2464 {
2465 NDArray kidsarray = kids.array_value ();
2466 gh_mgr.get_object (0).set ("currentfigure", kidsarray(0));
2467 }
2468 }
2469 }
2470
2471 base_properties::update_handlevisibility ();
2472}
2473
2474static void
2475update_text_pos (graphics_handle h)
2476{
2477 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
2478
2479 graphics_object go = gh_mgr.get_object (h);
2480
2481 if (go.isa ("text"))
2482 {
2483 text::properties& tp
2484 = dynamic_cast<text::properties&> (go.get_properties ());
2485 tp.update_font ();
2486 tp.update_text_extent ();
2487 }
2488 else if (go.isa ("figure") || go.isa ("uipanel") || go.isa ("axes")
2489 || go.isa ("hggroup"))
2490 {
2491 Matrix ch = go.get_properties ().get_all_children ();
2492 for (octave_idx_type ii = 0; ii < ch.numel (); ii++)
2493 update_text_pos (graphics_handle (ch(ii)));
2494
2495 if (go.isa ("axes"))
2496 {
2497 axes::properties& ap
2498 = dynamic_cast<axes::properties&> (go.get_properties ());
2499 ap.update_font ();
2500 ap.sync_positions ();
2501 }
2502 }
2503}
2504
2505void
2506figure::properties::update___device_pixel_ratio__ ()
2507{
2508 update_text_pos (get___myhandle__ ());
2509}
2510
2511// ---------------------------------------------------------------------
2512
2513void
2514property_list::set (const caseless_str& name, const octave_value& val)
2515{
2516 std::size_t offset = 0;
2517
2518 std::size_t len = name.length ();
2519
2520 if (len > 4)
2521 {
2522 caseless_str pfx = name.substr (0, 4);
2523
2524 if (pfx.compare ("axes") || pfx.compare ("line")
2525 || pfx.compare ("text"))
2526 offset = 4;
2527 else if (len > 5)
2528 {
2529 pfx = name.substr (0, 5);
2530
2531 if (pfx.compare ("image") || pfx.compare ("patch"))
2532 offset = 5;
2533 else if (len > 6)
2534 {
2535 pfx = name.substr (0, 6);
2536
2537 if (pfx.compare ("figure") || pfx.compare ("uimenu"))
2538 offset = 6;
2539 else if (len > 7)
2540 {
2541 pfx = name.substr (0, 7);
2542
2543 if (pfx.compare ("surface") || pfx.compare ("scatter")
2544 || pfx.compare ("hggroup")|| pfx.compare ("uipanel")
2545 || pfx.compare ("uitable"))
2546 offset = 7;
2547 else if (len > 9)
2548 {
2549 pfx = name.substr (0, 9);
2550
2551 if (pfx.compare ("uicontrol")
2552 || pfx.compare ("uitoolbar"))
2553 offset = 9;
2554 else if (len > 10)
2555 {
2556 pfx = name.substr (0, 10);
2557
2558 if (pfx.compare ("uipushtool"))
2559 offset = 10;
2560 else if (len > 12)
2561 {
2562 pfx = name.substr (0, 12);
2563
2564 if (pfx.compare ("uitoogletool"))
2565 offset = 12;
2566 else if (len > 13)
2567 {
2568 pfx = name.substr (0, 13);
2569
2570 if (pfx.compare ("uicontextmenu")
2571 || pfx.compare ("uibuttongroup"))
2572 offset = 13;
2573 }
2574 }
2575 }
2576 }
2577 }
2578 }
2579 }
2580
2581 if (offset > 0)
2582 {
2583 // FIXME: should we validate property names and values here?
2584
2585 std::string pname = name.substr (offset);
2586
2587 std::transform (pfx.begin (), pfx.end (), pfx.begin (), tolower);
2588 std::transform (pname.begin (), pname.end (), pname.begin (),
2589 tolower);
2590
2591 bool has_property = false;
2592 if (pfx == "axes")
2593 has_property = axes::properties::has_core_property (pname);
2594 else if (pfx == "figure")
2595 has_property = figure::properties::has_core_property (pname);
2596 else if (pfx == "line")
2597 has_property = line::properties::has_core_property (pname);
2598 else if (pfx == "text")
2599 has_property = text::properties::has_core_property (pname);
2600 else if (pfx == "image")
2601 has_property = image::properties::has_core_property (pname);
2602 else if (pfx == "patch")
2603 has_property = patch::properties::has_core_property (pname);
2604 else if (pfx == "scatter")
2605 has_property = scatter::properties::has_core_property (pname);
2606 else if (pfx == "surface")
2607 has_property = surface::properties::has_core_property (pname);
2608 else if (pfx == "hggroup")
2609 has_property = hggroup::properties::has_core_property (pname);
2610 else if (pfx == "uimenu")
2611 has_property = uimenu::properties::has_core_property (pname);
2612 else if (pfx == "uicontrol")
2613 has_property = uicontrol::properties::has_core_property (pname);
2614 else if (pfx == "uibuttongroup")
2615 has_property = uibuttongroup::properties::has_core_property (pname);
2616 else if (pfx == "uipanel")
2617 has_property = uipanel::properties::has_core_property (pname);
2618 else if (pfx == "uicontextmenu")
2619 has_property = uicontextmenu::properties::has_core_property (pname);
2620 else if (pfx == "uitable")
2621 has_property = uitable::properties::has_core_property (pname);
2622 else if (pfx == "uitoolbar")
2623 has_property = uitoolbar::properties::has_core_property (pname);
2624 else if (pfx == "uipushtool")
2625 has_property = uipushtool::properties::has_core_property (pname);
2626
2627 if (! has_property)
2628 error ("invalid %s property '%s'", pfx.c_str (), pname.c_str ());
2629
2630 bool remove = false;
2631 if (val.is_string ())
2632 {
2633 std::string sval = val.string_value ();
2634
2635 remove = (sval == "remove");
2636 }
2637
2638 pval_map_type& pval_map = m_plist_map[pfx];
2639
2640 if (remove)
2641 {
2642 auto p = pval_map.find (pname);
2643
2644 if (p != pval_map.end ())
2645 pval_map.erase (p);
2646 }
2647 else
2648 pval_map[pname] = val;
2649 }
2650 }
2651
2652 if (offset == 0)
2653 error ("invalid default property specification");
2654}
2655
2657property_list::lookup (const caseless_str& name) const
2658{
2659 octave_value retval;
2660
2661 std::size_t offset = 0;
2662
2663 std::size_t len = name.length ();
2664
2665 if (len > 4)
2666 {
2667 caseless_str pfx = name.substr (0, 4);
2668
2669 if (pfx.compare ("axes") || pfx.compare ("line")
2670 || pfx.compare ("text"))
2671 offset = 4;
2672 else if (len > 5)
2673 {
2674 pfx = name.substr (0, 5);
2675
2676 if (pfx.compare ("image") || pfx.compare ("patch"))
2677 offset = 5;
2678 else if (len > 6)
2679 {
2680 pfx = name.substr (0, 6);
2681
2682 if (pfx.compare ("figure") || pfx.compare ("uimenu"))
2683 offset = 6;
2684 else if (len > 7)
2685 {
2686 pfx = name.substr (0, 7);
2687
2688 if (pfx.compare ("surface") || pfx.compare ("scatter")
2689 || pfx.compare ("hggroup") || pfx.compare ("uipanel")
2690 || pfx.compare ("uitable"))
2691 offset = 7;
2692 else if (len > 9)
2693 {
2694 pfx = name.substr (0, 9);
2695
2696 if (pfx.compare ("uicontrol")
2697 || pfx.compare ("uitoolbar"))
2698 offset = 9;
2699 else if (len > 10)
2700 {
2701 pfx = name.substr (0, 10);
2702
2703 if (pfx.compare ("uipushtool"))
2704 offset = 10;
2705 else if (len > 12)
2706 {
2707 pfx = name.substr (0, 12);
2708
2709 if (pfx.compare ("uitoggletool"))
2710 offset = 12;
2711 else if (len > 13)
2712 {
2713 pfx = name.substr (0, 13);
2714
2715 if (pfx.compare ("uicontextmenu")
2716 || pfx.compare ("uibuttongroup"))
2717 offset = 13;
2718 }
2719 }
2720 }
2721 }
2722 }
2723 }
2724 }
2725
2726 if (offset > 0)
2727 {
2728 std::string pname = name.substr (offset);
2729
2730 std::transform (pfx.begin (), pfx.end (), pfx.begin (), tolower);
2731 std::transform (pname.begin (), pname.end (), pname.begin (),
2732 tolower);
2733
2734 plist_map_const_iterator p = find (pfx);
2735
2736 if (p != end ())
2737 {
2738 const pval_map_type& pval_map = p->second;
2739
2740 pval_map_const_iterator q = pval_map.find (pname);
2741
2742 if (q != pval_map.end ())
2743 retval = q->second;
2744 }
2745 }
2746 }
2747
2748 return retval;
2749}
2750
2752property_list::as_struct (const std::string& prefix_arg) const
2753{
2755
2756 for (const auto& p : *this)
2757 {
2758 std::string prefix = prefix_arg + p.first;
2759
2760 for (const auto& prop_val : p.second)
2761 m.assign (prefix + prop_val.first, prop_val.second);
2762 }
2763
2764 return m;
2765}
2766
2767// Set property given as either cs-list of name/value pairs or a struct.
2768
2769void
2770graphics_object::set (const octave_value_list& args)
2771{
2772 int nargin = args.length ();
2773
2774 if (nargin == 0)
2775 error ("graphics_object::set: Nothing to set");
2776
2777 for (int i = 0; i < nargin; )
2778 {
2779 if (args(i).isstruct ())
2780 {
2781 set (args(i).map_value ());
2782 i++;
2783 }
2784 else if (i < nargin - 1)
2785 {
2786 caseless_str pname = args(i).xstring_value ("set: argument %d must be a property name", i);
2787 octave_value val = args(i+1);
2788 set_value_or_default (pname, val);
2789 i += 2;
2790 }
2791 else
2792 error ("set: invalid number of arguments");
2793 }
2794}
2795
2796/*
2797## test set with name, value pairs
2798%!test
2799%! hf = figure ("visible", "off");
2800%! h = plot (1:10, 10:-1:1);
2801%! set (h, "linewidth", 10, "marker", "x");
2802%! lw = get (h, "linewidth");
2803%! mk = get (h, "marker");
2804%! close (hf);
2805%! assert (lw, 10);
2806%! assert (mk, "x");
2807*/
2808
2809// Set properties given in two cell arrays containing names and values.
2810void
2811graphics_object::set (const Array<std::string>& pnames,
2812 const Cell& values, octave_idx_type row)
2813{
2814 if (pnames.numel () != values.columns ())
2815 error ("set: number of names must match number of value columns "
2816 "(%" OCTAVE_IDX_TYPE_FORMAT " != %" OCTAVE_IDX_TYPE_FORMAT ")",
2817 pnames.numel (), values.columns ());
2818
2819 octave_idx_type k = pnames.columns ();
2820
2821 for (octave_idx_type column = 0; column < k; column++)
2822 {
2823 caseless_str pname = pnames(column);
2824 octave_value val = values(row, column);
2825
2826 set_value_or_default (pname, val);
2827 }
2828}
2829
2830/*
2831## test set with cell array arguments
2832%!test
2833%! hf = figure ("visible", "off");
2834%! h = plot (1:10, 10:-1:1);
2835%! set (h, {"linewidth", "marker"}, {10, "x"});
2836%! lw = get (h, "linewidth");
2837%! mk = get (h, "marker");
2838%! close (hf);
2839%! assert (lw, 10);
2840%! assert (mk, "x");
2841
2842## test set with multiple handles and cell array arguments
2843%!test
2844%! hf = figure ("visible", "off");
2845%! unwind_protect
2846%! h = plot (1:10, 10:-1:1, 1:10, 1:10);
2847%! set (h, {"linewidth", "marker"}, {10, "x"; 5, "o"});
2848%! assert (get (h, "linewidth"), {10; 5});
2849%! assert (get (h, "marker"), {"x"; "o"});
2850%! set (h, {"linewidth", "marker"}, {10, "x"});
2851%! assert (get (h, "linewidth"), {10; 10});
2852%! assert (get (h, "marker"), {"x"; "x"});
2853%! unwind_protect_cleanup
2854%! close (hf);
2855%! end_unwind_protect
2856
2857%!error <set: number of graphics handles must match number of value rows>
2858%! hf = figure ("visible", "off");
2859%! unwind_protect
2860%! h = plot (1:10, 10:-1:1, 1:10, 1:10);
2861%! set (h, {"linewidth", "marker"}, {10, "x"; 5, "o"; 7, "."});
2862%! unwind_protect_cleanup
2863%! close (hf);
2864%! end_unwind_protect
2865
2866%!error <set: number of names must match number of value columns>
2867%! hf = figure ("visible", "off");
2868%! unwind_protect
2869%! h = plot (1:10, 10:-1:1, 1:10, 1:10);
2870%! set (h, {"linewidth"}, {10, "x"; 5, "o"});
2871%! unwind_protect_cleanup
2872%! close (hf);
2873%! end_unwind_protect
2874*/
2875
2876// Set properties given in a struct array
2877void
2878graphics_object::set (const octave_map& m)
2879{
2880 for (octave_idx_type p = 0; p < m.nfields (); p++)
2881 {
2882 // FIXME: Would it be better to extract all the keys at once rather than
2883 // repeatedly call keys() inside a for loop?
2884 caseless_str pname = m.keys ()[p];
2885
2886 octave_value val = octave_value (m.contents (pname).elem (m.numel () - 1));
2887
2888 set_value_or_default (pname, val);
2889 }
2890}
2891
2892/*
2893## test set with struct arguments
2894%!test
2895%! hf = figure ("visible", "off");
2896%! unwind_protect
2897%! h = plot (1:10, 10:-1:1);
2898%! set (h, struct ("linewidth", 10, "marker", "x"));
2899%! assert (get (h, "linewidth"), 10);
2900%! assert (get (h, "marker"), "x");
2901%! h = plot (1:10, 10:-1:1, 1:10, 1:10);
2902%! set (h, struct ("linewidth", {5, 10}));
2903%! assert (get (h, "linewidth"), {10; 10});
2904%! unwind_protect_cleanup
2905%! close (hf);
2906%! end_unwind_protect
2907
2908## test ordering
2909%!test
2910%! markchanged = @(h, foobar, name) set (h, "userdata", [get(h,"userdata"); {name}]);
2911%! hf = figure ("visible", "off");
2912%! unwind_protect
2913%! h = line ();
2914%! set (h, "userdata", {});
2915%! addlistener (h, "color", {markchanged, "color"});
2916%! addlistener (h, "linewidth", {markchanged, "linewidth"});
2917%! ## "linewidth" first
2918%! props.linewidth = 2;
2919%! props.color = "r";
2920%! set (h, props);
2921%! assert (get (h, "userdata"), fieldnames (props));
2922%! clear props;
2923%! clf ();
2924%! h = line ();
2925%! set (h, "userdata", {});
2926%! addlistener (h, "color", {markchanged, "color"});
2927%! addlistener (h, "linewidth", {markchanged, "linewidth"});
2928%! ## "color" first
2929%! props.color = "r";
2930%! props.linewidth = 2;
2931%! set (h, props);
2932%! assert (get (h, "userdata"), fieldnames (props));
2933%! unwind_protect_cleanup
2934%! close (hf);
2935%! end_unwind_protect
2936*/
2937
2938// Set a property to a value or to its (factory) default value.
2939
2940void
2941graphics_object::set_value_or_default (const caseless_str& pname,
2942 const octave_value& val)
2943{
2944 if (val.is_string () && val.rows () == 1)
2945 {
2946 std::string sval = val.string_value ();
2947
2948 octave_value default_val;
2949
2950 if (sval == "default")
2951 {
2952 default_val = get_default (pname);
2953
2954 m_rep->set (pname, default_val);
2955 }
2956 else if (sval == "factory")
2957 {
2958 default_val = get_factory_default (pname);
2959
2960 m_rep->set (pname, default_val);
2961 }
2962 else
2963 {
2964 // Matlab specifically uses "\default" to escape string setting
2965 if (sval == R"(\default)")
2966 m_rep->set (pname, "default");
2967 else if (sval == R"(\factory)")
2968 m_rep->set (pname, "factory");
2969 else
2970 m_rep->set (pname, val);
2971 }
2972 }
2973 else
2974 m_rep->set (pname, val);
2975}
2976
2977/*
2978## test setting of default values
2979%!test
2980%! old_lw = get (0, "defaultlinelinewidth");
2981%! unwind_protect
2982%! hf = figure ("visible", "off");
2983%! h = plot (1:10, 10:-1:1);
2984%! set (0, "defaultlinelinewidth", 20);
2985%! set (h, "linewidth", "default");
2986%! assert (get (h, "linewidth"), 20);
2987%! set (h, "linewidth", "factory");
2988%! assert (get (h, "linewidth"), 0.5);
2989%! unwind_protect_cleanup
2990%! close (hf);
2991%! set (0, "defaultlinelinewidth", old_lw);
2992%! end_unwind_protect
2993*/
2994
2995// This function is NOT equivalent to the scripting language function gcf.
2998{
2999 octave_value val = xget (0, "currentfigure");
3000
3001 return val.isempty () ? octave::numeric_limits<double>::NaN ()
3002 : val.double_value ();
3003}
3004
3005// This function is NOT equivalent to the scripting language function gca.
3008{
3009 octave_value val = xget (gcf (), "currentaxes");
3010
3011 return val.isempty () ? octave::numeric_limits<double>::NaN ()
3012 : val.double_value ();
3013}
3014
3015static void
3016adopt (const graphics_handle& parent_h, const graphics_handle& h)
3017{
3018 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3019
3020 graphics_object parent_go = gh_mgr.get_object (parent_h);
3021
3022 parent_go.adopt (h);
3023}
3024
3025static bool
3026ishghandle (const graphics_handle& h)
3027{
3028 return h.ok ();
3029}
3030
3031static bool
3032ishghandle (double val)
3033{
3034 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3035
3036 graphics_handle h = gh_mgr.lookup (val);
3037
3038 return h.ok ();
3039}
3040
3041static octave_value
3042ishghandle (const octave_value& val)
3043{
3044 octave_value retval = false;
3045
3046 if (val.is_real_scalar () && ishghandle (val.double_value ()))
3047 retval = true;
3048 else if (val.isnumeric () && val.isreal ())
3049 {
3050 const NDArray handles = val.array_value ();
3051
3052 boolNDArray result (handles.dims ());
3053
3054 for (octave_idx_type i = 0; i < handles.numel (); i++)
3055 result.xelem (i) = ishghandle (handles(i));
3056
3057 retval = result;
3058 }
3059
3060 return retval;
3061}
3062
3063static void
3064xcreatefcn (const graphics_handle& h)
3065{
3066 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3067
3068 graphics_object go = gh_mgr.get_object (h);
3069
3070 go.get_properties ().execute_createfcn ();
3071}
3072
3073static void
3074xinitialize (const graphics_handle& h)
3075{
3076 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3077
3078 graphics_object go = gh_mgr.get_object (h);
3079
3080 if (go)
3081 go.initialize ();
3082}
3083
3084// ---------------------------------------------------------------------
3085
3086static int
3087toggle_warn (std::string id, bool on, int state = -1)
3088{
3089 if (! on)
3090 {
3091 state = octave::warning_enabled (id);
3092 octave::disable_warning (id);
3093 }
3094 else
3095 {
3096 if (state == 1)
3097 octave::set_warning_state (id, "on");
3098 else if (state == 2)
3099 octave::set_warning_state (id, "error");
3100 }
3101 return state;
3102}
3103
3104static void
3105xreset_default_properties (graphics_handle h,
3106 property_list::pval_map_type factory_pval)
3107{
3108 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3109
3110 graphics_object go = gh_mgr.get_object (h);
3111
3112 // Replace factory defaults by user defined ones
3113 std::string go_name = go.get_properties ().graphics_object_name ();
3114 property_list::pval_map_type pval;
3115 go.build_user_defaults_map (pval, go_name);
3116
3117 for (const auto& p : pval)
3118 factory_pval[p.first] = p.second;
3119
3120 // Save warning state of "Octave:deprecated-property"
3121 int state = toggle_warn ("Octave:deprecated-property", false);
3122
3123 // Reset defaults
3124 for (const auto& p : factory_pval)
3125 {
3126 std::string pname = p.first;
3127
3128 // Don't reset internal properties and handle_properties
3129 if (! go.has_readonly_property (pname)
3130 && pname.find ("__") != 0 && pname.find ("current") != 0
3131 && pname != "uicontextmenu" && pname != "parent")
3132 {
3133 // Store *mode prop/val in order to set them last
3134 if (pname.find ("mode") == (pname.length () - 4))
3135 pval[pname] = p.second;
3136 else
3137 go.set (pname, p.second);
3138 }
3139 }
3140
3141 // set *mode properties
3142 for (const auto& p : pval)
3143 go.set (p.first, p.second);
3144
3145 toggle_warn ("Octave:deprecated-property", true, state);
3146}
3147
3148// ---------------------------------------------------------------------
3149
3150void
3151base_properties::set_from_list (base_graphics_object& bgo,
3152 property_list& defaults)
3153{
3154 std::string go_name = graphics_object_name ();
3155
3156 property_list::plist_map_const_iterator plist = defaults.find (go_name);
3157
3158 if (plist != defaults.end ())
3159 {
3160 const property_list::pval_map_type pval_map = plist->second;
3161
3162 for (const auto& prop_val : pval_map)
3163 {
3164 std::string pname = prop_val.first;
3165
3166 try
3167 {
3168 bgo.set (pname, prop_val.second);
3169 }
3170 catch (octave::execution_exception& ee)
3171 {
3172 error (ee, "error setting default property %s", pname.c_str ());
3173 }
3174 }
3175 }
3176}
3177
3178/*
3179## test defaults are set in the order they were stored
3180%!test
3181%! set (0, "defaultfigureunits", "normalized");
3182%! set(0, "defaultfigureposition", [0.7 0 0.3 0.3]);
3183%! hf = figure ("visible", "off");
3184%! tol = 20 * eps;
3185%! unwind_protect
3186%! assert (get (hf, "position"), [0.7 0 0.3 0.3], tol);
3187%! unwind_protect_cleanup
3188%! close (hf);
3189%! set (0, "defaultfigureunits", "remove");
3190%! set (0, "defaultfigureposition", "remove");
3191%! end_unwind_protect
3192*/
3193
3195base_properties::get_dynamic (const caseless_str& pname) const
3196{
3197 std::map<caseless_str, property, cmp_caseless_str>::const_iterator it
3198 = m_all_props.find (pname);
3199
3200 if (it == m_all_props.end ())
3201 error (R"(get: unknown property "%s")", pname.c_str ());
3202
3203 return it->second.get ();
3204}
3205
3207base_properties::get_dynamic (bool all) const
3208{
3210
3211 for (const auto& it : m_all_props)
3212 if (all || ! it.second.is_hidden ())
3213 m.assign (it.second.get_name (), it.second.get ());
3214
3215 return m;
3216}
3217
3218std::set<std::string>
3219base_properties::dynamic_property_names () const
3220{
3221 return m_dynamic_properties;
3222}
3223
3224bool
3225base_properties::has_dynamic_property (const std::string& pname) const
3226{
3227 const std::set<std::string>& dynprops = dynamic_property_names ();
3228
3229 if (dynprops.find (pname) != dynprops.end ())
3230 return true;
3231 else
3232 return m_all_props.find (pname) != m_all_props.end ();
3233}
3234
3235void
3236base_properties::set_dynamic (const caseless_str& pname,
3237 const octave_value& val)
3238{
3239 auto it = m_all_props.find (pname);
3240
3241 if (it == m_all_props.end ())
3242 error (R"(set: unknown property "%s")", pname.c_str ());
3243
3244 it->second.set (val);
3245
3246 m_dynamic_properties.insert (pname);
3247
3248 mark_modified ();
3249}
3250
3251property
3252base_properties::get_property_dynamic (const caseless_str& pname) const
3253{
3254 std::map<caseless_str, property, cmp_caseless_str>::const_iterator it
3255 = m_all_props.find (pname);
3256
3257 if (it == m_all_props.end ())
3258 error (R"(get_property: unknown property "%s")", pname.c_str ());
3259
3260 return it->second;
3261}
3262
3263void
3264base_properties::set_parent (const octave_value& val)
3265{
3266 double hp = val.xdouble_value ("set: parent must be a graphics handle");
3267 if (hp == m___myhandle__)
3268 error ("set: can not set object parent to be object itself");
3269
3270 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3271
3272 graphics_handle new_parent = gh_mgr.lookup (hp);
3273 if (! new_parent.ok ())
3274 error ("set: invalid graphics handle (= %g) for parent", hp);
3275
3276 // Remove child from current parent
3277 graphics_object old_parent_go;
3278 old_parent_go = gh_mgr.get_object (get_parent ());
3279
3280 if (old_parent_go.get_handle () != hp)
3281 old_parent_go.remove_child (m___myhandle__);
3282 else
3283 return; // Do nothing more
3284
3285 // Check new parent's parent is not this child to avoid recursion
3286 graphics_object new_parent_go;
3287 new_parent_go = gh_mgr.get_object (new_parent);
3288 if (new_parent_go.get_parent () == m___myhandle__)
3289 {
3290 // new parent's parent gets child's original parent
3291 new_parent_go.get_properties ().set_parent (get_parent ().as_octave_value ());
3292 }
3293
3294 // Set parent property to new_parent and do adoption
3295 m_parent = new_parent.as_octave_value ();
3296 octave::adopt (m_parent.handle_value (), m___myhandle__);
3297}
3298
3299/*
3300%!test
3301%! hf = figure ("visible", "off");
3302%! unwind_protect
3303%! hax = gca ();
3304%! set (hax, "parent", gcf ());
3305%! assert (gca (), hax);
3306%! unwind_protect_cleanup
3307%! close (hf);
3308%! end_unwind_protect
3309*/
3310
3311void
3312base_properties::mark_modified ()
3313{
3314 // Mark existing object as modified
3315 m___modified__ = "on";
3316
3317 // Attempt to mark parent object as modified if it exists
3318
3319 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3320
3321 graphics_object parent_go = gh_mgr.get_object (get_parent ());
3322
3323 if (parent_go)
3324 parent_go.mark_modified ();
3325}
3326
3327void
3328base_properties::override_defaults (base_graphics_object& obj)
3329{
3330 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3331
3332 graphics_object parent_go = gh_mgr.get_object (get_parent ());
3333
3334 if (parent_go)
3335 parent_go.override_defaults (obj);
3336}
3337
3338void
3339base_properties::update_axis_limits (const std::string& axis_type) const
3340{
3341 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3342
3343 graphics_object go = gh_mgr.get_object (m___myhandle__);
3344
3345 if (go)
3346 go.update_axis_limits (axis_type);
3347}
3348
3349void
3350base_properties::update_axis_limits (const std::string& axis_type,
3351 const graphics_handle& h) const
3352{
3353 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3354
3355 graphics_object go = gh_mgr.get_object (m___myhandle__);
3356
3357 if (go)
3358 go.update_axis_limits (axis_type, h);
3359}
3360
3361void
3362base_properties::update_contextmenu () const
3363{
3364 if (m_contextmenu.get ().isempty ())
3365 return;
3366
3367 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3368
3369 graphics_object go = gh_mgr.get_object (m_contextmenu.get ());
3370
3371 if (go && go.isa ("uicontextmenu"))
3372 {
3373 uicontextmenu::properties& props
3374 = reinterpret_cast<uicontextmenu::properties&> (go.get_properties ());
3375 props.add_dependent_obj (m___myhandle__);
3376 }
3377}
3378
3379bool
3380base_properties::is_handle_visible () const
3381{
3382 return (m_handlevisibility.is ("on")
3383 || (! executing_callbacks.empty () && ! m_handlevisibility.is ("off")));
3384}
3385
3386octave::graphics_toolkit
3387base_properties::get_toolkit () const
3388{
3389 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3390
3391 graphics_object go = gh_mgr.get_object (get_parent ());
3392
3393 if (go)
3394 return go.get_toolkit ();
3395 else
3396 return octave::graphics_toolkit ();
3397}
3398
3399void
3400base_properties::update_boundingbox ()
3401{
3402 Matrix kids = get_children ();
3403
3404 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3405
3406 for (int i = 0; i < kids.numel (); i++)
3407 {
3408 graphics_object go = gh_mgr.get_object (kids(i));
3409
3410 if (go.valid_object ())
3411 go.get_properties ().update_boundingbox ();
3412 }
3413}
3414
3415void
3416base_properties::update_autopos (const std::string& elem_type)
3417{
3418 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3419
3420 graphics_object parent_go = gh_mgr.get_object (get_parent ());
3421
3422 if (parent_go.valid_object ())
3423 parent_go.get_properties ().update_autopos (elem_type);
3424}
3425
3426void
3427base_properties::update_handlevisibility ()
3428{
3429 if (is_handle_visible ())
3430 return;
3431
3432 // This object should not be the figure "currentobject"
3433
3434 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3435
3436 graphics_object go (gh_mgr.get_object (get___myhandle__ ()));
3437
3438 graphics_object fig (go.get_ancestor ("figure"));
3439
3440 if (fig.valid_object ())
3441 {
3442 octave_value co = fig.get ("currentobject");
3443 if (! co.isempty () && co.double_value () == m___myhandle__)
3444 {
3445 octave::autolock guard (gh_mgr.graphics_lock ());
3446
3447 auto& fig_props = dynamic_cast<figure::properties&> (fig.get_properties ());
3448 fig_props.set_currentobject (Matrix ());
3449 }
3450 }
3451}
3452
3453/*
3454## test current figure and current axes have visible handles
3455%!test
3456%! hf1 = figure ("visible", "off");
3457%! hf2 = figure ("visible", "off");
3458%! hax1 = axes ();
3459%! hax2 = axes ();
3460%! unwind_protect
3461%! assert (get (0, "currentfigure"), hf2);
3462%! assert (get (hf2, "currentaxes"), hax2);
3463%! set (hf2, "handlevisibility", "off");
3464%! assert (get (0, "currentfigure"), hf1);
3465%! set (hax2, "handlevisibility", "off");
3466%! assert (get (hf2, "currentaxes"), hax1);
3467%! assert (get (hf2, "currentobject"), []);
3468%! unwind_protect_cleanup
3469%! close ([hf1, hf2]);
3470%! end_unwind_protect
3471*/
3472
3473/*
3474## test current callback object have visible handle
3475%!test
3476%! hf = figure ("handlevisibility", "off", "visible", "off");
3477%! hax = axes ("parent", hf, "handlevisibility", "off");
3478%! unwind_protect
3479%! fcn = @(h, ~) setappdata (h, "testdata", gcbo ());
3480%! addlistener (hf, "color", fcn);
3481%! addlistener (hax, "color", fcn);
3482%! set (hf, "color", "b");
3483%! set (hax, "color", "b");
3484%! assert (getappdata (hf, "testdata"), hf)
3485%! assert (getappdata (hax, "testdata"), hax)
3486%! unwind_protect_cleanup
3487%! close (hf);
3488%! end_unwind_protect
3489*/
3490
3491void
3492base_properties::add_listener (const caseless_str& pname,
3493 const octave_value& val,
3494 listener_mode mode)
3495{
3496 property p = get_property (pname);
3497
3498 if (p.ok ())
3499 p.add_listener (val, mode);
3500}
3501
3502void
3503base_properties::delete_listener (const caseless_str& pname,
3504 const octave_value& val,
3505 listener_mode mode)
3506{
3507 property p = get_property (pname);
3508
3509 if (p.ok ())
3510 p.delete_listener (val, mode);
3511}
3512
3513void
3514base_properties::get_children_of_type (const caseless_str& chtype,
3515 bool get_invisible,
3516 bool traverse,
3517 std::list<graphics_object>& children_list) const
3518{
3519 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3520
3521 Matrix ch = get_children ();
3522
3523 for (octave_idx_type i = 0; i < ch.numel (); i++)
3524 {
3525 graphics_handle hkid = gh_mgr.lookup (ch(i));
3526
3527 if (hkid.ok ())
3528 {
3529 graphics_object go = gh_mgr.get_object (hkid);
3530 if ( get_invisible || go.get_properties ().is_visible () )
3531 {
3532 if (go.isa (chtype))
3533 children_list.push_back (go);
3534 else if (traverse && go.isa ("hggroup"))
3535 go.get_properties ().get_children_of_type (chtype,
3536 get_invisible,
3537 traverse,
3538 children_list);
3539 }
3540 }
3541 }
3542}
3543
3544// ---------------------------------------------------------------------
3545
3546void
3547base_graphics_object::update_axis_limits (const std::string& axis_type)
3548{
3549 if (! valid_object ())
3550 error ("base_graphics_object::update_axis_limits: invalid graphics object");
3551
3552 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3553
3554 graphics_object parent_go = gh_mgr.get_object (get_parent ());
3555
3556 if (parent_go)
3557 parent_go.update_axis_limits (axis_type);
3558}
3559
3560void
3561base_graphics_object::update_axis_limits (const std::string& axis_type,
3562 const graphics_handle& h)
3563{
3564 if (! valid_object ())
3565 error ("base_graphics_object::update_axis_limits: invalid graphics object");
3566
3567 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3568
3569 graphics_object parent_go = gh_mgr.get_object (get_parent ());
3570
3571 if (parent_go)
3572 parent_go.update_axis_limits (axis_type, h);
3573}
3574
3575void
3576base_graphics_object::remove_all_listeners ()
3577{
3578 int state = toggle_warn ("Octave:deprecated-property", false);
3579 octave_map m = get (true).map_value ();
3580 toggle_warn ("Octave:deprecated-property", true, state);
3581
3582 for (const auto& pm : m)
3583 {
3584 // FIXME: there has to be a better way. I think we want to
3585 // ask whether it is OK to delete the listener for the given
3586 // property. How can we know in advance that it will be OK?
3587
3588 octave::unwind_protect frame;
3589
3590 octave::interpreter_try (frame);
3591
3592 try
3593 {
3594 property p = get_properties ().get_property (pm.first);
3595
3596 if (p.ok ())
3597 p.delete_listener ();
3598 }
3599 catch (const octave::execution_exception&)
3600 {
3601 octave::interpreter& interp = octave::__get_interpreter__ ();
3602
3603 interp.recover_from_exception ();
3604 }
3605 }
3606}
3607
3608void
3609base_graphics_object::build_user_defaults_map (property_list::pval_map_type& def,
3610 const std::string go_name) const
3611{
3612 property_list local_defaults = get_defaults_list ();
3613 const auto it = local_defaults.find (go_name);
3614
3615 if (it != local_defaults.end ())
3616 {
3617 property_list::pval_map_type pval_lst = it->second;
3618 for (const auto& prop_val : pval_lst)
3619 {
3620 std::string pname = prop_val.first;
3621 if (def.find (pname) == def.end ())
3622 def[pname] = prop_val.second;
3623 }
3624 }
3625
3626 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3627
3628 graphics_object parent_go = gh_mgr.get_object (get_parent ());
3629
3630 if (parent_go)
3631 parent_go.build_user_defaults_map (def, go_name);
3632}
3633
3634void
3635base_graphics_object::reset_default_properties ()
3636{
3637 if (valid_object ())
3638 {
3639 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3640
3641 property_list::pval_map_type factory_pval
3642 = gh_mgr.get_object (0).get_factory_defaults_list ().find (type ())->second;
3643
3644 remove_all_listeners ();
3645 xreset_default_properties (get_handle (), factory_pval);
3646 }
3647}
3648
3649std::string
3650base_graphics_object::values_as_string ()
3651{
3652 if (! valid_object ())
3653 error ("base_graphics_object::values_as_string: invalid graphics object");
3654
3655 std::string retval;
3656 octave_map m = get ().map_value ();
3657
3658 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3659
3660 graphics_object go = gh_mgr.get_object (get_handle ());
3661
3662 for (const auto& pm : m)
3663 {
3664 const auto& pname = pm.first;
3665 if (pname != "children" && ! go.has_readonly_property (pname))
3666 {
3667 property p = get_properties ().get_property (pname);
3668
3669 if (p.ok () && ! p.is_hidden ())
3670 {
3671 retval += "\n\t" + std::string (pname) + ": ";
3672 if (p.is_radio ())
3673 retval += p.values_as_string ();
3674 }
3675 }
3676 }
3677
3678 if (! retval.empty ())
3679 retval += '\n';
3680
3681 return retval;
3682}
3683
3684std::string
3685base_graphics_object::value_as_string (const std::string& prop)
3686{
3687 std::string retval;
3688
3689 if (! valid_object ())
3690 error ("base_graphics_object::value_as_string: invalid graphics object");
3691
3692 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3693
3694 graphics_object go = gh_mgr.get_object (get_handle ());
3695
3696 if (prop != "children" && ! go.has_readonly_property (prop))
3697 {
3698 property p = get_properties ().get_property (prop);
3699
3700 if (p.ok () && ! p.is_hidden ())
3701 {
3702 if (p.is_radio ())
3703 retval += p.values_as_string ();
3704 }
3705 }
3706
3707 if (! retval.empty ())
3708 retval += '\n';
3709
3710 return retval;
3711}
3712
3714base_graphics_object::values_as_struct ()
3715{
3716 octave_scalar_map retval;
3717
3718 if (! valid_object ())
3719 error ("base_graphics_object::values_as_struct: invalid graphics object");
3720
3721 octave_scalar_map m = get ().scalar_map_value ();
3722
3723 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3724
3725 graphics_object go = gh_mgr.get_object (get_handle ());
3726
3727 for (const auto& pm : m)
3728 {
3729 const auto& pname = pm.first;
3730 if (pname != "children" && ! go.has_readonly_property (pname))
3731 {
3732 property p = get_properties ().get_property (pname);
3733
3734 if (p.ok () && ! p.is_hidden ())
3735 {
3736 if (p.is_radio ())
3737 retval.assign (p.get_name (), p.values_as_cell ());
3738 else
3739 retval.assign (p.get_name (), Cell ());
3740 }
3741 }
3742 }
3743
3744 return retval;
3745}
3746
3747/*
3748%!test
3749%! hfig = figure ("visible", "off");
3750%! unwind_protect
3751%! hax = axes ();
3752%! ret = set (hax, "tightinset");
3753%! assert (isempty (ret));
3754%! ret = set (hax, "type");
3755%! assert (isempty (ret));
3756%! ret = set (hfig, "tag");
3757%! assert (isempty (ret));
3758%! ret = set (0, "commandwindowsize");
3759%! assert (isempty (ret));
3760%! ret = set (0);
3761%! assert (! isfield (ret, "commandwindowsize"));
3762%! unwind_protect_cleanup
3763%! close (hfig);
3764%! end_unwind_protect
3765*/
3766
3767graphics_object
3768graphics_object::get_ancestor (const std::string& obj_type) const
3769{
3770 if (valid_object ())
3771 {
3772 if (isa (obj_type))
3773 return *this;
3774 else
3775 {
3776 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3777
3778 return gh_mgr.get_object (get_parent ()).get_ancestor (obj_type);
3779 }
3780 }
3781 else
3782 return graphics_object ();
3783}
3784
3785// ---------------------------------------------------------------------
3786
3787#include "graphics-props.cc"
3788
3789// ---------------------------------------------------------------------
3790
3791void
3792root_figure::properties::set_callbackobject (const octave_value& v)
3793{
3794 graphics_handle val (v);
3795
3796 if (octave::math::isnan (val.value ()))
3797 m_callbackobject = graphics_handle ();
3798 else if (ishghandle (val))
3799 m_callbackobject = val;
3800 else
3801 err_set_invalid ("callbackobject");
3802}
3803
3804void
3805root_figure::properties::set_currentfigure (const octave_value& v)
3806{
3807 graphics_handle val (v);
3808
3809 if (octave::math::isnan (val.value ()) || ishghandle (val))
3810 {
3811 m_currentfigure = val;
3812
3813 if (val.ok ())
3814 {
3815 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3816
3817 gh_mgr.push_figure (val);
3818 }
3819 }
3820 else
3821 err_set_invalid ("currentfigure");
3822}
3823
3824void
3825figure::properties::set_integerhandle (const octave_value& val)
3826{
3827 if (m_integerhandle.set (val, true))
3828 {
3829 bool int_fig_handle = m_integerhandle.is_on ();
3830
3831 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3832
3833 graphics_object this_go = gh_mgr.get_object (m___myhandle__);
3834
3835 graphics_handle old_myhandle = m___myhandle__;
3836
3837 m___myhandle__ = gh_mgr.get_handle (int_fig_handle);
3838
3839 gh_mgr.renumber_figure (old_myhandle, m___myhandle__);
3840
3841 graphics_object parent_go = gh_mgr.get_object (get_parent ());
3842
3843 base_properties& props = parent_go.get_properties ();
3844
3845 props.renumber_child (old_myhandle, m___myhandle__);
3846
3847 Matrix kids = get_children ();
3848
3849 for (octave_idx_type i = 0; i < kids.numel (); i++)
3850 {
3851 graphics_object kid = gh_mgr.get_object (kids(i));
3852
3853 kid.get_properties ().renumber_parent (m___myhandle__);
3854 }
3855
3856 graphics_handle cf = gh_mgr.current_figure ();
3857
3858 if (m___myhandle__ == cf)
3859 xset (0, "currentfigure", m___myhandle__.value ());
3860
3861 this_go.update (m_integerhandle.get_id ());
3862
3863 mark_modified ();
3864 }
3865}
3866
3867// FIXME: This should update monitorpositions and pointerlocation, but as these
3868// properties aren't yet used, it doesn't matter that they aren't set either.
3869void
3870root_figure::properties::update_units ()
3871{
3872 std::string xunits = get_units ();
3873
3874 Matrix scrn_sz = default_screensize ();
3875
3876 double dpi = get_screenpixelsperinch ();
3877
3878 if (xunits == "pixels")
3879 {
3880 // Most common case (default).
3881 // Don't need to convert anything, but short-circuit if/else tree.
3882 }
3883 else if (xunits == "normalized")
3884 {
3885 scrn_sz = Matrix (1, 4, 1.0);
3886 scrn_sz(0) = 0;
3887 scrn_sz(1) = 0;
3888 }
3889 else if (xunits == "inches")
3890 {
3891 scrn_sz(0) = 0;
3892 scrn_sz(1) = 0;
3893 scrn_sz(2) /= dpi;
3894 scrn_sz(3) /= dpi;
3895 }
3896 else if (xunits == "centimeters")
3897 {
3898 scrn_sz(0) = 0;
3899 scrn_sz(1) = 0;
3900 scrn_sz(2) *= 2.54 / dpi;
3901 scrn_sz(3) *= 2.54 / dpi;
3902 }
3903 else if (xunits == "points")
3904 {
3905 scrn_sz(0) = 0;
3906 scrn_sz(1) = 0;
3907 scrn_sz(2) *= 72 / dpi;
3908 scrn_sz(3) *= 72 / dpi;
3909 }
3910 else if (xunits == "characters")
3911 {
3912 scrn_sz(0) = 0;
3913 scrn_sz(1) = 0;
3914 // FIXME: this assumes the system font is Helvetica 10pt
3915 // (for which "x" requires 6x12 pixels at 74.951 pixels/inch)
3916 scrn_sz(2) *= 74.951 / 12.0 / dpi;
3917 scrn_sz(3) *= 74.951 / 12.0 / dpi;
3918 }
3919
3920 set_screensize (scrn_sz);
3921}
3922
3923Matrix
3924root_figure::properties::get_boundingbox (bool, const Matrix&) const
3925{
3926 Matrix screen_size = screen_size_pixels ();
3927 Matrix pos = Matrix (1, 4, 0.0);
3928
3929 pos(2) = screen_size(0);
3930 pos(3) = screen_size(1);
3931
3932 return pos;
3933}
3934
3935/*
3936%!test
3937%! old_units = get (0, "units");
3938%! unwind_protect
3939%! set (0, "units", "pixels");
3940%! sz = get (0, "screensize") - [1, 1, 0, 0];
3941%! dpi = get (0, "screenpixelsperinch");
3942%! set (0, "units", "inches");
3943%! assert (get (0, "screensize"), sz / dpi, 0.5 / dpi);
3944%! set (0, "units", "centimeters");
3945%! assert (get (0, "screensize"), sz / dpi * 2.54, 0.5 / dpi * 2.54);
3946%! set (0, "units", "points");
3947%! assert (get (0, "screensize"), sz / dpi * 72, 0.5 / dpi * 72);
3948%! set (0, "units", "normalized");
3949%! assert (get (0, "screensize"), [0.0, 0.0, 1.0, 1.0]);
3950%! set (0, "units", "pixels");
3951%! assert (get (0, "screensize"), sz + [1, 1, 0, 0]);
3952%! set (0, "units", "characters");
3953%! assert (get (0, "screensize"),
3954%! sz / dpi * (74.951 / 12.0), 0.5 / dpi * (74.951 / 12.0));
3955%! unwind_protect_cleanup
3956%! set (0, "units", old_units);
3957%! end_unwind_protect
3958*/
3959
3960void
3961root_figure::properties::remove_child (const graphics_handle& h, bool)
3962{
3963 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
3964
3965 gh_mgr.pop_figure (h);
3966
3967 graphics_handle cf = gh_mgr.current_figure ();
3968
3969 xset (0, "currentfigure", cf.value ());
3970
3971 base_properties::remove_child (h, true);
3972}
3973
3974void
3975root_figure::reset_default_properties ()
3976{
3977 // empty list of local defaults
3978 m_default_properties = property_list ();
3979
3980 remove_all_listeners ();
3981 xreset_default_properties (get_handle (),
3982 m_properties.factory_defaults ());
3983}
3984
3985// ---------------------------------------------------------------------
3986
3987void
3988figure::properties::set_currentaxes (const octave_value& val)
3989{
3990 graphics_handle hax (val);
3991
3992 if (octave::math::isnan (hax.value ()) || ishghandle (hax))
3993 m_currentaxes = hax;
3994 else
3995 err_set_invalid ("currentaxes");
3996}
3997
3998void
3999figure::properties::remove_child (const graphics_handle& h, bool from_root)
4000{
4001 base_properties::remove_child (h, from_root);
4002
4003 if (h == m_currentaxes.handle_value ())
4004 {
4005 graphics_handle new_currentaxes;
4006
4007 Matrix kids = get_children ();
4008
4009 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
4010
4011 for (octave_idx_type i = 0; i < kids.numel (); i++)
4012 {
4013 graphics_handle kid = kids(i);
4014
4015 graphics_object go = gh_mgr.get_object (kid);
4016
4017 if (go.isa ("axes"))
4018 {
4019 new_currentaxes = kid;
4020 break;
4021 }
4022 }
4023
4024 m_currentaxes = new_currentaxes;
4025 }
4026}
4027
4029figure::properties::get_number () const
4030{
4031 if (m_integerhandle.is_on ())
4032 return m___myhandle__.value ();
4033 else
4034 return Matrix ();
4035}
4036
4037octave::graphics_toolkit
4038figure::properties::get_toolkit () const
4039{
4040 return m_toolkit;
4041}
4042
4043void
4044figure::properties::set___graphics_toolkit__ (const octave_value& val)
4045{
4046 if (! val.is_string ())
4047 error ("set___graphics_toolkit__: toolkit must be a string");
4048
4049 std::string nm = val.string_value ();
4050
4051 octave::gtk_manager& gtk_mgr = octave::__get_gtk_manager__ ();
4052
4053 octave::graphics_toolkit b = gtk_mgr.find_toolkit (nm);
4054
4055 if (b.get_name () != nm)
4056 error ("set___graphics_toolkit__: invalid graphics toolkit");
4057
4058 if (nm != get___graphics_toolkit__ ())
4059 {
4060 set_toolkit (b);
4061 mark_modified ();
4062 }
4063}
4064
4065void
4066figure::properties::adopt (const graphics_handle& h)
4067{
4068 base_properties::adopt (h);
4069
4070 if (! get_currentaxes ().ok ())
4071 {
4072 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
4073
4074 graphics_object go = gh_mgr.get_object (h);
4075
4076 if (go.type () == "axes")
4077 set_currentaxes (h.as_octave_value ());
4078 }
4079}
4080
4081/*
4082%!test
4083%! hf1 = figure ("visible", "off");
4084%! ax1 = subplot (1,2,1);
4085%! ax2 = subplot (1,2,2);
4086%! hf2 = figure ("visible", "off");
4087%! unwind_protect
4088%! set (ax2, "parent", hf2);
4089%! assert (get (hf2, "currentaxes"), ax2);
4090%! assert (get (hf1, "currentaxes"), ax1);
4091%! set (ax1, "parent", hf2);
4092%! assert (get (hf2, "currentaxes"), ax2);
4093%! unwind_protect_cleanup
4094%! close (hf1);
4095%! close (hf2);
4096%! end_unwind_protect
4097*/
4098
4099void
4100figure::properties::set_visible (const octave_value& val)
4101{
4102 std::string sval = val.string_value ();
4103
4104 if (sval == "on")
4105 xset (0, "currentfigure", m___myhandle__.value ());
4106
4107 m_visible = val;
4108}
4109
4110Matrix
4111figure::properties::get_boundingbox (bool internal, const Matrix&) const
4112{
4113#if defined (__APPLE__) && defined (__MACH__)
4114 // On macOS with Retina display, it looks like we'd need to divide the
4115 // "__device_pixel_ratio__" (reported by Qt) by the "backingScaleFactor":
4116 // https://developer.apple.com/documentation/appkit/nswindow/1419459-backingscalefactor
4117 // It might also be that we'd need to multiply the screensize (in pixels) by
4118 // the "backingScaleFactor" instead.
4119 // Afaict, that factor is only available via an Objective-C-API.
4120 // FIXME: Check how to get that from C++.
4121 // As a workaround, assume that we are either on a display prior to Retina
4122 // scaling where "__device_pixel_ratio__" and "backingScaleFactor" are both
4123 // 1, or we are on a Retina display where both are probably 2. The latter
4124 // might not always be the case.
4125 double dpr = 1.0;
4126#else
4127 double dpr = get___device_pixel_ratio__ ();
4128#endif
4129 Matrix screen_size = screen_size_pixels ();
4130 Matrix pos = (internal ?
4131 get_position ().matrix_value () :
4132 get_outerposition ().matrix_value ());
4133
4134 pos = convert_position (pos, get_units (), "pixels", screen_size);
4135
4136 pos(0)--;
4137 pos(1)--;
4138 pos(1) = screen_size(1) / dpr - pos(1) - pos(3);
4139
4140 return pos;
4141}
4142
4143Matrix
4144figure::properties::bbox2position (const Matrix& bb) const
4145{
4146#if defined (__APPLE__) && defined (__MACH__)
4147 // FIXME: See comment in figure::properties::get_boundingbox.
4148 double dpr = 1.0;
4149#else
4150 double dpr = get___device_pixel_ratio__ ();
4151#endif
4152 Matrix screen_size = screen_size_pixels ();
4153 Matrix pos = bb;
4154
4155 pos(1) = screen_size(1) - (pos(1) + pos(3)) * dpr;
4156 pos(1)++;
4157 pos(0)++;
4158 pos = convert_position (pos, "pixels", get_units (), screen_size);
4159 return pos;
4160}
4161
4162void
4163figure::properties::set_boundingbox (const Matrix& bb, bool internal,
4164 bool do_notify_toolkit)
4165{
4166 Matrix pos = bbox2position (bb);
4167
4168 if (internal)
4169 set_position (pos, do_notify_toolkit);
4170 else
4171 set_outerposition (pos, do_notify_toolkit);
4172}
4173
4174Matrix
4175figure::properties::map_from_boundingbox (double x, double y) const
4176{
4177 Matrix bb = get_boundingbox (true);
4178 Matrix pos (1, 2, 0.0);
4179
4180 pos(0) = x;
4181 pos(1) = y;
4182
4183 pos(1) = bb(3) - pos(1);
4184 pos(0)++;
4185 pos = convert_position (pos, "pixels", get_units (),
4186 bb.extract_n (0, 2, 1, 2));
4187
4188 return pos;
4189}
4190
4191Matrix
4192figure::properties::map_to_boundingbox (double x, double y) const
4193{
4194 Matrix bb = get_boundingbox (true);
4195 Matrix pos (1, 2, 0.0);
4196
4197 pos(0) = x;
4198 pos(1) = y;
4199
4200 pos = convert_position (pos, get_units (), "pixels",
4201 bb.extract_n (0, 2, 1, 2));
4202 pos(0)--;
4203 pos(1) = bb(3) - pos(1);
4204
4205 return pos;
4206}
4207
4208void
4209figure::properties::set_position (const octave_value& v,
4210 bool do_notify_toolkit)
4211{
4212 Matrix old_bb, new_bb;
4213 bool modified = false;
4214
4215 old_bb = get_boundingbox (true);
4216 modified = m_position.set (v, false, do_notify_toolkit);
4217 new_bb = get_boundingbox (true);
4218
4219 if (old_bb != new_bb)
4220 {
4221 if (old_bb(2) != new_bb(2) || old_bb(3) != new_bb(3))
4222 {
4223 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
4224
4225 if (! get_resizefcn ().isempty ())
4226 gh_mgr.post_callback (m___myhandle__, "resizefcn");
4227
4228 if (! get_sizechangedfcn ().isempty ())
4229 gh_mgr.post_callback (m___myhandle__, "sizechangedfcn");
4230
4231 update_boundingbox ();
4232 }
4233 }
4234
4235 if (modified)
4236 {
4237 m_position.run_listeners (GCB_POSTSET);
4238 mark_modified ();
4239 }
4240
4241 if (m_paperpositionmode.is ("auto"))
4242 m_paperposition.set (get_auto_paperposition ());
4243}
4244
4245void
4246figure::properties::set_outerposition (const octave_value& v,
4247 bool do_notify_toolkit)
4248{
4249 if (m_outerposition.set (v, true, do_notify_toolkit))
4250 mark_modified ();
4251}
4252
4253void
4254figure::properties::set_paperunits (const octave_value& val)
4255{
4256 caseless_str punits = val.string_value ();
4257 caseless_str ptype = get_papertype ();
4258
4259 if (punits.compare ("normalized") && ptype.compare ("<custom>"))
4260 error ("set: can't set paperunits to normalized when papertype is custom");
4261
4262 caseless_str old_paperunits = get_paperunits ();
4263 if (m_paperunits.set (val, true))
4264 {
4265 update_paperunits (old_paperunits);
4266 mark_modified ();
4267 }
4268}
4269
4270void
4271figure::properties::set_papertype (const octave_value& val)
4272{
4273 caseless_str ptype = val.string_value ();
4274 caseless_str punits = get_paperunits ();
4275
4276 if (punits.compare ("normalized") && ptype.compare ("<custom>"))
4277 error ("set: can't set paperunits to normalized when papertype is custom");
4278
4279 if (m_papertype.set (val, true))
4280 {
4281 update_papertype ();
4282 mark_modified ();
4283 }
4284}
4285
4286static Matrix
4287papersize_from_type (const caseless_str punits, const caseless_str ptype)
4288{
4289 Matrix retval (1, 2, 1.0);
4290
4291 if (! punits.compare ("normalized"))
4292 {
4293 double in2units;
4294 double mm2units;
4295
4296 if (punits.compare ("inches"))
4297 {
4298 in2units = 1.0;
4299 mm2units = 1 / 25.4;
4300 }
4301 else if (punits.compare ("centimeters"))
4302 {
4303 in2units = 2.54;
4304 mm2units = 1 / 10.0;
4305 }
4306 else // points
4307 {
4308 in2units = 72.0;
4309 mm2units = 72.0 / 25.4;
4310 }
4311
4312 if (ptype.compare ("usletter"))
4313 {
4314 retval(0) = 8.5 * in2units;
4315 retval(1) = 11.0 * in2units;
4316 }
4317 else if (ptype.compare ("uslegal"))
4318 {
4319 retval(0) = 8.5 * in2units;
4320 retval(1) = 14.0 * in2units;
4321 }
4322 else if (ptype.compare ("tabloid"))
4323 {
4324 retval(0) = 11.0 * in2units;
4325 retval(1) = 17.0 * in2units;
4326 }
4327 else if (ptype.compare ("a0"))
4328 {
4329 retval(0) = 841.0 * mm2units;
4330 retval(1) = 1189.0 * mm2units;
4331 }
4332 else if (ptype.compare ("a1"))
4333 {
4334 retval(0) = 594.0 * mm2units;
4335 retval(1) = 841.0 * mm2units;
4336 }
4337 else if (ptype.compare ("a2"))
4338 {
4339 retval(0) = 420.0 * mm2units;
4340 retval(1) = 594.0 * mm2units;
4341 }
4342 else if (ptype.compare ("a3"))
4343 {
4344 retval(0) = 297.0 * mm2units;
4345 retval(1) = 420.0 * mm2units;
4346 }
4347 else if (ptype.compare ("a4"))
4348 {
4349 retval(0) = 210.0 * mm2units;
4350 retval(1) = 297.0 * mm2units;
4351 }
4352 else if (ptype.compare ("a5"))
4353 {
4354 retval(0) = 148.0 * mm2units;
4355 retval(1) = 210.0 * mm2units;
4356 }
4357 else if (ptype.compare ("b0"))
4358 {
4359 retval(0) = 1029.0 * mm2units;
4360 retval(1) = 1456.0 * mm2units;
4361 }
4362 else if (ptype.compare ("b1"))
4363 {
4364 retval(0) = 728.0 * mm2units;
4365 retval(1) = 1028.0 * mm2units;
4366 }
4367 else if (ptype.compare ("b2"))
4368 {
4369 retval(0) = 514.0 * mm2units;
4370 retval(1) = 728.0 * mm2units;
4371 }
4372 else if (ptype.compare ("b3"))
4373 {
4374 retval(0) = 364.0 * mm2units;
4375 retval(1) = 514.0 * mm2units;
4376 }
4377 else if (ptype.compare ("b4"))
4378 {
4379 retval(0) = 257.0 * mm2units;
4380 retval(1) = 364.0 * mm2units;
4381 }
4382 else if (ptype.compare ("b5"))
4383 {
4384 retval(0) = 182.0 * mm2units;
4385 retval(1) = 257.0 * mm2units;
4386 }
4387 else if (ptype.compare ("arch-a"))
4388 {
4389 retval(0) = 9.0 * in2units;
4390 retval(1) = 12.0 * in2units;
4391 }
4392 else if (ptype.compare ("arch-b"))
4393 {
4394 retval(0) = 12.0 * in2units;
4395 retval(1) = 18.0 * in2units;
4396 }
4397 else if (ptype.compare ("arch-c"))
4398 {
4399 retval(0) = 18.0 * in2units;
4400 retval(1) = 24.0 * in2units;
4401 }
4402 else if (ptype.compare ("arch-d"))
4403 {
4404 retval(0) = 24.0 * in2units;
4405 retval(1) = 36.0 * in2units;
4406 }
4407 else if (ptype.compare ("arch-e"))
4408 {
4409 retval(0) = 36.0 * in2units;
4410 retval(1) = 48.0 * in2units;
4411 }
4412 else if (ptype.compare ("a"))
4413 {
4414 retval(0) = 8.5 * in2units;
4415 retval(1) = 11.0 * in2units;
4416 }
4417 else if (ptype.compare ("b"))
4418 {
4419 retval(0) = 11.0 * in2units;
4420 retval(1) = 17.0 * in2units;
4421 }
4422 else if (ptype.compare ("c"))
4423 {
4424 retval(0) = 17.0 * in2units;
4425 retval(1) = 22.0 * in2units;
4426 }
4427 else if (ptype.compare ("d"))
4428 {
4429 retval(0) = 22.0 * in2units;
4430 retval(1) = 34.0 * in2units;
4431 }
4432 else if (ptype.compare ("e"))
4433 {
4434 retval(0) = 34.0 * in2units;
4435 retval(1) = 43.0 * in2units;
4436 }
4437 }
4438
4439 return retval;
4440}
4441
4442Matrix
4443figure::properties::get_auto_paperposition ()
4444{
4445 Matrix pos = get_position ().matrix_value ();
4446 Matrix sz;
4447
4448 caseless_str funits = get_units ();
4449 caseless_str punits = get_paperunits ();
4450
4451 // Convert position from figure units to paperunits
4452 if (funits == "normalized" || punits == "normalized")
4453 {
4454 sz = screen_size_pixels ();
4455 pos = convert_position (pos, funits, "inches", sz);
4456
4457 if (punits == "normalized")
4458 sz = papersize_from_type ("points", get_papertype ());
4459
4460 pos = convert_position (pos, "inches", punits, sz);
4461 }
4462 else
4463 pos = convert_position (pos, funits, punits, sz);
4464
4465 // Center the figure on the page
4466 sz = get_papersize ().matrix_value ();
4467
4468 pos(0) = sz(0)/2 - pos(2)/2;
4469 pos(1) = sz(1)/2 - pos(3)/2;
4470
4471 return pos;
4472}
4473
4474/*
4475%!test
4476%! hf = figure ("visible", "off", "paperpositionmode", "auto");
4477%! in_pos = [0 0 4 5];
4478%! tol = 20 * eps ();
4479%! unwind_protect
4480%! ## paperpositionmode "auto" converts figure size to paper units
4481%! set (hf, "units", "inches");
4482%! set (hf, "position", in_pos);
4483%! set (hf, "paperunits", "centimeters");
4484%! psz = get (hf, "papersize");
4485%! fsz = in_pos(3:4) * 2.54;
4486%! pos = [(psz/2 - fsz/2) fsz];
4487%! set (hf, "paperpositionmode", "auto");
4488%! assert (get (hf, "paperposition"), pos, tol);
4489%! unwind_protect_cleanup
4490%! close (hf);
4491%! end_unwind_protect
4492
4493%!test
4494%! hf = figure ("visible", "off", "paperpositionmode", "auto");
4495%! in_pos = [0 0 4 5];
4496%! tol = 20 * eps ();
4497%! unwind_protect
4498%! ## likewise with normalized units
4499%! set (hf, "units", "inches");
4500%! set (hf, "position", in_pos);
4501%! psz = get (hf, "papersize");
4502%! set (hf, "paperunits", "normalized");
4503%! fsz = in_pos(3:4) ./ psz;
4504%! pos = [([0.5 0.5] - fsz/2) fsz];
4505%! assert (get (hf, "paperposition"), pos, tol);
4506%! unwind_protect_cleanup
4507%! close (hf);
4508%! end_unwind_protect
4509
4510%!test
4511%! hf = figure ("visible", "off", "paperpositionmode", "auto");
4512%! in_pos = [0 0 4 5];
4513%! tol = 20 * eps ();
4514%! unwind_protect
4515%! ## changing papertype updates paperposition
4516%! set (hf, "units", "inches");
4517%! set (hf, "position", in_pos);
4518%! set (hf, "papertype", "a4");
4519%! psz = get (hf, "papersize");
4520%! fsz = in_pos(3:4);
4521%! pos = [(psz/2 - fsz/2) fsz];
4522%! assert (get (hf, "paperposition"), pos, tol);
4523%! unwind_protect_cleanup
4524%! close (hf);
4525%! end_unwind_protect
4526
4527%!test
4528%! hf = figure ("visible", "off", "paperpositionmode", "auto");
4529%! in_pos = [0 0 4 5];
4530%! tol = 20 * eps ();
4531%! unwind_protect
4532%! ## lanscape updates paperposition
4533%! set (hf, "units", "inches");
4534%! set (hf, "position", in_pos);
4535%! set (hf, "paperorientation", "landscape");
4536%! psz = get (hf, "papersize");
4537%! fsz = in_pos(3:4);
4538%! pos = [(psz/2 - fsz/2) fsz];
4539%! assert (get (hf, "paperposition"), pos, tol);
4540%! unwind_protect_cleanup
4541%! close (hf);
4542%! end_unwind_protect
4543
4544%!test
4545%! hf = figure ("visible", "off", "paperpositionmode", "auto");
4546%! in_pos = [0 0 4 5];
4547%! unwind_protect
4548%! ## back to manual mode
4549%! set (hf, "paperposition", in_pos * 1.1);
4550%! assert (get (hf, "paperpositionmode"), "manual");
4551%! assert (get (hf, "paperposition"), in_pos * 1.1);
4552%! unwind_protect_cleanup
4553%! close (hf);
4554%! end_unwind_protect
4555*/
4556
4557void
4558figure::properties::update_paperunits (const caseless_str& old_paperunits)
4559{
4560 Matrix pos = get_paperposition ().matrix_value ();
4561 Matrix sz = get_papersize ().matrix_value ();
4562
4563 pos(0) /= sz(0);
4564 pos(1) /= sz(1);
4565 pos(2) /= sz(0);
4566 pos(3) /= sz(1);
4567
4568 std::string porient = get_paperorientation ();
4569 caseless_str punits = get_paperunits ();
4570 caseless_str ptype = get_papertype ();
4571
4572 if (ptype.compare ("<custom>"))
4573 {
4574 if (old_paperunits.compare ("centimeters"))
4575 {
4576 sz(0) /= 2.54;
4577 sz(1) /= 2.54;
4578 }
4579 else if (old_paperunits.compare ("points"))
4580 {
4581 sz(0) /= 72.0;
4582 sz(1) /= 72.0;
4583 }
4584
4585 if (punits.compare ("centimeters"))
4586 {
4587 sz(0) *= 2.54;
4588 sz(1) *= 2.54;
4589 }
4590 else if (punits.compare ("points"))
4591 {
4592 sz(0) *= 72.0;
4593 sz(1) *= 72.0;
4594 }
4595 }
4596 else
4597 {
4598 sz = papersize_from_type (punits, ptype);
4599 if (porient == "landscape")
4600 std::swap (sz(0), sz(1));
4601 }
4602
4603 pos(0) *= sz(0);
4604 pos(1) *= sz(1);
4605 pos(2) *= sz(0);
4606 pos(3) *= sz(1);
4607
4608 m_papersize.set (octave_value (sz));
4609 m_paperposition.set (octave_value (pos));
4610}
4611
4612void
4613figure::properties::update_papertype ()
4614{
4615 std::string typ = get_papertype ();
4616 if (typ != "<custom>")
4617 {
4618 Matrix sz = papersize_from_type (get_paperunits (), typ);
4619 if (get_paperorientation () == "landscape")
4620 std::swap (sz(0), sz(1));
4621 // Call papersize.set rather than set_papersize to avoid loops
4622 // between update_papersize and update_papertype.
4623 m_papersize.set (octave_value (sz));
4624 }
4625
4626 if (m_paperpositionmode.is ("auto"))
4627 m_paperposition.set (get_auto_paperposition ());
4628}
4629
4630void
4631figure::properties::update_papersize ()
4632{
4633 Matrix sz = get_papersize ().matrix_value ();
4634 if (sz(0) > sz(1))
4635 {
4636 std::swap (sz(0), sz(1));
4637 m_papersize.set (octave_value (sz));
4638 m_paperorientation.set (octave_value ("landscape"));
4639 }
4640 else
4641 {
4642 m_paperorientation.set ("portrait");
4643 }
4644
4645 std::string punits = get_paperunits ();
4646 if (punits == "centimeters")
4647 {
4648 sz(0) /= 2.54;
4649 sz(1) /= 2.54;
4650 }
4651 else if (punits == "points")
4652 {
4653 sz(0) /= 72.0;
4654 sz(1) /= 72.0;
4655 }
4656 if (punits == "normalized")
4657 {
4658 if (get_papertype () == "<custom>")
4659 error ("set: can't set the papertype to <custom> when the paperunits is normalized");
4660 }
4661 else
4662 {
4663 // FIXME: The papersizes info is also in papersize_from_type().
4664 // Both should be rewritten to avoid the duplication.
4665 // Don't Repeat Yourself (DRY) principle.
4666 std::string ptype = "<custom>";
4667 const double mm2in = 1.0 / 25.4;
4668 const double tol = 0.01;
4669
4670 if (std::abs (sz(0) - 8.5) + std::abs (sz(1) - 11.0) < tol)
4671 ptype = "usletter";
4672 else if (std::abs (sz(0) - 8.5) + std::abs (sz(1) - 14.0) < tol)
4673 ptype = "uslegal";
4674 else if (std::abs (sz(0) - 11.0) + std::abs (sz(1) - 17.0) < tol)
4675 ptype = "tabloid";
4676 else if (std::abs (sz(0) - 841.0 * mm2in)
4677 + std::abs (sz(1) - 1198.0 * mm2in) < tol)
4678 ptype = "a0";
4679 else if (std::abs (sz(0) - 594.0 * mm2in)
4680 + std::abs (sz(1) - 841.0 * mm2in) < tol)
4681 ptype = "a1";
4682 else if (std::abs (sz(0) - 420.0 * mm2in)
4683 + std::abs (sz(1) - 594.0 * mm2in) < tol)
4684 ptype = "a2";
4685 else if (std::abs (sz(0) - 297.0 * mm2in)
4686 + std::abs (sz(1) - 420.0 * mm2in) < tol)
4687 ptype = "a3";
4688 else if (std::abs (sz(0) - 210.0 * mm2in)
4689 + std::abs (sz(1) - 297.0 * mm2in) < tol)
4690 ptype = "a4";
4691 else if (std::abs (sz(0) - 148.0 * mm2in)
4692 + std::abs (sz(1) - 210.0 * mm2in) < tol)
4693 ptype = "a5";
4694 else if (std::abs (sz(0) - 1029.0 * mm2in)
4695 + std::abs (sz(1) - 1456.0 * mm2in) < tol)
4696 ptype = "b0";
4697 else if (std::abs (sz(0) - 728.0 * mm2in)
4698 + std::abs (sz(1) - 1028.0 * mm2in) < tol)
4699 ptype = "b1";
4700 else if (std::abs (sz(0) - 514.0 * mm2in)
4701 + std::abs (sz(1) - 728.0 * mm2in) < tol)
4702 ptype = "b2";
4703 else if (std::abs (sz(0) - 364.0 * mm2in)
4704 + std::abs (sz(1) - 514.0 * mm2in) < tol)
4705 ptype = "b3";
4706 else if (std::abs (sz(0) - 257.0 * mm2in)
4707 + std::abs (sz(1) - 364.0 * mm2in) < tol)
4708 ptype = "b4";
4709 else if (std::abs (sz(0) - 182.0 * mm2in)
4710 + std::abs (sz(1) - 257.0 * mm2in) < tol)
4711 ptype = "b5";
4712 else if (std::abs (sz(0) - 9.0)
4713 + std::abs (sz(1) - 12.0) < tol)
4714 ptype = "arch-a";
4715 else if (std::abs (sz(0) - 12.0)
4716 + std::abs (sz(1) - 18.0) < tol)
4717 ptype = "arch-b";
4718 else if (std::abs (sz(0) - 18.0)
4719 + std::abs (sz(1) - 24.0) < tol)
4720 ptype = "arch-c";
4721 else if (std::abs (sz(0) - 24.0)
4722 + std::abs (sz(1) - 36.0) < tol)
4723 ptype = "arch-d";
4724 else if (std::abs (sz(0) - 36.0)
4725 + std::abs (sz(1) - 48.0) < tol)
4726 ptype = "arch-e";
4727 else if (std::abs (sz(0) - 8.5)
4728 + std::abs (sz(1) - 11.0) < tol)
4729 ptype = "a";
4730 else if (std::abs (sz(0) - 11.0)
4731 + std::abs (sz(1) - 17.0) < tol)
4732 ptype = "b";
4733 else if (std::abs (sz(0) - 17.0)
4734 + std::abs (sz(1) - 22.0) < tol)
4735 ptype = "c";
4736 else if (std::abs (sz(0) - 22.0)
4737 + std::abs (sz(1) - 34.0) < tol)
4738 ptype = "d";
4739 else if (std::abs (sz(0) - 34.0)
4740 + std::abs (sz(1) - 43.0) < tol)
4741 ptype = "e";
4742 // Call papertype.set rather than set_papertype to avoid loops between
4743 // update_papersize and update_papertype
4744 m_papertype.set (ptype);
4745 }
4746 if (punits == "centimeters")
4747 {
4748 sz(0) *= 2.54;
4749 sz(1) *= 2.54;
4750 }
4751 else if (punits == "points")
4752 {
4753 sz(0) *= 72.0;
4754 sz(1) *= 72.0;
4755 }
4756 if (get_paperorientation () == "landscape")
4757 {
4758 std::swap (sz(0), sz(1));
4759 m_papersize.set (octave_value (sz));
4760 }
4761
4762 if (m_paperpositionmode.is ("auto"))
4763 m_paperposition.set (get_auto_paperposition ());
4764}
4765
4766/*
4767%!test
4768%! hf = figure ("visible", "off");
4769%! unwind_protect
4770%! set (hf, "paperunits", "inches");
4771%! set (hf, "papersize", [5, 4]);
4772%! set (hf, "paperunits", "points");
4773%! assert (get (hf, "papersize"), [5, 4] * 72, 1);
4774%! papersize = get (hf, "papersize");
4775%! set (hf, "papersize", papersize + 1);
4776%! set (hf, "papersize", papersize);
4777%! assert (get (hf, "papersize"), [5, 4] * 72, 1);
4778%! unwind_protect_cleanup
4779%! close (hf);
4780%! end_unwind_protect
4781
4782%!test
4783%! hf = figure ("visible", "off");
4784%! unwind_protect
4785%! set (hf, "paperunits", "inches");
4786%! set (hf, "papersize", [5, 4]);
4787%! set (hf, "paperunits", "centimeters");
4788%! assert (get (hf, "papersize"), [5, 4] * 2.54, 2.54/72);
4789%! papersize = get (hf, "papersize");
4790%! set (hf, "papersize", papersize + 1);
4791%! set (hf, "papersize", papersize);
4792%! assert (get (hf, "papersize"), [5, 4] * 2.54, 2.54/72);
4793%! unwind_protect_cleanup
4794%! close (hf);
4795%! end_unwind_protect
4796*/
4797
4798void
4799figure::properties::update_paperorientation ()
4800{
4801 std::string porient = get_paperorientation ();
4802 Matrix sz = get_papersize ().matrix_value ();
4803 if ((sz(0) > sz(1) && porient == "portrait")
4804 || (sz(0) < sz(1) && porient == "landscape"))
4805 {
4806 std::swap (sz(0), sz(1));
4807 // Call papertype.set rather than set_papertype to avoid loops
4808 // between update_papersize and update_papertype
4809 m_papersize.set (octave_value (sz));
4810 }
4811
4812 if (m_paperpositionmode.is ("auto"))
4813 m_paperposition.set (get_auto_paperposition ());
4814}
4815
4816/*
4817%!test
4818%! hf = figure ("visible", "off");
4819%! unwind_protect
4820%! tol = 100 * eps ();
4821%! ## UPPER case and MiXed case is part of test and should not be changed.
4822%! set (hf, "paperorientation", "PORTRAIT");
4823%! set (hf, "paperunits", "inches");
4824%! set (hf, "papertype", "USletter");
4825%! assert (get (hf, "papersize"), [8.5, 11.0], tol);
4826%! set (hf, "paperorientation", "Landscape");
4827%! assert (get (hf, "papersize"), [11.0, 8.5], tol);
4828%! set (hf, "paperunits", "centimeters");
4829%! assert (get (hf, "papersize"), [11.0, 8.5] * 2.54, tol);
4830%! set (hf, "papertype", "a4");
4831%! assert (get (hf, "papersize"), [29.7, 21.0], tol);
4832%! set (hf, "paperunits", "inches", "papersize", [8.5, 11.0]);
4833%! assert (get (hf, "papertype"), "usletter");
4834%! assert (get (hf, "paperorientation"), "portrait");
4835%! set (hf, "papersize", [11.0, 8.5]);
4836%! assert (get (hf, "papertype"), "usletter");
4837%! assert (get (hf, "paperorientation"), "landscape");
4838%! unwind_protect_cleanup
4839%! close (hf);
4840%! end_unwind_protect
4841*/
4842
4843void
4844figure::properties::set_units (const octave_value& val)
4845{
4846 caseless_str old_units = get_units ();
4847
4848 if (m_units.set (val, true))
4849 {
4850 update_units (old_units);
4851 mark_modified ();
4852 }
4853}
4854
4855void
4856figure::properties::update_units (const caseless_str& old_units)
4857{
4858 m_position.set (convert_position (get_position ().matrix_value (),
4859 old_units, get_units (),
4860 screen_size_pixels ()), false);
4861}
4862
4863/*
4864%!test
4865%! hf = figure ("visible", "off");
4866%! old_units = get (0, "units");
4867%! unwind_protect
4868%! set (0, "units", "pixels");
4869%! rsz = get (0, "screensize");
4870%! set (gcf (), "units", "pixels");
4871%! fsz = get (gcf (), "position");
4872%! set (gcf (), "units", "normalized");
4873%! pos = get (gcf (), "position");
4874%! assert (pos, (fsz - [1, 1, 0, 0]) ./ rsz([3, 4, 3, 4]));
4875%! unwind_protect_cleanup
4876%! close (hf);
4877%! set (0, "units", old_units);
4878%! end_unwind_protect
4879*/
4880
4881std::string
4882figure::properties::get_title () const
4883{
4884 std::string title;
4885 if (! get_number ().isempty () && is_numbertitle ())
4886 {
4887 std::ostringstream os;
4888 std::string nm = get_name ();
4889
4890 os << "Figure " << m___myhandle__.value ();
4891 if (! nm.empty ())
4892 os << ": " << get_name ();
4893
4894 title = os.str ();
4895 }
4896 else
4897 title = get_name ();
4898
4899 // Qt will use QCoreApplication name (set in main-window.cc)
4900 // if the name is empty, so force blank.
4901 if (title.empty ())
4902 title = " ";
4903
4904 return title;
4905}
4906
4908figure::get_default (const caseless_str& name) const
4909{
4910 octave_value retval = m_default_properties.lookup (name);
4911
4912 if (retval.is_undefined ())
4913 {
4914 graphics_handle parent_h = get_parent ();
4915
4916 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
4917
4918 graphics_object parent_go = gh_mgr.get_object (parent_h);
4919
4920 retval = parent_go.get_default (name);
4921 }
4922
4923 return retval;
4924}
4925
4926void
4927figure::reset_default_properties ()
4928{
4929 // empty list of local defaults
4930 m_default_properties = property_list ();
4931 property_list::pval_map_type plist = m_properties.factory_defaults ();
4932
4933 plist.erase ("units");
4934 plist.erase ("position");
4935 plist.erase ("innerposition");
4936 plist.erase ("outerposition");
4937 plist.erase ("paperunits");
4938 plist.erase ("paperposition");
4939 plist.erase ("paperpositionmode");
4940 plist.erase ("windowstyle");
4941
4942 remove_all_listeners ();
4943 xreset_default_properties (get_handle (), plist);
4944}
4945
4946// ---------------------------------------------------------------------
4947
4948void
4949axes::properties::init ()
4950{
4951 m_position.add_constraint (dim_vector (1, 4));
4952 m_outerposition.add_constraint (dim_vector (1, 4));
4953 m_tightinset.add_constraint (dim_vector (1, 4));
4954 m_looseinset.add_constraint (dim_vector (1, 4));
4955 m_colororder.add_constraint (dim_vector (-1, 3));
4956 m_dataaspectratio.add_constraint (3);
4957 m_dataaspectratio.add_constraint ("min", 0, false);
4958 m_dataaspectratio.add_constraint (FINITE);
4959 m_plotboxaspectratio.add_constraint (3);
4960 m_plotboxaspectratio.add_constraint ("min", 0, false);
4961 m_plotboxaspectratio.add_constraint (FINITE);
4962 // FIXME: Should these use dimension vectors? Currently can set 'xlim' to
4963 // any matrix size, but only first two elements are used.
4964 m_alim.add_constraint (2);
4965 m_alim.add_constraint (NOT_NAN);
4966 m_clim.add_constraint (2);
4967 m_clim.add_constraint (NOT_NAN);
4968 m_xlim.add_constraint (2);
4969 m_xlim.add_constraint (NOT_NAN);
4970 m_ylim.add_constraint (2);
4971 m_ylim.add_constraint (NOT_NAN);
4972 m_zlim.add_constraint (2);
4973 m_zlim.add_constraint (NOT_NAN);
4974 m_xtick.add_constraint (dim_vector (1, -1));
4975 m_xtick.add_constraint (FINITE);
4976 m_ytick.add_constraint (dim_vector (1, -1));
4977 m_ytick.add_constraint (FINITE);
4978 m_ztick.add_constraint (dim_vector (1, -1));
4979 m_ztick.add_constraint (FINITE);
4980 m_ticklength.add_constraint (dim_vector (1, 2));
4981 Matrix vw (1, 2, 0);
4982 vw(1) = 90;
4983 m_view = vw;
4984 m_view.add_constraint (dim_vector (1, 2));
4985 m_cameraposition.add_constraint (3);
4986 m_cameraposition.add_constraint (FINITE);
4987 m_cameratarget.add_constraint (3);
4988 m_cameratarget.add_constraint (FINITE);
4989 Matrix upv (1, 3, 0.0);
4990 upv(2) = 1.0;
4991 m_cameraupvector = upv;
4992 m_cameraupvector.add_constraint (3);
4993 m_cameraupvector.add_constraint (FINITE);
4994 m_cameraviewangle.add_constraint (FINITE);
4995 m_currentpoint.add_constraint (dim_vector (2, 3));
4996
4997 // Range constraints for double properties
4998 m_fontsize.add_constraint ("min", 0.0, false);
4999 m_gridalpha.add_constraint ("min", 0.0, true);
5000 m_gridalpha.add_constraint ("max", 1.0, true);
5001 m_labelfontsizemultiplier.add_constraint ("min", 0.0, false);
5002 m_linewidth.add_constraint ("min", 0.0, false);
5003 m_minorgridalpha.add_constraint ("min", 0.0, true);
5004 m_minorgridalpha.add_constraint ("max", 1.0, true);
5005 m_titlefontsizemultiplier.add_constraint ("min", 0.0, false);
5006
5007 // No constraints for hidden transform properties
5008 update_font ();
5009
5010 m_x_zlim.resize (1, 2);
5011
5012 m_sx = "linear";
5013 m_sy = "linear";
5014 m_sz = "linear";
5015
5016 calc_ticklabels (m_xtick, m_xticklabel, m_xscale.is ("log"),
5017 xaxislocation_is ("origin"),
5018 m_yscale.is ("log") ? 2 : (yaxislocation_is ("origin") ? 0 :
5019 (yaxislocation_is ("left") ? -1 : 1)), m_xlim);
5020 calc_ticklabels (m_ytick, m_yticklabel, m_yscale.is ("log"),
5021 yaxislocation_is ("origin"),
5022 m_xscale.is ("log") ? 2 : (xaxislocation_is ("origin") ? 0 :
5023 (xaxislocation_is ("bottom") ? -1 : 1)), m_ylim);
5024 calc_ticklabels (m_ztick, m_zticklabel, m_zscale.is ("log"),
5025 false, 2, m_zlim);
5026
5027 xset (m_xlabel.handle_value (), "handlevisibility", "off");
5028 xset (m_ylabel.handle_value (), "handlevisibility", "off");
5029 xset (m_zlabel.handle_value (), "handlevisibility", "off");
5030 xset (m_title.handle_value (), "handlevisibility", "off");
5031
5032 xset (m_xlabel.handle_value (), "horizontalalignment", "center");
5033 xset (m_xlabel.handle_value (), "horizontalalignmentmode", "auto");
5034 xset (m_ylabel.handle_value (), "horizontalalignment", "center");
5035 xset (m_ylabel.handle_value (), "horizontalalignmentmode", "auto");
5036 xset (m_zlabel.handle_value (), "horizontalalignment", "right");
5037 xset (m_zlabel.handle_value (), "horizontalalignmentmode", "auto");
5038 xset (m_title.handle_value (), "horizontalalignment", "center");
5039 xset (m_title.handle_value (), "horizontalalignmentmode", "auto");
5040
5041 xset (m_xlabel.handle_value (), "verticalalignment", "top");
5042 xset (m_xlabel.handle_value (), "verticalalignmentmode", "auto");
5043 xset (m_ylabel.handle_value (), "verticalalignment", "bottom");
5044 xset (m_ylabel.handle_value (), "verticalalignmentmode", "auto");
5045 xset (m_title.handle_value (), "verticalalignment", "bottom");
5046 xset (m_title.handle_value (), "verticalalignmentmode", "auto");
5047
5048 xset (m_ylabel.handle_value (), "rotation", 90.0);
5049 xset (m_ylabel.handle_value (), "rotationmode", "auto");
5050
5051 xset (m_zlabel.handle_value (), "visible", "off");
5052
5053 xset (m_xlabel.handle_value (), "clipping", "off");
5054 xset (m_ylabel.handle_value (), "clipping", "off");
5055 xset (m_zlabel.handle_value (), "clipping", "off");
5056 xset (m_title.handle_value (), "clipping", "off");
5057
5058 xset (m_xlabel.handle_value (), "__autopos_tag__", "xlabel");
5059 xset (m_ylabel.handle_value (), "__autopos_tag__", "ylabel");
5060 xset (m_zlabel.handle_value (), "__autopos_tag__", "zlabel");
5061 xset (m_title.handle_value (), "__autopos_tag__", "title");
5062
5063 double fs = m_labelfontsizemultiplier.double_value () *
5064 m_fontsize.double_value ();
5065 xset (m_xlabel.handle_value (), "fontsize", octave_value (fs));
5066 xset (m_ylabel.handle_value (), "fontsize", octave_value (fs));
5067 xset (m_zlabel.handle_value (), "fontsize", octave_value (fs));
5068 fs = m_titlefontsizemultiplier.double_value () * m_fontsize.double_value ();
5069 xset (m_title.handle_value (), "fontsize", octave_value (fs));
5070 xset (m_title.handle_value (), "fontweight", m_titlefontweight.get ());
5071
5072 adopt (m_xlabel.handle_value ());
5073 adopt (m_ylabel.handle_value ());
5074 adopt (m_zlabel.handle_value ());
5075 adopt (m_title.handle_value ());
5076
5077 Matrix tlooseinset = default_axes_position ();
5078 tlooseinset(2) = 1-tlooseinset(0)-tlooseinset(2);
5079 tlooseinset(3) = 1-tlooseinset(1)-tlooseinset(3);
5080 m_looseinset = tlooseinset;
5081}
5082
5083/*
5084## Test validation of axes double properties range
5085%!test
5086%! hf = figure ("visible", "off");
5087%! unwind_protect
5088%! hax = axes ("parent", hf);
5089%! try
5090%! set (hax, "linewidth", -1);
5091%! catch
5092%! err = lasterr ();
5093%! end_try_catch
5094%! assert (err, 'set: "linewidth" must be greater than 0');
5095%! try
5096%! set (hax, "minorgridalpha", 1.5);
5097%! catch
5098%! err = lasterr ();
5099%! end_try_catch
5100%! assert (err, 'set: "minorgridalpha" must be less than or equal to 1');
5101%! unwind_protect_cleanup
5102%! delete (hf);
5103%! end_unwind_protect
5104*/
5105
5106Matrix
5107axes::properties::calc_tightbox (const Matrix& init_pos)
5108{
5109 Matrix pos = init_pos;
5110
5111 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
5112
5113 graphics_object go = gh_mgr.get_object (get_parent ());
5114
5115 Matrix parent_bb = go.get_properties ().get_boundingbox (true);
5116
5117 // FIXME: The layout should be clean at this stage and we should not have to
5118 // update ticks and labels positions here again. See bug #48718.
5119 update_ticklength ();
5120
5121 Matrix ext = get_extent (true, true);
5122 ext(1) = parent_bb(3) - ext(1) - ext(3);
5123 ext(0)++;
5124 ext(1)++;
5125 ext = convert_position (ext, "pixels", get_units (),
5126 parent_bb.extract_n (0, 2, 1, 2));
5127 if (ext(0) < pos(0))
5128 {
5129 pos(2) += pos(0)-ext(0);
5130 pos(0) = ext(0);
5131 }
5132 if (ext(0)+ext(2) > pos(0)+pos(2))
5133 pos(2) = ext(0)+ext(2)-pos(0);
5134
5135 if (ext(1) < pos(1))
5136 {
5137 pos(3) += pos(1)-ext(1);
5138 pos(1) = ext(1);
5139 }
5140 if (ext(1)+ext(3) > pos(1)+pos(3))
5141 pos(3) = ext(1)+ext(3)-pos(1);
5142
5143 return pos;
5144}
5145
5146void
5147axes::properties::sync_positions ()
5148{
5149 // First part is equivalent to 'update_tightinset ()'
5150 if (m_positionconstraint.is ("innerposition"))
5151 update_position ();
5152 else
5153 update_outerposition ();
5154 caseless_str old_units = get_units ();
5155 set_units ("normalized");
5156 Matrix pos = m_position.get ().matrix_value ();
5157 Matrix outpos = m_outerposition.get ().matrix_value ();
5158 Matrix tightpos = calc_tightbox (pos);
5159 Matrix tinset (1, 4, 1.0);
5160 tinset(0) = pos(0)-tightpos(0);
5161 tinset(1) = pos(1)-tightpos(1);
5162 tinset(2) = tightpos(0)+tightpos(2)-pos(0)-pos(2);
5163 tinset(3) = tightpos(1)+tightpos(3)-pos(1)-pos(3);
5164 m_tightinset = tinset;
5165 set_units (old_units);
5166 update_transform ();
5167 if (m_positionconstraint.is ("innerposition"))
5168 update_position ();
5169 else
5170 update_outerposition ();
5171}
5172
5173/*
5174%!testif HAVE_OPENGL, HAVE_QT; have_window_system () && any (strcmp ("qt", available_graphics_toolkits ()))
5175%! hf = figure ("visible", "off");
5176%! graphics_toolkit (hf, "qt");
5177%! unwind_protect
5178%! subplot (2,1,1); plot (rand (10,1)); subplot (2,1,2); plot (rand (10,1));
5179%! hax = findall (gcf (), "type", "axes");
5180%! positions = cell2mat (get (hax, "position"));
5181%! outerpositions = cell2mat (get (hax, "outerposition"));
5182%! looseinsets = cell2mat (get (hax, "looseinset"));
5183%! tightinsets = cell2mat (get (hax, "tightinset"));
5184%! subplot (2,1,1); plot (rand (10,1)); subplot (2,1,2); plot (rand (10,1));
5185%! hax = findall (gcf (), "type", "axes");
5186%! assert (cell2mat (get (hax, "position")), positions, 1e-4);
5187%! assert (cell2mat (get (hax, "outerposition")), outerpositions, 1e-4);
5188%! assert (cell2mat (get (hax, "looseinset")), looseinsets, 1e-4);
5189%! assert (cell2mat (get (hax, "tightinset")), tightinsets, 1e-4);
5190%! unwind_protect_cleanup
5191%! close (hf);
5192%! end_unwind_protect
5193
5194%!testif HAVE_OPENGL, HAVE_QT; have_window_system () && any (strcmp ("qt", available_graphics_toolkits ()))
5195%! hf = figure ("visible", "off");
5196%! graphics_toolkit (hf, "qt");
5197%! fpos = get (hf, "position");
5198%! unwind_protect
5199%! plot (rand (3));
5200%! position = get (gca, "position");
5201%! outerposition = get (gca, "outerposition");
5202%! looseinset = get (gca, "looseinset");
5203%! tightinset = get (gca, "tightinset");
5204%! set (hf, "position", [fpos(1:2), 2*fpos(3:4)]);
5205%! set (hf, "position", fpos);
5206%! assert (get (gca, "outerposition"), outerposition, 0.001);
5207%! assert (get (gca, "position"), position, 0.001);
5208%! assert (get (gca, "looseinset"), looseinset, 0.001);
5209%! assert (get (gca, "tightinset"), tightinset, 0.001);
5210%! unwind_protect_cleanup
5211%! close (hf);
5212%! end_unwind_protect
5213
5214%!testif HAVE_OPENGL, HAVE_QT; have_window_system () && any (strcmp ("qt", available_graphics_toolkits ()))
5215%! hf = figure ("visible", "off");
5216%! graphics_toolkit (hf, "qt");
5217%! fpos = get (hf, "position");
5218%! set (gca, "positionconstraint", "innerposition");
5219%! unwind_protect
5220%! plot (rand (3));
5221%! position = get (gca, "position");
5222%! outerposition = get (gca, "outerposition");
5223%! looseinset = get (gca, "looseinset");
5224%! tightinset = get (gca, "tightinset");
5225%! set (hf, "position", [fpos(1:2), 2*fpos(3:4)]);
5226%! set (hf, "position", fpos);
5227%! assert (get (gca, "position"), position, 0.001);
5228%! assert (get (gca, "outerposition"), outerposition, 0.001);
5229%! assert (get (gca, "looseinset"), looseinset, 0.001);
5230%! assert (get (gca, "tightinset"), tightinset, 0.001);
5231%! unwind_protect_cleanup
5232%! close (hf);
5233%! end_unwind_protect
5234*/
5235
5236void
5237axes::properties::set_text_child (handle_property& hp,
5238 const std::string& who,
5239 const octave_value& v)
5240{
5241 if (v.is_string ())
5242 {
5243 xset (hp.handle_value (), "string", v);
5244 return;
5245 }
5246
5247 graphics_handle val;
5248
5249 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
5250
5251 graphics_object go = gh_mgr.get_object (gh_mgr.lookup (v));
5252
5253 if (go.isa ("text"))
5254 val = octave::reparent (v, "set", who, m___myhandle__, false);
5255 else
5256 {
5257 std::string cname = v.class_name ();
5258
5259 error ("set: expecting text graphics object or character string for %s property, found %s",
5260 who.c_str (), cname.c_str ());
5261 }
5262
5263 xset (val, "handlevisibility", "off");
5264
5265 gh_mgr.free (hp.handle_value ());
5266
5267 hp = val;
5268
5269 adopt (hp.handle_value ());
5270}
5271
5272void
5273axes::properties::set_xlabel (const octave_value& v)
5274{
5275 set_text_child (m_xlabel, "xlabel", v);
5276 xset (m_xlabel.handle_value (), "positionmode", "auto");
5277 xset (m_xlabel.handle_value (), "rotationmode", "auto");
5278 xset (m_xlabel.handle_value (), "horizontalalignmentmode", "auto");
5279 xset (m_xlabel.handle_value (), "verticalalignmentmode", "auto");
5280 xset (m_xlabel.handle_value (), "clipping", "off");
5281 xset (m_xlabel.handle_value (), "color", get_xcolor ());
5282 xset (m_xlabel.handle_value (), "__autopos_tag__", "xlabel");
5283 update_xlabel_position ();
5284}
5285
5286void
5287axes::properties::set_ylabel (const octave_value& v)
5288{
5289 set_text_child (m_ylabel, "ylabel", v);
5290 xset (m_ylabel.handle_value (), "positionmode", "auto");
5291 xset (m_ylabel.handle_value (), "rotationmode", "auto");
5292 xset (m_ylabel.handle_value (), "horizontalalignmentmode", "auto");
5293 xset (m_ylabel.handle_value (), "verticalalignmentmode", "auto");
5294 xset (m_ylabel.handle_value (), "clipping", "off");
5295 xset (m_ylabel.handle_value (), "color", get_ycolor ());
5296 xset (m_ylabel.handle_value (), "__autopos_tag__", "ylabel");
5297 update_ylabel_position ();
5298}
5299
5300void
5301axes::properties::set_zlabel (const octave_value& v)
5302{
5303 set_text_child (m_zlabel, "zlabel", v);
5304 xset (m_zlabel.handle_value (), "positionmode", "auto");
5305 xset (m_zlabel.handle_value (), "rotationmode", "auto");
5306 xset (m_zlabel.handle_value (), "horizontalalignmentmode", "auto");
5307 xset (m_zlabel.handle_value (), "verticalalignmentmode", "auto");
5308 xset (m_zlabel.handle_value (), "clipping", "off");
5309 xset (m_zlabel.handle_value (), "color", get_zcolor ());
5310 xset (m_zlabel.handle_value (), "__autopos_tag__", "zlabel");
5311 update_zlabel_position ();
5312}
5313
5314void
5315axes::properties::set_title (const octave_value& v)
5316{
5317 set_text_child (m_title, "title", v);
5318 xset (m_title.handle_value (), "positionmode", "auto");
5319 xset (m_title.handle_value (), "horizontalalignment", "center");
5320 xset (m_title.handle_value (), "horizontalalignmentmode", "auto");
5321 xset (m_title.handle_value (), "verticalalignment", "bottom");
5322 xset (m_title.handle_value (), "verticalalignmentmode", "auto");
5323 xset (m_title.handle_value (), "clipping", "off");
5324 xset (m_title.handle_value (), "__autopos_tag__", "title");
5325 update_title_position ();
5326}
5327
5328void
5329axes::properties::set_defaults (base_graphics_object& bgo,
5330 const std::string& mode)
5331{
5332 // FIXME: Should this have all properties in it?
5333 // Including ones we do don't implement?
5334
5335 // FIXME: This function is probably never called without mode == "reset".
5336 // Error if this is not true. If there are reports of problems
5337 // then figure out what code is calling it with the mode set to
5338 // something else. It's apparently been this way since
5339 // 1/6/2017 without any reports. Maybe we should eliminate the
5340 // mode argument?
5341
5342 if (mode != "reset")
5343 error (R"(axes::properties::set_defaults: expected mode = "reset", found "%s")", mode.c_str ());
5344
5345 Matrix tlim (1, 2, 0.0);
5346 tlim(1) = 1;
5347 m_alim = tlim;
5348 m_clim = tlim;
5349 m_xlim = tlim;
5350 m_ylim = tlim;
5351 m_zlim = tlim;
5352
5353 m_alimmode = "auto";
5354 m_climmode = "auto";
5355 m_xlimmode = "auto";
5356 m_ylimmode = "auto";
5357 m_zlimmode = "auto";
5358
5359 m_alphamap = Matrix ();
5360 m_alphascale = "linear";
5361
5362 m_ambientlightcolor = Matrix (1, 3, 1.0);
5363
5364 m_box = "off";
5365 m_boxstyle = "back";
5366
5367 // Note: camera properties (not mode) will be set in update_transform
5368 m_camerapositionmode = "auto";
5369 m_cameratargetmode = "auto";
5370 m_cameraupvectormode = "auto";
5371 m_cameraviewanglemode = "auto";
5372
5373 m_clippingstyle = "3dbox";
5374
5375 m_color = color_values ("white");
5376 m_colormap = Matrix ();
5377 m_colororder = default_colororder ();
5378 m_colororderindex = 1.0;
5379 m_colorscale = "linear";
5380
5381 // Note: dataspectratio (not mode) will be set through update_aspectratios
5382 m_dataaspectratiomode = "auto";
5383
5384 m_fontangle = "normal";
5385 m_fontname = OCTAVE_DEFAULT_FONTNAME;
5386 m_fontsize = 10;
5387 m_fontsizemode = "auto";
5388 m_fontsmoothing = "on";
5389 m_fontunits = "points";
5390 m_fontweight = "normal";
5391
5392 m_gridalpha = 0.15;
5393 m_gridalphamode = "auto";
5394 m_gridcolor = color_values (0.15, 0.15, 0.15);
5395 m_gridcolormode = "auto";
5396 m_gridlinestyle = "-";
5397
5398 m_labelfontsizemultiplier = 1.1;
5399
5400 m_layer = "bottom";
5401
5402 m_linestyleorder = "-";
5403 m_linestyleorderindex = 1.0;
5404
5405 m_linewidth = 0.5;
5406
5407 m_minorgridalpha = 0.25;
5408 m_minorgridalphamode = "auto";
5409 m_minorgridcolor = color_values (0.1, 0.1, 0.1);
5410 m_minorgridcolormode = "auto";
5411 m_minorgridlinestyle = ":";
5412
5413 m_nextplot = "replace";
5414
5415 // Note: plotboxaspectratio will be set through update_aspectratios
5416 m_plotboxaspectratiomode = "auto";
5417 m_projection = "orthographic";
5418
5419 m_sortmethod = "depth";
5420
5421 m_tickdir = "in";
5422 m_tickdirmode = "auto";
5423 m_ticklabelinterpreter = "tex";
5424 m_ticklength = default_axes_ticklength ();
5425
5426 m_tightinset = Matrix (1, 4, 0.0);
5427
5428 m_titlefontsizemultiplier = 1.1;
5429 m_titlefontweight = "bold";
5430
5431 Matrix tview (1, 2, 0.0);
5432 tview(1) = 90;
5433 m_view = tview;
5434
5435 m_xaxislocation = "bottom";
5436
5437 m_xcolor = color_values (0.15, 0.15, 0.15);
5438 m_xcolormode = "auto";
5439 m_xdir = "normal";
5440 m_xgrid = "off";
5441 m_xlimitmethod = "tickaligned";
5442 m_xminorgrid = "off";
5443 m_xminortick = "off";
5444 m_xscale = "linear";
5445 m_xtick = Matrix ();
5446 m_xticklabel = "";
5447 m_xticklabelmode = "auto";
5448 m_xticklabelrotation = 0.0;
5449 m_xtickmode = "auto";
5450
5451 m_yaxislocation = "left";
5452
5453 m_ycolor = color_values (0.15, 0.15, 0.15);
5454 m_ycolormode = "auto";
5455 m_ydir = "normal";
5456 m_ygrid = "off";
5457 m_ylimitmethod = "tickaligned";
5458 m_yminorgrid = "off";
5459 m_yminortick = "off";
5460 m_yscale = "linear";
5461 m_ytick = Matrix ();
5462 m_yticklabel = "";
5463 m_yticklabelmode = "auto";
5464 m_yticklabelrotation = 0.0;
5465 m_ytickmode = "auto";
5466
5467 m_zcolor = color_values (0.15, 0.15, 0.15);
5468 m_zcolormode = "auto";
5469 m_zdir = "normal";
5470 m_zgrid = "off";
5471 m_zlimitmethod = "tickaligned";
5472 m_zminorgrid = "off";
5473 m_zminortick = "off";
5474 m_zscale = "linear";
5475 m_ztick = Matrix ();
5476 m_zticklabel = "";
5477 m_zticklabelmode = "auto";
5478 m_zticklabelrotation = 0.0;
5479 m_ztickmode = "auto";
5480
5481 m_sx = "linear";
5482 m_sy = "linear";
5483 m_sz = "linear";
5484
5485 m_visible = "on";
5486
5487 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
5488
5489 graphics_object go = gh_mgr.get_object (m_xlabel.handle_value ());
5490 go.reset_default_properties ();
5491 go = gh_mgr.get_object (m_ylabel.handle_value ());
5492 go.reset_default_properties ();
5493 go = gh_mgr.get_object (m_zlabel.handle_value ());
5494 go.reset_default_properties ();
5495 go = gh_mgr.get_object (m_title.handle_value ());
5496 go.reset_default_properties ();
5497
5498 xset (m_xlabel.handle_value (), "handlevisibility", "off");
5499 xset (m_ylabel.handle_value (), "handlevisibility", "off");
5500 xset (m_zlabel.handle_value (), "handlevisibility", "off");
5501 xset (m_title.handle_value (), "handlevisibility", "off");
5502
5503 xset (m_xlabel.handle_value (), "horizontalalignment", "center");
5504 xset (m_xlabel.handle_value (), "horizontalalignmentmode", "auto");
5505 xset (m_ylabel.handle_value (), "horizontalalignment", "center");
5506 xset (m_ylabel.handle_value (), "horizontalalignmentmode", "auto");
5507 xset (m_zlabel.handle_value (), "horizontalalignment", "right");
5508 xset (m_zlabel.handle_value (), "horizontalalignmentmode", "auto");
5509 xset (m_title.handle_value (), "horizontalalignment", "center");
5510 xset (m_title.handle_value (), "horizontalalignmentmode", "auto");
5511
5512 xset (m_xlabel.handle_value (), "verticalalignment", "top");
5513 xset (m_xlabel.handle_value (), "verticalalignmentmode", "auto");
5514 xset (m_ylabel.handle_value (), "verticalalignment", "bottom");
5515 xset (m_ylabel.handle_value (), "verticalalignmentmode", "auto");
5516 xset (m_title.handle_value (), "verticalalignment", "bottom");
5517 xset (m_title.handle_value (), "verticalalignmentmode", "auto");
5518
5519 xset (m_ylabel.handle_value (), "rotation", 90.0);
5520 xset (m_ylabel.handle_value (), "rotationmode", "auto");
5521
5522 xset (m_zlabel.handle_value (), "visible", "off");
5523
5524 xset (m_xlabel.handle_value (), "clipping", "off");
5525 xset (m_ylabel.handle_value (), "clipping", "off");
5526 xset (m_zlabel.handle_value (), "clipping", "off");
5527 xset (m_title.handle_value (), "clipping", "off");
5528
5529 xset (m_xlabel.handle_value (), "__autopos_tag__", "xlabel");
5530 xset (m_ylabel.handle_value (), "__autopos_tag__", "ylabel");
5531 xset (m_zlabel.handle_value (), "__autopos_tag__", "zlabel");
5532 xset (m_title.handle_value (), "__autopos_tag__", "title");
5533
5534 double fs;
5535 fs = m_labelfontsizemultiplier.double_value () * m_fontsize.double_value ();
5536 xset (m_xlabel.handle_value (), "fontsize", octave_value (fs));
5537 xset (m_ylabel.handle_value (), "fontsize", octave_value (fs));
5538 xset (m_zlabel.handle_value (), "fontsize", octave_value (fs));
5539 fs = m_titlefontsizemultiplier.double_value () * m_fontsize.double_value ();
5540 xset (m_title.handle_value (), "fontsize", octave_value (fs));
5541 xset (m_title.handle_value (), "fontweight", m_titlefontweight.get ());
5542
5543 update_transform ();
5544 sync_positions ();
5545 override_defaults (bgo);
5546}
5547
5549axes::properties::get_colormap () const
5550{
5551 if (m___colormap__.get ().isempty ())
5552 {
5553 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
5554
5555 graphics_object go (gh_mgr.get_object (get___myhandle__ ()));
5556 graphics_object go_f (go.get_ancestor ("figure"));
5557 figure::properties& figure_props
5558 = reinterpret_cast<figure::properties&> (go_f.get_properties ());
5559 return figure_props.get_colormap ();
5560 }
5561
5562 return get___colormap__ ();
5563}
5564
5565void
5566axes::properties::delete_text_child (handle_property& hp, bool from_root)
5567{
5568 graphics_handle h = hp.handle_value ();
5569
5570 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
5571
5572 if (h.ok ())
5573 {
5574 graphics_object go = gh_mgr.get_object (h);
5575
5576 if (go.valid_object ())
5577 gh_mgr.free (h, from_root);
5578 }
5579
5580 // FIXME: is it necessary to check whether the axes object is
5581 // being deleted now? I think this function is only called when an
5582 // individual child object is delete and not when the parent axes
5583 // object is deleted.
5584
5585 if (! is_beingdeleted ())
5586 {
5587 hp = gh_mgr.make_graphics_handle ("text", m___myhandle__, false, false);
5588
5589 xset (hp.handle_value (), "handlevisibility", "off");
5590
5591 adopt (hp.handle_value ());
5592 }
5593}
5594
5595void
5596axes::properties::remove_child (const graphics_handle& h, bool from_root)
5597{
5598 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
5599
5600 graphics_object go = gh_mgr.get_object (h);
5601
5602 if (m_xlabel.handle_value ().ok () && h == m_xlabel.handle_value ())
5603 {
5604 delete_text_child (m_xlabel, from_root);
5605 update_xlabel_position ();
5606 }
5607 else if (m_ylabel.handle_value ().ok () && h == m_ylabel.handle_value ())
5608 {
5609 delete_text_child (m_ylabel, from_root);
5610 update_ylabel_position ();
5611 }
5612 else if (m_zlabel.handle_value ().ok () && h == m_zlabel.handle_value ())
5613 {
5614 delete_text_child (m_zlabel, from_root);
5615 update_zlabel_position ();
5616 }
5617 else if (m_title.handle_value ().ok () && h == m_title.handle_value ())
5618 {
5619 delete_text_child (m_title, from_root);
5620 update_title_position ();
5621 }
5622 else if (get_num_lights () > 0 && go.isa ("light")
5623 && go.get_properties ().is_visible ())
5624 decrease_num_lights ();
5625
5626 if (go.valid_object ())
5627 base_properties::remove_child (h, from_root);
5628
5629}
5630
5631void
5632axes::properties::adopt (const graphics_handle& h)
5633{
5634 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
5635
5636 graphics_object go (gh_mgr.get_object (h));
5637
5638 if (go.isa ("light") && go.get_properties ().is_visible ())
5639 increase_num_lights ();
5640
5641 base_properties::adopt (h);
5642
5643 // FIXME: For performance reasons, we would like to call
5644 // update_axis_limits ("xlim", h);
5645 // which updates the limits based ONLY on the new data from h.
5646 // But this isn't working properly at the moment, so we
5647 // call the other form which invokes a full tree traversal of all
5648 // of the axes children.
5649 if (xlimmode_is ("auto"))
5650 update_axis_limits ("xlim");
5651
5652 if (ylimmode_is ("auto"))
5653 update_axis_limits ("ylim");
5654
5655 if (zlimmode_is ("auto"))
5656 update_axis_limits ("zlim");
5657
5658 if (climmode_is ("auto"))
5659 update_axis_limits ("clim");
5660
5661 if (climmode_is ("auto"))
5662 update_axis_limits ("alim");
5663}
5664
5665inline Matrix
5667{
5668 Matrix m (4, 4, 0.0);
5669
5670 for (int i = 0; i < 4; i++)
5671 m(i, i) = 1;
5672
5673 return m;
5674}
5675
5676inline ColumnVector
5678{
5679 ColumnVector v (4, 0.0);
5680
5681 v(3) = 1;
5682
5683 return v;
5684}
5685
5686inline ColumnVector
5687xform_vector (double x, double y, double z)
5688{
5689 ColumnVector v (4, 1.0);
5690
5691 v(0) = x;
5692 v(1) = y;
5693 v(2) = z;
5694
5695 return v;
5696}
5697
5698inline ColumnVector
5699transform (const Matrix& m, double x, double y, double z)
5700{
5701 return (m * xform_vector (x, y, z));
5702}
5703
5704inline Matrix
5705xform_scale (double x, double y, double z)
5706{
5707 Matrix m (4, 4, 0.0);
5708
5709 m(0, 0) = x;
5710 m(1, 1) = y;
5711 m(2, 2) = z;
5712 m(3, 3) = 1;
5713
5714 return m;
5715}
5716
5717inline Matrix
5718xform_translate (double x, double y, double z)
5719{
5720 Matrix m = xform_matrix ();
5721
5722 m(0, 3) = x;
5723 m(1, 3) = y;
5724 m(2, 3) = z;
5725 m(3, 3) = 1;
5726
5727 return m;
5728}
5729
5730inline void
5731scale (Matrix& m, double x, double y, double z)
5732{
5733 m = m * xform_scale (x, y, z);
5734}
5735
5736inline void
5737translate (Matrix& m, double x, double y, double z)
5738{
5739 m = m * xform_translate (x, y, z);
5740}
5741
5742inline void
5744{
5745 v = m * v;
5746}
5747
5748inline void
5749scale (ColumnVector& v, double x, double y, double z)
5750{
5751 v(0) *= x;
5752 v(1) *= y;
5753 v(2) *= z;
5754}
5755
5756inline void
5757translate (ColumnVector& v, double x, double y, double z)
5758{
5759 v(0) += x;
5760 v(1) += y;
5761 v(2) += z;
5762}
5763
5764inline void
5766{
5767 double fact = 1.0 / sqrt (v(0)*v(0)+v(1)*v(1)+v(2)*v(2));
5768 scale (v, fact, fact, fact);
5769}
5770
5771inline double
5772dot (const ColumnVector& v1, const ColumnVector& v2)
5773{
5774 return (v1(0)*v2(0)+v1(1)*v2(1)+v1(2)*v2(2));
5775}
5776
5777inline double
5779{
5780 return sqrt (dot (v, v));
5781}
5782
5783inline ColumnVector
5785{
5787
5788 r(0) = v1(1)*v2(2) - v1(2)*v2(1);
5789 r(1) = v1(2)*v2(0) - v1(0)*v2(2);
5790 r(2) = v1(0)*v2(1) - v1(1)*v2(0);
5791
5792 return r;
5793}
5794
5795inline Matrix
5797{
5798 static double data[32] =
5799 {
5800 0, 0, 0, 1,
5801 1, 0, 0, 1,
5802 0, 1, 0, 1,
5803 0, 0, 1, 1,
5804 1, 1, 0, 1,
5805 1, 0, 1, 1,
5806 0, 1, 1, 1,
5807 1, 1, 1, 1
5808 };
5809 Matrix m (4, 8);
5810
5811 memcpy (m.rwdata (), data, sizeof (double)*32);
5812
5813 return m;
5814}
5815
5816inline ColumnVector
5818{
5819 ColumnVector retval (4, 1.0);
5820
5821 memcpy (retval.rwdata (), m.data (), sizeof (double)*3);
5822
5823 return retval;
5824}
5825
5826inline RowVector
5828{
5829 return v.extract_n (0, 3).transpose ();
5830}
5831
5832void
5833axes::properties::update_camera ()
5834{
5835 double xd = (xdir_is ("normal") ? 1 : -1);
5836 double yd = (ydir_is ("normal") ? 1 : -1);
5837 double zd = (zdir_is ("normal") ? 1 : -1);
5838
5839 Matrix xlimits = m_sx.scale (get_xlim ().matrix_value ());
5840 Matrix ylimits = m_sy.scale (get_ylim ().matrix_value ());
5841 Matrix zlimits = m_sz.scale (get_zlim ().matrix_value ());
5842
5843 double xo = xlimits(xd > 0 ? 0 : 1);
5844 double yo = ylimits(yd > 0 ? 0 : 1);
5845 double zo = zlimits(zd > 0 ? 0 : 1);
5846
5847 Matrix pb = get_plotboxaspectratio ().matrix_value ();
5848
5849 bool autocam = (camerapositionmode_is ("auto")
5850 && cameratargetmode_is ("auto")
5851 && cameraupvectormode_is ("auto")
5852 && cameraviewanglemode_is ("auto"));
5853 bool dowarp = (autocam && dataaspectratiomode_is ("auto")
5854 && plotboxaspectratiomode_is ("auto"));
5855
5856 ColumnVector c_eye (xform_vector ());
5857 ColumnVector c_center (xform_vector ());
5858 ColumnVector c_upv (xform_vector ());
5859
5860 if (cameratargetmode_is ("auto"))
5861 {
5862 c_center(0) = (xlimits(0) + xlimits(1)) / 2;
5863 c_center(1) = (ylimits(0) + ylimits(1)) / 2;
5864 c_center(2) = (zlimits(0) + zlimits(1)) / 2;
5865
5866 m_cameratarget = xform2cam (c_center);
5867 }
5868 else
5869 c_center = cam2xform (get_cameratarget ().matrix_value ());
5870
5871 if (camerapositionmode_is ("auto"))
5872 {
5873 Matrix tview = get_view ().matrix_value ();
5874 double az = tview(0);
5875 double el = tview(1);
5876 double d = 5 * sqrt (pb(0)*pb(0) + pb(1)*pb(1) + pb(2)*pb(2));
5877
5878 if (el == 90 || el == -90)
5879 c_eye(2) = d*octave::math::signum (el);
5880 else
5881 {
5882 az *= M_PI/180.0;
5883 el *= M_PI/180.0;
5884 c_eye(0) = d * cos (el) * sin (az);
5885 c_eye(1) = -d* cos (el) * cos (az);
5886 c_eye(2) = d * sin (el);
5887 }
5888 c_eye(0) = c_eye(0)*(xlimits(1)-xlimits(0))/(xd*pb(0))+c_center(0);
5889 c_eye(1) = c_eye(1)*(ylimits(1)-ylimits(0))/(yd*pb(1))+c_center(1);
5890 c_eye(2) = c_eye(2)*(zlimits(1)-zlimits(0))/(zd*pb(2))+c_center(2);
5891
5892 m_cameraposition = xform2cam (c_eye);
5893 }
5894 else
5895 c_eye = cam2xform (get_cameraposition ().matrix_value ());
5896
5897 if (cameraupvectormode_is ("auto"))
5898 {
5899 Matrix tview = get_view ().matrix_value ();
5900 double az = tview(0);
5901 double el = tview(1);
5902
5903 if (el == 90 || el == -90)
5904 {
5905 c_upv(0) = -octave::math::signum (el)
5906 * sin (az*M_PI/180.0)*(xlimits(1)-xlimits(0))/pb(0);
5907 c_upv(1) = octave::math::signum (el)
5908 * cos (az*M_PI/180.0)*(ylimits(1)-ylimits(0))/pb(1);
5909 }
5910 else
5911 c_upv(2) = 1;
5912
5913 m_cameraupvector = xform2cam (c_upv);
5914 }
5915 else
5916 c_upv = cam2xform (get_cameraupvector ().matrix_value ());
5917
5918 Matrix x_view = xform_matrix ();
5919 Matrix x_projection = xform_matrix ();
5920 Matrix x_viewport = xform_matrix ();
5921 Matrix x_normrender;
5922 Matrix x_pre = xform_matrix ();
5923
5924 m_x_render = xform_matrix ();
5925 m_x_render_inv = xform_matrix ();
5926
5927 scale (x_pre, pb(0), pb(1), pb(2));
5928 translate (x_pre, -0.5, -0.5, -0.5);
5929 scale (x_pre, xd/(xlimits(1)-xlimits(0)), yd/(ylimits(1)-ylimits(0)),
5930 zd/(zlimits(1)-zlimits(0)));
5931 translate (x_pre, -xo, -yo, -zo);
5932
5933 xform (c_eye, x_pre);
5934 xform (c_center, x_pre);
5935 scale (c_upv, pb(0)/(xlimits(1)-xlimits(0)), pb(1)/(ylimits(1)-ylimits(0)),
5936 pb(2)/(zlimits(1)-zlimits(0)));
5937 translate (c_center, -c_eye(0), -c_eye(1), -c_eye(2));
5938
5939 ColumnVector F (c_center), f (F), UP (c_upv);
5940 normalize (f);
5941 normalize (UP);
5942
5943 if (std::abs (dot (f, UP)) > 1e-15)
5944 {
5945 double fa = 1 / sqrt (1 - f(2)*f(2));
5946 scale (UP, fa, fa, fa);
5947 }
5948
5949 ColumnVector s = cross (f, UP);
5950 ColumnVector u = cross (s, f);
5951
5952 scale (x_view, 1, 1, -1);
5953 Matrix l = xform_matrix ();
5954 l(0, 0) = s(0); l(0, 1) = s(1); l(0, 2) = s(2);
5955 l(1, 0) = u(0); l(1, 1) = u(1); l(1, 2) = u(2);
5956 l(2, 0) = -f(0); l(2, 1) = -f(1); l(2, 2) = -f(2);
5957 x_view = x_view * l;
5958 translate (x_view, -c_eye(0), -c_eye(1), -c_eye(2));
5959 scale (x_view, pb(0), pb(1), pb(2));
5960 translate (x_view, -0.5, -0.5, -0.5);
5961
5962 Matrix x_cube = x_view * unit_cube ();
5963 ColumnVector cmin = x_cube.row_min ();
5964 ColumnVector cmax = x_cube.row_max ();
5965 double xM = cmax(0) - cmin(0);
5966 double yM = cmax(1) - cmin(1);
5967
5968 Matrix bb = get_boundingbox (true);
5969
5970 double v_angle;
5971
5972 if (cameraviewanglemode_is ("auto"))
5973 {
5974 double af;
5975
5976 // FIXME: was this really needed? When compared to Matlab, it
5977 // does not seem to be required. Need investigation with concrete
5978 // graphics toolkit to see results visually.
5979 if (false && dowarp)
5980 af = (1.0 / (xM > yM ? xM : yM));
5981 else
5982 {
5983 if ((bb(2)/bb(3)) > (xM/yM))
5984 af = 1.0 / yM;
5985 else
5986 af = 1.0 / xM;
5987 }
5988 v_angle = 2 * (180.0 / M_PI) * atan (1 / (2 * af * norm (F)));
5989
5990 m_cameraviewangle = v_angle;
5991 }
5992 else
5993 v_angle = get_cameraviewangle ();
5994
5995 double pf = 1 / (2 * tan ((v_angle / 2) * M_PI / 180.0) * norm (F));
5996 scale (x_projection, pf, pf, 1);
5997
5998 if (dowarp)
5999 {
6000 xM *= pf;
6001 yM *= pf;
6002 translate (x_viewport, bb(0)+bb(2)/2, bb(1)+bb(3)/2, 0);
6003 scale (x_viewport, bb(2)/xM, -bb(3)/yM, 1);
6004 }
6005 else
6006 {
6007 double pix = 1;
6008 if (autocam)
6009 {
6010 if ((bb(2)/bb(3)) > (xM/yM))
6011 pix = bb(3);
6012 else
6013 pix = bb(2);
6014 }
6015 else
6016 pix = (bb(2) < bb(3) ? bb(2) : bb(3));
6017 translate (x_viewport, bb(0)+bb(2)/2, bb(1)+bb(3)/2, 0);
6018 scale (x_viewport, pix, -pix, 1);
6019 }
6020
6021 x_normrender = x_viewport * x_projection * x_view;
6022
6023 x_cube = x_normrender * unit_cube ();
6024 cmin = x_cube.row_min ();
6025 cmax = x_cube.row_max ();
6026 m_x_zlim.resize (1, 2);
6027 m_x_zlim(0) = cmin(2);
6028 m_x_zlim(1) = cmax(2);
6029
6030 m_x_render = x_normrender;
6031 scale (m_x_render, xd/(xlimits(1)-xlimits(0)), yd/(ylimits(1)-ylimits(0)),
6032 zd/(zlimits(1)-zlimits(0)));
6033 translate (m_x_render, -xo, -yo, -zo);
6034
6035 m_x_render_inv = m_x_render.inverse ();
6036
6037 // Note: these matrices are a slight modified version of the regular matrices,
6038 // more suited for OpenGL rendering (m_x_gl_mat1 => light => m_x_gl_mat2)
6039 m_x_gl_mat1 = x_view;
6040 scale (m_x_gl_mat1, xd/(xlimits(1)-xlimits(0)), yd/(ylimits(1)-ylimits(0)),
6041 zd/(zlimits(1)-zlimits(0)));
6042 translate (m_x_gl_mat1, -xo, -yo, -zo);
6043 m_x_gl_mat2 = x_viewport * x_projection;
6044}
6045
6046static bool updating_axes_layout = false;
6047
6048void
6049axes::properties::update_axes_layout ()
6050{
6051 if (updating_axes_layout)
6052 return;
6053
6054 graphics_xform xform = get_transform ();
6055
6056 double xd = (xdir_is ("normal") ? 1 : -1);
6057 double yd = (ydir_is ("normal") ? 1 : -1);
6058 double zd = (zdir_is ("normal") ? 1 : -1);
6059
6060 const Matrix xlims = xform.xscale (get_xlim ().matrix_value ());
6061 const Matrix ylims = xform.yscale (get_ylim ().matrix_value ());
6062 const Matrix zlims = xform.zscale (get_zlim ().matrix_value ());
6063
6064 double x_min, x_max, y_min, y_max, z_min, z_max;
6065 x_min = xlims(0), x_max = xlims(1);
6066 y_min = ylims(0), y_max = ylims(1);
6067 z_min = zlims(0), z_max = zlims(1);
6068
6069 ColumnVector p1, p2, dir (3);
6070
6071 m_xstate = m_ystate = m_zstate = AXE_ANY_DIR;
6072
6073 p1 = xform.transform (x_min, (y_min+y_max)/2, (z_min+z_max)/2, false);
6074 p2 = xform.transform (x_max, (y_min+y_max)/2, (z_min+z_max)/2, false);
6075 dir(0) = octave::math::round (p2(0) - p1(0));
6076 dir(1) = octave::math::round (p2(1) - p1(1));
6077 dir(2) = (p2(2) - p1(2));
6078 if (dir(0) == 0 && dir(1) == 0)
6079 m_xstate = AXE_DEPTH_DIR;
6080 else if (dir(2) == 0)
6081 {
6082 if (dir(0) == 0)
6083 m_xstate = AXE_VERT_DIR;
6084 else if (dir(1) == 0)
6085 m_xstate = AXE_HORZ_DIR;
6086 }
6087
6088 if (dir(2) == 0)
6089 {
6090 if (dir(1) == 0)
6091 m_xPlane = (dir(0) > 0 ? x_max : x_min);
6092 else
6093 m_xPlane = (dir(1) < 0 ? x_max : x_min);
6094 }
6095 else
6096 m_xPlane = (dir(2) < 0 ? x_min : x_max);
6097
6098 m_xPlaneN = (m_xPlane == x_min ? x_max : x_min);
6099 m_fx = (x_max - x_min) / sqrt (dir(0)*dir(0) + dir(1)*dir(1));
6100
6101 p1 = xform.transform ((x_min + x_max)/2, y_min, (z_min + z_max)/2, false);
6102 p2 = xform.transform ((x_min + x_max)/2, y_max, (z_min + z_max)/2, false);
6103 dir(0) = octave::math::round (p2(0) - p1(0));
6104 dir(1) = octave::math::round (p2(1) - p1(1));
6105 dir(2) = (p2(2) - p1(2));
6106 if (dir(0) == 0 && dir(1) == 0)
6107 m_ystate = AXE_DEPTH_DIR;
6108 else if (dir(2) == 0)
6109 {
6110 if (dir(0) == 0)
6111 m_ystate = AXE_VERT_DIR;
6112 else if (dir(1) == 0)
6113 m_ystate = AXE_HORZ_DIR;
6114 }
6115
6116 if (dir(2) == 0)
6117 {
6118 if (dir(1) == 0)
6119 m_yPlane = (dir(0) > 0 ? y_max : y_min);
6120 else
6121 m_yPlane = (dir(1) < 0 ? y_max : y_min);
6122 }
6123 else
6124 m_yPlane = (dir(2) < 0 ? y_min : y_max);
6125
6126 m_yPlaneN = (m_yPlane == y_min ? y_max : y_min);
6127 m_fy = (y_max - y_min) / sqrt (dir(0)*dir(0) + dir(1)*dir(1));
6128
6129 p1 = xform.transform ((x_min + x_max)/2, (y_min + y_max)/2, z_min, false);
6130 p2 = xform.transform ((x_min + x_max)/2, (y_min + y_max)/2, z_max, false);
6131 dir(0) = octave::math::round (p2(0) - p1(0));
6132 dir(1) = octave::math::round (p2(1) - p1(1));
6133 dir(2) = (p2(2) - p1(2));
6134 if (dir(0) == 0 && dir(1) == 0)
6135 m_zstate = AXE_DEPTH_DIR;
6136 else if (dir(2) == 0)
6137 {
6138 if (dir(0) == 0)
6139 m_zstate = AXE_VERT_DIR;
6140 else if (dir(1) == 0)
6141 m_zstate = AXE_HORZ_DIR;
6142 }
6143
6144 if (dir(2) == 0)
6145 {
6146 if (dir(1) == 0)
6147 m_zPlane = (dir(0) > 0 ? z_min : z_max);
6148 else
6149 m_zPlane = (dir(1) < 0 ? z_min : z_max);
6150 }
6151 else
6152 m_zPlane = (dir(2) < 0 ? z_min : z_max);
6153
6154 m_zPlaneN = (m_zPlane == z_min ? z_max : z_min);
6155 m_fz = (z_max - z_min) / sqrt (dir(0)*dir(0) + dir(1)*dir(1));
6156
6157 octave::unwind_protect_var<bool> restore_var (updating_axes_layout, true);
6158
6159 m_xySym = (xd*yd*(m_xPlane-m_xPlaneN)*(m_yPlane-m_yPlaneN) > 0);
6160 m_zSign = (zd*(m_zPlane-m_zPlaneN) <= 0);
6161 m_xyzSym = (m_zSign ? m_xySym : ! m_xySym);
6162 m_xpTick = (m_zSign ? m_xPlaneN : m_xPlane);
6163 m_ypTick = (m_zSign ? m_yPlaneN : m_yPlane);
6164 m_zpTick = (m_zSign ? m_zPlane : m_zPlaneN);
6165 m_xpTickN = (m_zSign ? m_xPlane : m_xPlaneN);
6166 m_ypTickN = (m_zSign ? m_yPlane : m_yPlaneN);
6167 m_zpTickN = (m_zSign ? m_zPlaneN : m_zPlane);
6168
6169 // 2D mode
6170 m_x2Dtop = false;
6171 m_y2Dright = false;
6172 m_layer2Dtop = false;
6173 if (m_xstate == AXE_HORZ_DIR && m_ystate == AXE_VERT_DIR)
6174 {
6175 Matrix ylimits = get_ylim ().matrix_value ();
6176 if (xaxislocation_is ("top")
6177 || (yscale_is ("log") && xaxislocation_is ("origin")
6178 && (ylimits(1) < 0.)))
6179 {
6180 std::swap (m_yPlane, m_yPlaneN);
6181 m_x2Dtop = true;
6182 }
6183 m_ypTick = m_yPlaneN;
6184 m_ypTickN = m_yPlane;
6185 Matrix xlimits = get_xlim ().matrix_value ();
6186 if (yaxislocation_is ("right")
6187 || (xscale_is ("log") && yaxislocation_is ("origin")
6188 && (xlimits(1) < 0.)))
6189 {
6190 std::swap (m_xPlane, m_xPlaneN);
6191 m_y2Dright = true;
6192 }
6193 m_xpTick = m_xPlaneN;
6194 m_xpTickN = m_xPlane;
6195 if (layer_is ("top"))
6196 {
6197 m_zpTick = m_zPlaneN;
6198 m_layer2Dtop = true;
6199 }
6200 else
6201 m_zpTick = m_zPlane;
6202 }
6203
6204 Matrix viewmat = get_view ().matrix_value ();
6205 m_nearhoriz = std::abs (viewmat(1)) <= 5;
6206 m_is2D = viewmat(1) == 90;
6207
6208 update_ticklength ();
6209}
6210
6211void
6212axes::properties::update_ticklength ()
6213{
6214 bool mode2D = (((m_xstate > AXE_DEPTH_DIR ? 1 : 0) +
6215 (m_ystate > AXE_DEPTH_DIR ? 1 : 0) +
6216 (m_zstate > AXE_DEPTH_DIR ? 1 : 0)) == 2);
6217
6218 if (tickdirmode_is ("auto"))
6219 m_tickdir.set (mode2D ? "in" : "out", true);
6220
6221 double ticksign;
6222 std::string tickdir = get_tickdir ();
6223 if (tickdir == "in")
6224 ticksign = -1;
6225 else if (tickdir == "out")
6226 ticksign = 1;
6227 else if (tickdir == "both")
6228 ticksign = 1;
6229 else // tickdir == "none"
6230 ticksign = 0;
6231
6232 Matrix bbox = get_boundingbox (true);
6233 Matrix ticklen = get_ticklength ().matrix_value ();
6234 ticklen(0) *= std::max (bbox(2), bbox(3));
6235 // FIXME: This algorithm is not Matlab-compatible. See bug #55483.
6236 // Scale the results of Octave's algorithm for better visuals.
6237 ticklen(1) *= (0.76 * std::max (bbox(2), bbox(3)));
6238
6239 m_xticklen = ticksign * (mode2D ? ticklen(0) : ticklen(1));
6240 m_yticklen = ticksign * (mode2D ? ticklen(0) : ticklen(1));
6241 m_zticklen = ticksign * (mode2D ? ticklen(0) : ticklen(1));
6242
6243 double offset = get___fontsize_points__ () / 2;
6244
6245 m_xtickoffset = (mode2D ? std::max (0., m_xticklen) : std::abs (m_xticklen)) +
6246 (m_xstate == AXE_HORZ_DIR ? offset*1.5 : offset);
6247 m_ytickoffset = (mode2D ? std::max (0., m_yticklen) : std::abs (m_yticklen)) +
6248 (m_ystate == AXE_HORZ_DIR ? offset*1.5 : offset);
6249 m_ztickoffset = (mode2D ? std::max (0., m_zticklen) : std::abs (m_zticklen)) +
6250 (m_zstate == AXE_HORZ_DIR ? offset*1.5 : offset);
6251
6252 update_xlabel_position ();
6253 update_ylabel_position ();
6254 update_zlabel_position ();
6255 update_title_position ();
6256}
6257
6258/*
6259## FIXME: A demo can't be called in a C++ file. This should be made a test
6260## or moved to a .m file where it can be called.
6261%!demo
6262%! clf;
6263%! subplot (2,1,1);
6264%! plot (rand (3));
6265%! xlabel xlabel;
6266%! ylabel ylabel;
6267%! title title;
6268%! subplot (2,1,2);
6269%! plot (rand (3));
6270%! set (gca, "ticklength", get (gca, "ticklength") * 2, "tickdir", "out");
6271%! xlabel xlabel;
6272%! ylabel ylabel;
6273%! title title;
6274*/
6275
6276static ColumnVector
6277convert_label_position (const ColumnVector& p,
6278 const text::properties& props,
6279 const graphics_xform& xform,
6280 const Matrix& bbox)
6281{
6282 ColumnVector retval;
6283
6284 std::string to_units = props.get_units ();
6285
6286 if (to_units != "data")
6287 {
6288 ColumnVector v = xform.transform (p(0), p(1), p(2));
6289
6290 retval.resize (3);
6291
6292 retval(0) = v(0) - bbox(0) + 1;
6293 retval(1) = bbox(1) + bbox(3) - v(1) + 1;
6294 retval(2) = 0;
6295
6296 retval = convert_position (retval, "pixels", to_units,
6297 bbox.extract_n (0, 2, 1, 2));
6298 }
6299 else
6300 retval = p;
6301
6302 return retval;
6303}
6304
6305static bool updating_xlabel_position = false;
6306
6307void
6308axes::properties::update_xlabel_position ()
6309{
6310 if (updating_xlabel_position)
6311 return;
6312
6313 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
6314
6315 graphics_object go = gh_mgr.get_object (get_xlabel ());
6316
6317 if (! go.valid_object ())
6318 return;
6319
6320 text::properties& xlabel_props
6321 = reinterpret_cast<text::properties&> (go.get_properties ());
6322
6323 bool isempty = xlabel_props.get_string ().isempty ();
6324
6325 octave::unwind_protect_var<bool>
6326 restore_var (updating_xlabel_position, true);
6327
6328 if (! isempty)
6329 {
6330 if (xlabel_props.horizontalalignmentmode_is ("auto"))
6331 {
6332 xlabel_props.set_horizontalalignment
6333 (m_xstate > AXE_DEPTH_DIR ? "center"
6334 : (m_xyzSym ? "left" : "right"));
6335
6336 xlabel_props.set_horizontalalignmentmode ("auto");
6337 }
6338
6339 if (xlabel_props.verticalalignmentmode_is ("auto"))
6340 {
6341 xlabel_props.set_verticalalignment
6342 (m_xstate == AXE_VERT_DIR || m_x2Dtop ? "bottom" : "top");
6343
6344 xlabel_props.set_verticalalignmentmode ("auto");
6345 }
6346 }
6347
6348 if (xlabel_props.positionmode_is ("auto")
6349 || xlabel_props.rotationmode_is ("auto"))
6350 {
6351 graphics_xform xform = get_transform ();
6352
6353 Matrix ext (1, 2, 0.0);
6354 ext = get_ticklabel_extents (get_xtick ().matrix_value (),
6355 get_xticklabel ().string_vector_value (),
6356 get_xlim ().matrix_value ());
6357
6358 double margin = 5;
6359 double wmax = ext(0) + margin;
6360 double hmax = ext(1) + margin;
6361 double angle = 0.0;
6362 ColumnVector p
6363 = graphics_xform::xform_vector ((m_xpTickN + m_xpTick)/2, m_ypTick, m_zpTick);
6364
6365 bool tick_along_z = m_nearhoriz || octave::math::isinf (m_fy);
6366 if (tick_along_z)
6367 p(2) += (octave::math::signum (m_zpTick - m_zpTickN) * m_fz * m_xtickoffset);
6368 else
6369 p(1) += (octave::math::signum (m_ypTick - m_ypTickN) * m_fy * m_xtickoffset);
6370
6371 p = xform.transform (p(0), p(1), p(2), false);
6372
6373 switch (m_xstate)
6374 {
6375 case AXE_ANY_DIR:
6376 p(0) += (m_xyzSym ? wmax : -wmax);
6377 p(1) += hmax;
6378 break;
6379
6380 case AXE_VERT_DIR:
6381 p(0) -= wmax;
6382 angle = 90;
6383 break;
6384
6385 case AXE_HORZ_DIR:
6386 p(1) += (m_x2Dtop ? -hmax : hmax);
6387 break;
6388 }
6389
6390 if (xlabel_props.positionmode_is ("auto"))
6391 {
6392 p = xform.untransform (p(0), p(1), p(2), true);
6393
6394 p = convert_label_position (p, xlabel_props, xform,
6395 get_extent (false));
6396
6397 xlabel_props.set_position (p.extract_n (0, 3).transpose ());
6398 xlabel_props.set_positionmode ("auto");
6399 }
6400
6401 if (! isempty && xlabel_props.rotationmode_is ("auto"))
6402 {
6403 xlabel_props.set_rotation (angle);
6404 xlabel_props.set_rotationmode ("auto");
6405 }
6406 }
6407}
6408
6409static bool updating_ylabel_position = false;
6410
6411void
6412axes::properties::update_ylabel_position ()
6413{
6414 if (updating_ylabel_position)
6415 return;
6416
6417 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
6418
6419 graphics_object go = gh_mgr.get_object (get_ylabel ());
6420
6421 if (! go.valid_object ())
6422 return;
6423
6424 text::properties& ylabel_props
6425 = reinterpret_cast<text::properties&> (go.get_properties ());
6426
6427 bool isempty = ylabel_props.get_string ().isempty ();
6428
6429 octave::unwind_protect_var<bool>
6430 restore_var (updating_ylabel_position, true);
6431
6432 if (! isempty)
6433 {
6434 if (ylabel_props.horizontalalignmentmode_is ("auto"))
6435 {
6436 ylabel_props.set_horizontalalignment
6437 (m_ystate > AXE_DEPTH_DIR ? "center"
6438 : (! m_xyzSym ? "left" : "right"));
6439
6440 ylabel_props.set_horizontalalignmentmode ("auto");
6441 }
6442
6443 if (ylabel_props.verticalalignmentmode_is ("auto"))
6444 {
6445 ylabel_props.set_verticalalignment
6446 (m_ystate == AXE_VERT_DIR && ! m_y2Dright ? "bottom" : "top");
6447
6448 ylabel_props.set_verticalalignmentmode ("auto");
6449 }
6450 }
6451
6452 if (ylabel_props.positionmode_is ("auto")
6453 || ylabel_props.rotationmode_is ("auto"))
6454 {
6455 graphics_xform xform = get_transform ();
6456
6457 Matrix ext (1, 2, 0.0);
6458
6459 ext = get_ticklabel_extents (get_ytick ().matrix_value (),
6460 get_yticklabel ().string_vector_value (),
6461 get_ylim ().matrix_value ());
6462 double margin = 5;
6463 double wmax = ext(0) + margin;
6464 double hmax = ext(1) + margin;
6465 double angle = 0.0;
6466 ColumnVector p
6467 = graphics_xform::xform_vector (m_xpTick, (m_ypTickN + m_ypTick)/2, m_zpTick);
6468
6469 bool tick_along_z = m_nearhoriz || octave::math::isinf (m_fx);
6470 if (tick_along_z)
6471 p(2) += (octave::math::signum (m_zpTick - m_zpTickN) * m_fz * m_ytickoffset);
6472 else
6473 p(0) += (octave::math::signum (m_xpTick - m_xpTickN) * m_fx * m_ytickoffset);
6474
6475 p = xform.transform (p(0), p(1), p(2), false);
6476
6477 switch (m_ystate)
6478 {
6479 case AXE_ANY_DIR:
6480 p(0) += (! m_xyzSym ? wmax : -wmax);
6481 p(1) += hmax;
6482 break;
6483
6484 case AXE_VERT_DIR:
6485 p(0) += (m_y2Dright ? wmax : -wmax);
6486 angle = 90;
6487 break;
6488
6489 case AXE_HORZ_DIR:
6490 p(1) += hmax;
6491 break;
6492 }
6493
6494 if (ylabel_props.positionmode_is ("auto"))
6495 {
6496 p = xform.untransform (p(0), p(1), p(2), true);
6497
6498 p = convert_label_position (p, ylabel_props, xform,
6499 get_extent (false));
6500
6501 ylabel_props.set_position (p.extract_n (0, 3).transpose ());
6502 ylabel_props.set_positionmode ("auto");
6503 }
6504
6505 if (! isempty && ylabel_props.rotationmode_is ("auto"))
6506 {
6507 ylabel_props.set_rotation (angle);
6508 ylabel_props.set_rotationmode ("auto");
6509 }
6510 }
6511}
6512
6513static bool updating_zlabel_position = false;
6514
6515void
6516axes::properties::update_zlabel_position ()
6517{
6518 if (updating_zlabel_position)
6519 return;
6520
6521 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
6522
6523 graphics_object go = gh_mgr.get_object (get_zlabel ());
6524
6525 if (! go.valid_object ())
6526 return;
6527
6528 text::properties& zlabel_props
6529 = reinterpret_cast<text::properties&> (go.get_properties ());
6530
6531 bool camAuto = cameraupvectormode_is ("auto");
6532 bool isempty = zlabel_props.get_string ().isempty ();
6533
6534 octave::unwind_protect_var<bool>
6535 restore_updating_zlabel_position (updating_zlabel_position, true);
6536
6537 if (! isempty)
6538 {
6539 if (zlabel_props.horizontalalignmentmode_is ("auto"))
6540 {
6541 zlabel_props.set_horizontalalignment
6542 ((m_zstate > AXE_DEPTH_DIR || camAuto) ? "center" : "right");
6543
6544 zlabel_props.set_horizontalalignmentmode ("auto");
6545 }
6546
6547 if (zlabel_props.verticalalignmentmode_is ("auto"))
6548 {
6549 zlabel_props.set_verticalalignment
6550 (m_zstate == AXE_VERT_DIR
6551 ? "bottom" : ((m_zSign || camAuto) ? "bottom" : "top"));
6552
6553 zlabel_props.set_verticalalignmentmode ("auto");
6554 }
6555 }
6556
6557 if (zlabel_props.positionmode_is ("auto")
6558 || zlabel_props.rotationmode_is ("auto"))
6559 {
6560 graphics_xform xform = get_transform ();
6561
6562 Matrix ext (1, 2, 0.0);
6563 ext = get_ticklabel_extents (get_ztick ().matrix_value (),
6564 get_zticklabel ().string_vector_value (),
6565 get_zlim ().matrix_value ());
6566
6567 double margin = 5;
6568 double wmax = ext(0) + margin;
6569 double hmax = ext(1) + margin;
6570 double angle = 0.0;
6571 ColumnVector p;
6572
6573 if (m_xySym)
6574 {
6575 p = graphics_xform::xform_vector (m_xPlaneN, m_yPlane,
6576 (m_zpTickN + m_zpTick)/2);
6577 if (octave::math::isinf (m_fy))
6578 p(0) += octave::math::signum (m_xPlaneN - m_xPlane) * m_fx * m_ztickoffset;
6579 else
6580 p(1) += octave::math::signum (m_yPlane - m_yPlaneN) * m_fy * m_ztickoffset;
6581 }
6582 else
6583 {
6584 p = graphics_xform::xform_vector (m_xPlane, m_yPlaneN,
6585 (m_zpTickN + m_zpTick)/2);
6586 if (octave::math::isinf (m_fx))
6587 p(1) += octave::math::signum (m_yPlaneN - m_yPlane) * m_fy * m_ztickoffset;
6588 else
6589 p(0) += octave::math::signum (m_xPlane - m_xPlaneN) * m_fx * m_ztickoffset;
6590 }
6591
6592 p = xform.transform (p(0), p(1), p(2), false);
6593
6594 switch (m_zstate)
6595 {
6596 case AXE_ANY_DIR:
6597 if (camAuto)
6598 {
6599 p(0) -= wmax;
6600 angle = 90;
6601 }
6602
6603 // FIXME: what's the correct offset?
6604 //
6605 // p[0] += (! m_xySym ? wmax : -wmax);
6606 // p[1] += (m_zSign ? hmax : -hmax);
6607
6608 break;
6609
6610 case AXE_VERT_DIR:
6611 p(0) -= wmax;
6612 angle = 90;
6613 break;
6614
6615 case AXE_HORZ_DIR:
6616 p(1) += hmax;
6617 break;
6618 }
6619
6620 if (zlabel_props.positionmode_is ("auto"))
6621 {
6622 p = xform.untransform (p(0), p(1), p(2), true);
6623
6624 p = convert_label_position (p, zlabel_props, xform,
6625 get_extent (false));
6626
6627 zlabel_props.set_position (p.extract_n (0, 3).transpose ());
6628 zlabel_props.set_positionmode ("auto");
6629 }
6630
6631 if (! isempty && zlabel_props.rotationmode_is ("auto"))
6632 {
6633 zlabel_props.set_rotation (angle);
6634 zlabel_props.set_rotationmode ("auto");
6635 }
6636 }
6637}
6638
6639static bool updating_title_position = false;
6640
6641void
6642axes::properties::update_title_position ()
6643{
6644 if (updating_title_position)
6645 return;
6646
6647 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
6648
6649 graphics_object go = gh_mgr.get_object (get_title ());
6650
6651 if (! go.valid_object ())
6652 return;
6653
6654 text::properties& title_props
6655 = reinterpret_cast<text::properties&> (go.get_properties ());
6656
6657 octave::unwind_protect_var<bool> restore_var (updating_title_position, true);
6658
6659 if (title_props.positionmode_is ("auto"))
6660 {
6661 graphics_xform xform = get_transform ();
6662
6663 // FIXME: bbox should be stored in axes::properties
6664 Matrix bbox = get_extent (false);
6665
6666 ColumnVector p
6667 = graphics_xform::xform_vector (bbox(0) + bbox(2)/2, bbox(1) - 10,
6668 (m_x_zlim(0) + m_x_zlim(1))/2);
6669
6670 if (m_x2Dtop)
6671 {
6672 Matrix ext (1, 2, 0.0);
6673 ext = get_ticklabel_extents (get_xtick ().matrix_value (),
6674 get_xticklabel ().string_vector_value (),
6675 get_xlim ().matrix_value ());
6676 p(1) -= ext(1);
6677 }
6678
6679 p = xform.untransform (p(0), p(1), p(2), true);
6680
6681 p = convert_label_position (p, title_props, xform, bbox);
6682
6683 title_props.set_position (p.extract_n (0, 3).transpose ());
6684 title_props.set_positionmode ("auto");
6685 }
6686}
6687
6688void
6689axes::properties::update_autopos (const std::string& elem_type)
6690{
6691 if (elem_type == "xlabel")
6692 update_xlabel_position ();
6693 else if (elem_type == "ylabel")
6694 update_ylabel_position ();
6695 else if (elem_type == "zlabel")
6696 update_zlabel_position ();
6697 else if (elem_type == "title")
6698 update_title_position ();
6699 else if (elem_type == "sync")
6700 sync_positions ();
6701}
6702
6703static void
6704normalized_aspectratios (Matrix& aspectratios, const Matrix& scalefactors,
6705 double xlength, double ylength, double zlength)
6706{
6707 double xval = xlength / scalefactors(0);
6708 double yval = ylength / scalefactors(1);
6709 double zval = zlength / scalefactors(2);
6710
6711 double minval = octave::math::min (octave::math::min (xval, yval), zval);
6712
6713 aspectratios(0) = xval / minval;
6714 aspectratios(1) = yval / minval;
6715 aspectratios(2) = zval / minval;
6716}
6717
6718static void
6719max_axes_scale (double& s, Matrix& limits, const Matrix& kids,
6720 double pbfactor, double dafactor, char limit_type, bool tight)
6721{
6722 if (tight)
6723 {
6724 double minval = octave::numeric_limits<double>::Inf ();
6725 double maxval = -octave::numeric_limits<double>::Inf ();
6726 double min_pos = octave::numeric_limits<double>::Inf ();
6727 double max_neg = -octave::numeric_limits<double>::Inf ();
6728 get_children_limits (minval, maxval, min_pos, max_neg, kids, limit_type);
6729 if (octave::math::isfinite (minval) && octave::math::isfinite (maxval))
6730 {
6731 limits(0) = minval;
6732 limits(1) = maxval;
6733 s = octave::math::max (s, (maxval - minval) / (pbfactor * dafactor));
6734 }
6735 }
6736 else
6737 s = octave::math::max (s, (limits(1) - limits(0)) / (pbfactor * dafactor));
6738}
6739
6740static std::set<double> updating_aspectratios;
6741
6742void
6743axes::properties::update_aspectratios ()
6744{
6745 if (updating_aspectratios.find (get___myhandle__ ().value ())
6746 != updating_aspectratios.end ())
6747 return;
6748
6749 Matrix xlimits = get_xlim ().matrix_value ();
6750 Matrix ylimits = get_ylim ().matrix_value ();
6751 Matrix zlimits = get_zlim ().matrix_value ();
6752
6753 double dx = (xlimits(1) - xlimits(0));
6754 double dy = (ylimits(1) - ylimits(0));
6755 double dz = (zlimits(1) - zlimits(0));
6756
6757 Matrix da = get_dataaspectratio ().matrix_value ();
6758 Matrix pba = get_plotboxaspectratio ().matrix_value ();
6759
6760 if (dataaspectratiomode_is ("auto"))
6761 {
6762 if (plotboxaspectratiomode_is ("auto"))
6763 {
6764 pba = Matrix (1, 3, 1.0);
6765 m_plotboxaspectratio.set (pba, false);
6766 }
6767
6768 normalized_aspectratios (da, pba, dx, dy, dz);
6769 m_dataaspectratio.set (da, false);
6770 }
6771 else if (plotboxaspectratiomode_is ("auto"))
6772 {
6773 normalized_aspectratios (pba, da, dx, dy, dz);
6774 m_plotboxaspectratio.set (pba, false);
6775 }
6776 else
6777 {
6778 double s = -octave::numeric_limits<double>::Inf ();
6779 bool modified_limits = false;
6780 Matrix kids;
6781
6782 if (xlimmode_is ("auto") && ylimmode_is ("auto") && zlimmode_is ("auto"))
6783 {
6784 modified_limits = true;
6785 kids = get_children ();
6786 max_axes_scale (s, xlimits, kids, pba(0), da(0), 'x', true);
6787 max_axes_scale (s, ylimits, kids, pba(1), da(1), 'y', true);
6788 max_axes_scale (s, zlimits, kids, pba(2), da(2), 'z', true);
6789 }
6790 else if (xlimmode_is ("auto") && ylimmode_is ("auto"))
6791 {
6792 modified_limits = true;
6793 max_axes_scale (s, zlimits, kids, pba(2), da(2), 'z', false);
6794 }
6795 else if (ylimmode_is ("auto") && zlimmode_is ("auto"))
6796 {
6797 modified_limits = true;
6798 max_axes_scale (s, xlimits, kids, pba(0), da(0), 'x', false);
6799 }
6800 else if (zlimmode_is ("auto") && xlimmode_is ("auto"))
6801 {
6802 modified_limits = true;
6803 max_axes_scale (s, ylimits, kids, pba(1), da(1), 'y', false);
6804 }
6805
6806 if (modified_limits)
6807 {
6808 octave::unwind_protect_var<std::set<double>>
6809 restore_var (updating_aspectratios);
6810
6811 updating_aspectratios.insert (get___myhandle__ ().value ());
6812
6813 dx = pba(0) * da(0);
6814 dy = pba(1) * da(1);
6815 dz = pba(2) * da(2);
6816 if (octave::math::isinf (s))
6817 s = 1 / octave::math::min (octave::math::min (dx, dy), dz);
6818
6819 if (xlimmode_is ("auto"))
6820 {
6821 dx = s * dx;
6822 xlimits(0) = 0.5 * (xlimits(0) + xlimits(1) - dx);
6823 xlimits(1) = xlimits(0) + dx;
6824 set_xlim (xlimits);
6825 set_xlimmode ("auto");
6826 }
6827
6828 if (ylimmode_is ("auto"))
6829 {
6830 dy = s * dy;
6831 ylimits(0) = 0.5 * (ylimits(0) + ylimits(1) - dy);
6832 ylimits(1) = ylimits(0) + dy;
6833 set_ylim (ylimits);
6834 set_ylimmode ("auto");
6835 }
6836
6837 if (zlimmode_is ("auto"))
6838 {
6839 dz = s * dz;
6840 zlimits(0) = 0.5 * (zlimits(0) + zlimits(1) - dz);
6841 zlimits(1) = zlimits(0) + dz;
6842 set_zlim (zlimits);
6843 set_zlimmode ("auto");
6844 }
6845 }
6846 else
6847 {
6848 normalized_aspectratios (pba, da, dx, dy, dz);
6849 m_plotboxaspectratio.set (pba, false);
6850 }
6851 }
6852}
6853
6854void
6855axes::properties::update_label_color (handle_property label,
6856 color_property col)
6857{
6858 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
6859
6860 gh_mgr.get_object (label.handle_value ()).set ("color", col.get ());
6861}
6862
6863void
6864axes::properties::update_font (std::string prop)
6865{
6866 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
6867
6868 if (! prop.empty ())
6869 {
6870 octave_value val = get (prop);
6871 octave_value tval = val;
6872 if (prop == "fontsize")
6873 {
6874 tval = octave_value (val.double_value () *
6875 get_titlefontsizemultiplier ());
6876 val = octave_value (val.double_value () *
6877 get_labelfontsizemultiplier ());
6878 }
6879 else if (prop == "fontweight")
6880 tval = get ("titlefontweight");
6881
6882 gh_mgr.get_object (get_xlabel ()).set (prop, val);
6883 gh_mgr.get_object (get_ylabel ()).set (prop, val);
6884 gh_mgr.get_object (get_zlabel ()).set (prop, val);
6885 gh_mgr.get_object (get_title ()).set (prop, tval);
6886 }
6887
6888 double dpr = device_pixel_ratio (get___myhandle__ ());
6889
6890 octave::autolock guard (gh_mgr.graphics_lock ());
6891
6892 m_txt_renderer.set_font (get ("fontname").string_value (),
6893 get ("fontweight").string_value (),
6894 get ("fontangle").string_value (),
6895 get ("__fontsize_points__").double_value () * dpr);
6896}
6897
6898// The INTERNAL flag defines whether position or outerposition is used.
6899
6900Matrix
6901axes::properties::get_boundingbox (bool internal,
6902 const Matrix& parent_pix_size) const
6903{
6904 Matrix pos = (internal ? get_position ().matrix_value ()
6905 : get_outerposition ().matrix_value ());
6906 Matrix parent_size (parent_pix_size);
6907
6908 if (parent_size.isempty ())
6909 {
6910 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
6911
6912 graphics_object go = gh_mgr.get_object (get_parent ());
6913
6914 if (go.valid_object ())
6915 parent_size
6916 = go.get_properties ().get_boundingbox (true).extract_n (0, 2, 1, 2);
6917 else
6918 parent_size = default_figure_position ();
6919 }
6920
6921 pos = convert_position (pos, get_units (), "pixels", parent_size);
6922
6923 pos(0)--;
6924 pos(1)--;
6925 pos(1) = parent_size(1) - pos(1) - pos(3);
6926
6927 return pos;
6928}
6929
6930Matrix
6931axes::properties::get_extent (bool with_text, bool only_text_height) const
6932{
6933 graphics_xform xform = get_transform ();
6934
6935 Matrix ext (1, 4, 0.0);
6936 ext(0) = ext(1) = octave::numeric_limits<double>::Inf ();
6937 ext(2) = ext(3) = -octave::numeric_limits<double>::Inf ();
6938 for (int i = 0; i <= 1; i++)
6939 for (int j = 0; j <= 1; j++)
6940 for (int k = 0; k <= 1; k++)
6941 {
6942 ColumnVector p = xform.transform (i ? m_xPlaneN : m_xPlane,
6943 j ? m_yPlaneN : m_yPlane,
6944 k ? m_zPlaneN : m_zPlane, false);
6945 ext(0) = std::min (ext(0), p(0));
6946 ext(1) = std::min (ext(1), p(1));
6947 ext(2) = std::max (ext(2), p(0));
6948 ext(3) = std::max (ext(3), p(1));
6949 }
6950
6951 if (with_text)
6952 {
6953 for (int i = 0; i < 4; i++)
6954 {
6955 graphics_handle htext;
6956 if (i == 0)
6957 htext = get_title ();
6958 else if (i == 1)
6959 htext = get_xlabel ();
6960 else if (i == 2)
6961 htext = get_ylabel ();
6962 else if (i == 3)
6963 htext = get_zlabel ();
6964
6965 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
6966
6967 text::properties& text_props
6968 = reinterpret_cast<text::properties&>
6969 (gh_mgr.get_object (htext).get_properties ());
6970
6971 Matrix text_pos = text_props.get_data_position ();
6972 text_pos = xform.transform (text_pos(0), text_pos(1), text_pos(2));
6973 if (text_props.get_string ().isempty ())
6974 {
6975 ext(0) = std::min (ext(0), text_pos(0));
6976 ext(1) = std::min (ext(1), text_pos(1));
6977 ext(2) = std::max (ext(2), text_pos(0));
6978 ext(3) = std::max (ext(3), text_pos(1));
6979 }
6980 else
6981 {
6982 Matrix text_ext = text_props.get_extent_matrix (true);
6983
6984 // The text extent is returned in device pixels. Unscale and
6985 // work with logical pixels
6986 double dpr = device_pixel_ratio (get___myhandle__ ());
6987 if (dpr != 1.0)
6988 for (int j = 0; j < 4; j++)
6989 text_ext(j) /= dpr;
6990
6991 bool ignore_horizontal = false;
6992 bool ignore_vertical = false;
6993 if (only_text_height)
6994 {
6995 double text_rotation = text_props.get_rotation ();
6996 if (text_rotation == 0. || text_rotation == 180.)
6997 ignore_horizontal = true;
6998 else if (text_rotation == 90. || text_rotation == 270.)
6999 ignore_vertical = true;
7000 }
7001
7002 if (! ignore_horizontal)
7003 {
7004 ext(0) = std::min (ext(0), text_pos(0)+text_ext(0));
7005 ext(2) = std::max (ext(2),
7006 text_pos(0)+text_ext(0)+text_ext(2));
7007 }
7008
7009 if (! ignore_vertical)
7010 {
7011 ext(1) = std::min (ext(1),
7012 text_pos(1)-text_ext(1)-text_ext(3));
7013 ext(3) = std::max (ext(3), text_pos(1)-text_ext(1));
7014 }
7015 }
7016 }
7017 }
7018
7019 ext(2) = ext(2) - ext(0);
7020 ext(3) = ext(3) - ext(1);
7021
7022 return ext;
7023}
7024
7025static octave_value
7026convert_ticklabel_string (const octave_value& val)
7027{
7028 octave_value retval = val;
7029
7030 if (val.iscellstr ())
7031 {
7032 // Always return a column vector for Matlab compatibility
7033 if (val.columns () > 1)
7034 retval = val.reshape (dim_vector (val.numel (), 1));
7035 }
7036 else
7037 {
7038 string_vector sv;
7039 if (val.isnumeric ())
7040 {
7041 NDArray data = val.array_value ();
7042 std::ostringstream oss;
7043 oss.precision (5);
7044 for (octave_idx_type i = 0; i < val.numel (); i++)
7045 {
7046 oss.str ("");
7047 // FIXME: Code should probably call out to display routines
7048 // within Octave, rather than hack things up with C++ library.
7049 // See FIXME in calc_ticklabels().
7050 if (std::abs (data(i)) < 1.0)
7051 oss.precision (4);
7052 else
7053 oss.precision (5);
7054 oss << data(i);
7055 sv.append (oss.str ());
7056 }
7057 }
7058 else if (val.is_string () && val.rows () == 1)
7059 {
7060 std::string valstr = val.string_value ();
7061 std::istringstream iss (valstr);
7062 std::string tmpstr;
7063
7064 // Split string with delimiter '|'
7065 while (std::getline (iss, tmpstr, '|'))
7066 sv.append (tmpstr);
7067
7068 // If string ends with '|' Matlab appends a null string
7069 if (*valstr.rbegin () == '|')
7070 sv.append (std::string (""));
7071 }
7072 else
7073 return retval;
7074
7075 charMatrix chmat (sv, ' ');
7076
7077 retval = octave_value (chmat);
7078 }
7079
7080 return retval;
7081}
7082
7083void
7084axes::properties::set_xticklabel (const octave_value& val)
7085{
7086 if (m_xticklabel.set (convert_ticklabel_string (val), false))
7087 {
7088 set_xticklabelmode ("manual");
7089 m_xticklabel.run_listeners (GCB_POSTSET);
7090 mark_modified ();
7091 }
7092 else
7093 set_xticklabelmode ("manual");
7094
7095 sync_positions ();
7096}
7097
7098void
7099axes::properties::set_yticklabel (const octave_value& val)
7100{
7101 if (m_yticklabel.set (convert_ticklabel_string (val), false))
7102 {
7103 set_yticklabelmode ("manual");
7104 m_yticklabel.run_listeners (GCB_POSTSET);
7105 mark_modified ();
7106 }
7107 else
7108 set_yticklabelmode ("manual");
7109
7110 sync_positions ();
7111}
7112
7113void
7114axes::properties::set_zticklabel (const octave_value& val)
7115{
7116 if (m_zticklabel.set (convert_ticklabel_string (val), false))
7117 {
7118 set_zticklabelmode ("manual");
7119 m_zticklabel.run_listeners (GCB_POSTSET);
7120 mark_modified ();
7121 }
7122 else
7123 set_zticklabelmode ("manual");
7124
7125 sync_positions ();
7126}
7127
7128// Almost identical to convert_ticklabel_string but it only accepts
7129// cellstr or string, not numeric input.
7130static octave_value
7131convert_linestyleorder_string (const octave_value& val)
7132{
7133 octave_value retval = val;
7134
7135 if (val.iscellstr ())
7136 {
7137 // Always return a column vector for Matlab Compatibility
7138 if (val.columns () > 1)
7139 retval = val.reshape (dim_vector (val.numel (), 1));
7140 }
7141 else
7142 {
7143 string_vector sv;
7144 if (val.is_string () && val.rows () == 1)
7145 {
7146 std::string valstr = val.string_value ();
7147 std::istringstream iss (valstr);
7148 std::string tmpstr;
7149
7150 // Split string with delimiter '|'
7151 while (std::getline (iss, tmpstr, '|'))
7152 sv.append (tmpstr);
7153
7154 // If string ends with '|' Matlab appends a null string
7155 if (*valstr.rbegin () == '|')
7156 sv.append (std::string (""));
7157 }
7158 else
7159 return retval;
7160
7161 charMatrix chmat (sv, ' ');
7162
7163 retval = octave_value (chmat);
7164 }
7165
7166 return retval;
7167}
7168
7169void
7170axes::properties::set_linestyleorder (const octave_value& val)
7171{
7172 m_linestyleorder.set (convert_linestyleorder_string (val), false);
7173}
7174
7175void
7176axes::properties::set_units (const octave_value& val)
7177{
7178 caseless_str old_units = get_units ();
7179
7180 if (m_units.set (val, true))
7181 {
7182 update_units (old_units);
7183 mark_modified ();
7184 }
7185}
7186
7187void
7188axes::properties::update_units (const caseless_str& old_units)
7189{
7190 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
7191
7192 graphics_object parent_go = gh_mgr.get_object (get_parent ());
7193
7194 Matrix parent_bb
7195 = parent_go.get_properties ().get_boundingbox (true).extract_n (0, 2, 1, 2);
7196
7197 caseless_str new_units = get_units ();
7198 m_position.set (octave_value
7199 (convert_position (get_position ().matrix_value (),
7200 old_units, new_units, parent_bb)),
7201 false);
7202 m_outerposition.set (octave_value
7203 (convert_position (get_outerposition ().matrix_value (),
7204 old_units, new_units, parent_bb)),
7205 false);
7206 m_tightinset.set (octave_value
7207 (convert_position (get_tightinset ().matrix_value (),
7208 old_units, new_units, parent_bb)),
7209 false);
7210 m_looseinset.set (octave_value
7211 (convert_position (get_looseinset ().matrix_value (),
7212 old_units, new_units, parent_bb)),
7213 false);
7214}
7215
7216void
7217axes::properties::set_fontunits (const octave_value& val)
7218{
7219 caseless_str old_fontunits = get_fontunits ();
7220
7221 if (m_fontunits.set (val, true))
7222 {
7223 update_fontunits (old_fontunits);
7224 mark_modified ();
7225 }
7226}
7227
7228void
7229axes::properties::update_fontunits (const caseless_str& old_units)
7230{
7231 caseless_str new_units = get_fontunits ();
7232 double parent_height = get_boundingbox (true).elem (3);
7233 double fontsz = get_fontsize ();
7234
7235 fontsz = convert_font_size (fontsz, old_units, new_units, parent_height);
7236
7237 set_fontsize (octave_value (fontsz));
7238}
7239
7240double
7241axes::properties::get___fontsize_points__ (double box_pix_height) const
7242{
7243 double fontsz = get_fontsize ();
7244 double parent_height = box_pix_height;
7245
7246 if (fontunits_is ("normalized") && parent_height <= 0)
7247 parent_height = get_boundingbox (true).elem (3);
7248
7249 return convert_font_size (fontsz, get_fontunits (), "points", parent_height);
7250}
7251
7253graphics_xform::xform_vector (double x, double y, double z)
7254{
7255 return octave::xform_vector (x, y, z);
7256}
7257
7258Matrix
7259graphics_xform::xform_eye ()
7260{
7261 return octave::xform_matrix ();
7262}
7263
7265graphics_xform::transform (double x, double y, double z, bool use_scale) const
7266{
7267 if (use_scale)
7268 {
7269 x = m_sx.scale (x);
7270 y = m_sy.scale (y);
7271 z = m_sz.scale (z);
7272 }
7273
7274 return octave::transform (m_xform, x, y, z);
7275}
7276
7278graphics_xform::untransform (double x, double y, double z,
7279 bool use_scale) const
7280{
7281 ColumnVector v = octave::transform (m_xform_inv, x, y, z);
7282
7283 if (use_scale)
7284 {
7285 v(0) = m_sx.unscale (v(0));
7286 v(1) = m_sy.unscale (v(1));
7287 v(2) = m_sz.unscale (v(2));
7288 }
7289
7290 return v;
7291}
7292
7294axes::get_default (const caseless_str& pname) const
7295{
7296 octave_value retval = m_default_properties.lookup (pname);
7297
7298 if (retval.is_undefined ())
7299 {
7300 graphics_handle parent_h = get_parent ();
7301
7302 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
7303
7304 graphics_object parent_go = gh_mgr.get_object (parent_h);
7305
7306 retval = parent_go.get_default (pname);
7307 }
7308
7309 return retval;
7310}
7311
7312// FIXME: remove.
7313// FIXME: maybe this should go into array_property class?
7314/*
7315static void
7316check_limit_vals (double& min_val, double& max_val,
7317 double& min_pos, double& max_neg,
7318 const array_property& data)
7319{
7320 double val = data.min_val ();
7321 if (octave::math::isfinite (val) && val < min_val)
7322 min_val = val;
7323 val = data.max_val ();
7324 if (octave::math::isfinite (val) && val > max_val)
7325 max_val = val;
7326 val = data.min_pos ();
7327 if (octave::math::isfinite (val) && val > 0 && val < min_pos)
7328 min_pos = val;
7329 val = data.max_neg ();
7330 if (octave::math::isfinite (val) && val < 0 && val > max_neg)
7331 max_neg = val;
7332}
7333*/
7334
7335static void
7336check_limit_vals (double& min_val, double& max_val,
7337 double& min_pos, double& max_neg,
7338 const octave_value& data)
7339{
7340 Matrix m;
7341
7342 if (data.is_matrix_type ())
7343 m = data.matrix_value ();
7344
7345 if (m.numel () != 4)
7346 {
7347 m = Matrix (1, 4, 0.0);
7348 m(2) = octave::numeric_limits<double>::Inf ();
7349 m(3) = -octave::numeric_limits<double>::Inf ();
7350 }
7351
7352 double val;
7353
7354 val = m(0);
7355 if (octave::math::isfinite (val) && val < min_val)
7356 min_val = val;
7357
7358 val = m(1);
7359 if (octave::math::isfinite (val) && val > max_val)
7360 max_val = val;
7361
7362 val = m(2);
7363 if (octave::math::isfinite (val) && val > 0 && val < min_pos)
7364 min_pos = val;
7365
7366 val = m(3);
7367 if (octave::math::isfinite (val) && val < 0 && val > max_neg)
7368 max_neg = val;
7369}
7370
7371// magform(x) Returns (a, b),
7372// where x = a * 10^b, abs (a) >= 1., and b is integer.
7373
7374static void
7375magform (double x, double& a, int& b)
7376{
7377 if (x == 0)
7378 {
7379 a = 0;
7380 b = 0;
7381 }
7382 else
7383 {
7384 b = static_cast<int> (std::floor (std::log10 (std::abs (x))));
7385 a = x / std::pow (10.0, b);
7386 }
7387}
7388
7389void
7390axes::properties::update_outerposition ()
7391{
7392 set_positionconstraint ("outerposition");
7393 caseless_str old_units = get_units ();
7394 set_units ("normalized");
7395
7396 Matrix outerbox = m_outerposition.get ().matrix_value ();
7397
7398 double outer_left = outerbox(0);
7399 double outer_bottom = outerbox(1);
7400 double outer_width = outerbox(2);
7401 double outer_height = outerbox(3);
7402
7403 double outer_right = outer_width + outer_left;
7404 double outer_top = outer_height + outer_bottom;
7405
7406 Matrix linset = m_looseinset.get ().matrix_value ();
7407 Matrix tinset = m_tightinset.get ().matrix_value ();
7408
7409 double left_margin = std::max (linset(0), tinset(0));
7410 double bottom_margin = std::max (linset(1), tinset(1));
7411 double right_margin = std::max (linset(2), tinset(2));
7412 double top_margin = std::max (linset(3), tinset(3));
7413
7414 double inner_left = outer_left;
7415 double inner_right = outer_right;
7416
7417 if ((left_margin + right_margin) < outer_width)
7418 {
7419 inner_left += left_margin;
7420 inner_right -= right_margin;
7421 }
7422
7423 double inner_bottom = outer_bottom;
7424 double inner_top = outer_top;
7425
7426 if ((bottom_margin + top_margin) < outer_height)
7427 {
7428 inner_bottom += bottom_margin;
7429 inner_top -= top_margin;
7430 }
7431
7432 double inner_width = inner_right - inner_left;
7433 double inner_height = inner_top - inner_bottom;
7434
7435 Matrix innerbox (1, 4);
7436
7437 innerbox(0) = inner_left;
7438 innerbox(1) = inner_bottom;
7439 innerbox(2) = inner_width;
7440 innerbox(3) = inner_height;
7441
7442 m_position = innerbox;
7443
7444 set_units (old_units);
7445 update_transform ();
7446}
7447
7448void
7449axes::properties::update_position ()
7450{
7451 set_positionconstraint ("innerposition");
7452 caseless_str old_units = get_units ();
7453 set_units ("normalized");
7454
7455 Matrix innerbox = m_position.get ().matrix_value ();
7456
7457 double inner_left = innerbox(0);
7458 double inner_bottom = innerbox(1);
7459 double inner_width = innerbox(2);
7460 double inner_height = innerbox(3);
7461
7462 double inner_right = inner_width + inner_left;
7463 double inner_top = inner_height + inner_bottom;
7464
7465 Matrix linset = m_looseinset.get ().matrix_value ();
7466 Matrix tinset = m_tightinset.get ().matrix_value ();
7467
7468 double left_margin = std::max (linset(0), tinset(0));
7469 double bottom_margin = std::max (linset(1), tinset(1));
7470 double right_margin = std::max (linset(2), tinset(2));
7471 double top_margin = std::max (linset(3), tinset(3));
7472
7473 // FIXME: do we need to place limits on any of these?
7474
7475 double outer_left = inner_left - left_margin;
7476 double outer_bottom = inner_bottom - bottom_margin;
7477 double outer_right = inner_right + right_margin;
7478 double outer_top = inner_top + top_margin;
7479
7480 double outer_width = outer_right - outer_left;
7481 double outer_height = outer_top - outer_bottom;
7482
7483 Matrix outerbox (1, 4);
7484
7485 outerbox(0) = outer_left;
7486 outerbox(1) = outer_bottom;
7487 outerbox(2) = outer_width;
7488 outerbox(3) = outer_height;
7489
7490 m_outerposition = outerbox;
7491
7492 set_units (old_units);
7493 update_transform ();
7494}
7495
7496void
7497axes::properties::update_looseinset ()
7498{
7499 caseless_str old_units = get_units ();
7500 set_units ("normalized");
7501
7502 Matrix linset = m_looseinset.get ().matrix_value ();
7503 Matrix tinset = m_tightinset.get ().matrix_value ();
7504
7505 double left_margin = std::max (linset(0), tinset(0));
7506 double bottom_margin = std::max (linset(1), tinset(1));
7507 double right_margin = std::max (linset(2), tinset(2));
7508 double top_margin = std::max (linset(3), tinset(3));
7509
7510 if (m_positionconstraint.is ("innerposition"))
7511 {
7512 Matrix innerbox = m_position.get ().matrix_value ();
7513
7514 double inner_left = innerbox(0);
7515 double inner_bottom = innerbox(1);
7516 double inner_width = innerbox(2);
7517 double inner_height = innerbox(3);
7518
7519 double inner_right = inner_width + inner_left;
7520 double inner_top = inner_height + inner_bottom;
7521
7522 // FIXME: do we need to place limits on any of these?
7523
7524 double outer_left = inner_left - left_margin;
7525 double outer_bottom = inner_bottom - bottom_margin;
7526 double outer_right = inner_right + right_margin;
7527 double outer_top = inner_top + top_margin;
7528
7529 double outer_width = outer_right - outer_left;
7530 double outer_height = outer_top - outer_bottom;
7531
7532 Matrix outerbox (1, 4);
7533
7534 outerbox(0) = outer_left;
7535 outerbox(1) = outer_bottom;
7536 outerbox(2) = outer_width;
7537 outerbox(3) = outer_height;
7538
7539 m_outerposition = outerbox;
7540 }
7541 else
7542 {
7543 Matrix outerbox = m_outerposition.get ().matrix_value ();
7544
7545 double outer_left = outerbox(0);
7546 double outer_bottom = outerbox(1);
7547 double outer_width = outerbox(2);
7548 double outer_height = outerbox(3);
7549
7550 double outer_right = outer_width + outer_left;
7551 double outer_top = outer_height + outer_bottom;
7552
7553 double inner_left = outer_left;
7554 double inner_right = outer_right;
7555
7556 if ((left_margin + right_margin) < outer_width)
7557 {
7558 inner_left += left_margin;
7559 inner_right -= right_margin;
7560 }
7561
7562 double inner_bottom = outer_bottom;
7563 double inner_top = outer_top;
7564
7565 if ((bottom_margin + top_margin) < outer_height)
7566 {
7567 inner_bottom += bottom_margin;
7568 inner_top -= top_margin;
7569 }
7570
7571 double inner_width = inner_right - inner_left;
7572 double inner_height = inner_top - inner_bottom;
7573
7574 Matrix innerbox (1, 4);
7575
7576 innerbox(0) = inner_left;
7577 innerbox(1) = inner_bottom;
7578 innerbox(2) = inner_width;
7579 innerbox(3) = inner_height;
7580
7581 m_position = innerbox;
7582 }
7583
7584 set_units (old_units);
7585 update_transform ();
7586}
7587
7588// A translation from Tom Holoryd's python code at
7589// http://kurage.nimh.nih.gov/tomh/tics.py
7590// FIXME: add log ticks
7591
7592double
7593axes::properties::calc_tick_sep (double lo, double hi)
7594{
7595 int ticint = 5;
7596
7597 // Reference: Lewart, C. R., "Algorithms SCALE1, SCALE2, and SCALE3 for
7598 // Determination of Scales on Computer Generated Plots", Communications of
7599 // the ACM, 10 (1973), 639-640.
7600 // Also cited as ACM Algorithm 463.
7601
7602 double a;
7603 int b, x;
7604
7605 magform ((hi - lo) / ticint, a, b);
7606
7607 static const double SQRT_2 = sqrt (2.0);
7608 static const double SQRT_10 = sqrt (10.0);
7609 static const double SQRT_50 = sqrt (50.0);
7610
7611 if (a < SQRT_2)
7612 x = 1;
7613 else if (a < SQRT_10)
7614 x = 2;
7615 else if (a < SQRT_50)
7616 x = 5;
7617 else
7618 x = 10;
7619
7620 return x * std::pow (10.0, b);
7621}
7622
7623// Attempt to make "nice" limits from the actual max and min of the data.
7624// For log plots, we will also use the smallest strictly positive value.
7625
7626Matrix
7627axes::properties::get_axis_limits (double xmin, double xmax,
7628 double min_pos, double max_neg,
7629 const bool logscale,
7630 const std::string& method)
7631{
7632 Matrix retval;
7633
7634 double min_val = xmin;
7635 double max_val = xmax;
7636
7637 if (octave::math::isinf (min_val) && min_val > 0
7638 && octave::math::isinf (max_val) && max_val < 0)
7639 {
7640 retval = default_lim (logscale);
7641 return retval;
7642 }
7643 else if (! (octave::math::isinf (min_val) || octave::math::isinf (max_val)))
7644 {
7645 if (logscale)
7646 {
7647 if (octave::math::isinf (min_pos) && octave::math::isinf (max_neg))
7648 {
7649 // FIXME: max_neg is needed for "loglog ([0 -Inf])"
7650 // This is the *only* place where max_neg is needed.
7651 // Is there another way?
7652 retval = default_lim (logscale);
7653 return retval;
7654 }
7655 if (min_val <= 0)
7656 {
7657 if (max_val > 0)
7658 {
7659 warning_with_id ("Octave:negative-data-log-axis",
7660 "axis: omitting non-positive data in log plot");
7661 min_val = min_pos;
7662 }
7663 else if (max_val == 0)
7664 max_val = max_neg;
7665 }
7666 // FIXME: maybe this test should also be relative?
7667 if (std::abs (min_val - max_val)
7668 < sqrt (std::numeric_limits<double>::epsilon ()))
7669 {
7670 // Widen range when too small
7671 if (min_val >= 0)
7672 {
7673 min_val *= 0.9;
7674 max_val *= 1.1;
7675 }
7676 else
7677 {
7678 min_val *= 1.1;
7679 max_val *= 0.9;
7680 }
7681 }
7682
7683 if (method == "tickaligned")
7684 {
7685 if (min_val > 0)
7686 {
7687 // Log plots with all positive data
7688 min_val = std::pow (10, std::floor (log10 (min_val)));
7689 max_val = std::pow (10, std::ceil (log10 (max_val)));
7690 }
7691 else
7692 {
7693 // Log plots with all negative data
7694 min_val = -std::pow (10, std::ceil (log10 (-min_val)));
7695 max_val = -std::pow (10, std::floor (log10 (-max_val)));
7696 }
7697 }
7698 else if (method == "padded")
7699 {
7700 if (min_val > 0)
7701 {
7702 // Log plots with all positive data
7703 double pad = (log10 (max_val) - log10 (min_val)) * 0.07;
7704 min_val = std::pow (10, log10 (min_val) - pad);
7705 max_val = std::pow (10, log10 (max_val) + pad);
7706 }
7707 else
7708 {
7709 // Log plots with all negative data
7710 double pad = (log10 (-min_val) - log10 (-max_val)) * 0.07;
7711 min_val = -std::pow (10, log10 (-min_val) + pad);
7712 max_val = -std::pow (10, log10 (-max_val) - pad);
7713 }
7714 }
7715 }
7716 else
7717 {
7718 if (min_val == 0 && max_val == 0)
7719 {
7720 min_val = -1;
7721 max_val = 1;
7722 }
7723 // FIXME: maybe this test should also be relative?
7724 else if (std::abs (min_val - max_val)
7725 < sqrt (std::numeric_limits<double>::epsilon ()))
7726 {
7727 min_val -= 0.1 * std::abs (min_val);
7728 max_val += 0.1 * std::abs (max_val);
7729 }
7730
7731 if (method == "tickaligned")
7732 {
7733 double tick_sep = calc_tick_sep (min_val, max_val);
7734 double min_tick = std::floor (min_val / tick_sep);
7735 double max_tick = std::ceil (max_val / tick_sep);
7736 // Prevent round-off from cropping ticks
7737 min_val = std::min (min_val, tick_sep * min_tick);
7738 max_val = std::max (max_val, tick_sep * max_tick);
7739 }
7740 else if (method == "padded")
7741 {
7742 double pad = 0.07 * (max_val - min_val);
7743 min_val -= pad;
7744 max_val += pad;
7745 }
7746 }
7747 }
7748
7749 retval.resize (1, 2);
7750
7751 retval(0) = min_val;
7752 retval(1) = max_val;
7753
7754 return retval;
7755}
7756
7757void
7758axes::properties::check_axis_limits (Matrix& limits, const Matrix kids,
7759 const bool logscale, char& update_type)
7760{
7761 double min_val = octave::numeric_limits<double>::Inf ();
7762 double max_val = -octave::numeric_limits<double>::Inf ();
7763 double min_pos = octave::numeric_limits<double>::Inf ();
7764 double max_neg = -octave::numeric_limits<double>::Inf ();
7765 double eps = std::numeric_limits<double>::epsilon ();
7766 bool do_update = false;
7767 bool have_children_limits = false;
7768
7769 // check whether we need to get children limits
7770 if (! octave::math::isfinite (limits(0))
7771 || ! octave::math::isfinite (limits(1)))
7772 {
7773 get_children_limits (min_val, max_val, min_pos, max_neg, kids,
7774 update_type);
7775 have_children_limits = true;
7776 }
7777 if (! octave::math::isfinite (limits(0)))
7778 {
7779 limits(0) = min_val;
7780 do_update = true;
7781 }
7782 if (! octave::math::isfinite (limits(1)))
7783 {
7784 limits(1) = max_val;
7785 do_update = true;
7786 }
7787 if (limits(0) == 0 && limits(1) == 0)
7788 {
7789 limits = default_lim (logscale);
7790 do_update = true;
7791 }
7792 // FIXME: maybe this test should also be relative?
7793 else if (! logscale && (std::abs (limits(0) - limits(1)) < sqrt (eps)))
7794 {
7795 limits(0) -= 0.1 * std::abs (limits(0));
7796 limits(1) += 0.1 * std::abs (limits(1));
7797 do_update = true;
7798 }
7799 else if (logscale
7800 && (std::abs (std::log10 (limits(0) / limits(1))) < sqrt (eps)))
7801 {
7802 limits(0) = (limits(0) < 0 ? 10.0 * limits(0) : 0.1 * limits(0));
7803 limits(1) = (limits(1) < 0 ? 0.1 * limits(1) : 10.0 * limits(1));
7804 do_update = true;
7805 }
7806
7807 if (logscale && limits(0)*limits(1) <= 0)
7808 {
7809 if (! have_children_limits)
7810 get_children_limits (min_val, max_val, min_pos, max_neg, kids,
7811 update_type);
7812
7813 if (limits(1) > 0)
7814 {
7815 warning_with_id ("Octave:axis-non-positive-log-limits",
7816 "Non-positive limit for logarithmic axis ignored\n");
7817 if (octave::math::isfinite (min_pos))
7818 limits(0) = min_pos;
7819 else
7820 limits(0) = 0.1 * limits(1);
7821 }
7822 else
7823 {
7824 warning_with_id ("Octave:axis-non-negative-log-limits",
7825 "Non-negative limit for logarithmic axis ignored\n");
7826 if (octave::math::isfinite (max_neg))
7827 limits(1) = max_neg;
7828 else
7829 limits(1) = 0.1 * limits(0);
7830 }
7831 // FIXME: maybe this test should also be relative?
7832 if (std::abs (limits(0) - limits(1)) < sqrt (eps))
7833 {
7834 // Widen range when too small
7835 if (limits(0) > 0)
7836 {
7837 limits(0) *= 0.9;
7838 limits(1) *= 1.1;
7839 }
7840 else
7841 {
7842 limits(0) *= 1.1;
7843 limits(1) *= 0.9;
7844 }
7845 }
7846 do_update = true;
7847 }
7848
7849 if (! do_update)
7850 update_type = 0;
7851
7852}
7853
7854/*
7855## Test validation of auto and manual axis limits
7856%!test
7857%! hf = figure ("visible", "off");
7858%! unwind_protect
7859%! hax = axes ("parent", hf);
7860%! plot (0, pi);
7861%! assert (get (hax, "xlim"), [-1, 1]);
7862%! assert (get (hax, "xlimmode"), "auto");
7863%! assert (get (hax, "ylim"), [2.8, 3.5], 2*eps);
7864%! assert (get (hax, "ylimmode"), "auto");
7865%! set (hax, "xlim", [1, 1], "ylim", [0, 0]);
7866%! assert (get (hax, "xlim"), [0.9, 1.1]);
7867%! assert (get (hax, "xlimmode"), "manual");
7868%! assert (get (hax, "ylim"), [0, 1]);
7869%! assert (get (hax, "ylimmode"), "manual");
7870%! set (hax, "xlim", [-Inf, Inf], "ylim", [-Inf, Inf]);
7871%! ## Matlab does not update the properties
7872%! assert (get (hax, "xlim"), [0, 1]);
7873%! assert (get (hax, "ylim"), [0.9, 1.1]*pi, 2*eps);
7874%! unwind_protect_cleanup
7875%! delete (hf);
7876%! end_unwind_protect
7877
7878%!test
7879%! hf = figure ("visible", "off");
7880%! unwind_protect
7881%! hax = axes ("parent", hf);
7882%! plot ([0, exp(1)], [-pi, 3]);
7883%! assert (get (hax, "xlim"), [0, 3]);
7884%! assert (get (hax, "xlimmode"), "auto");
7885%! assert (get (hax, "ylim"), [-4, 3]);
7886%! assert (get (hax, "ylimmode"), "auto");
7887%! set (hax, "xlim", [-Inf, Inf], "ylim", [-Inf, Inf]);
7888%! ## Matlab does not update the properties but uses tight limits on screen
7889%! assert (get (hax, "xlim"), [0, exp(1)]);
7890%! assert (get (hax, "xlimmode"), "manual");
7891%! assert (get (hax, "ylim"), [-pi, 3]);
7892%! assert (get (hax, "ylimmode"), "manual");
7893%! unwind_protect_cleanup
7894%! delete (hf);
7895%! end_unwind_protect
7896
7897%!test
7898%! hf = figure ("visible", "off");
7899%! unwind_protect
7900%! hax = axes ("parent", hf);
7901%! loglog (0, pi);
7902%! assert (get (hax, "xlim"), [0.1, 1.0]);
7903%! assert (get (hax, "xlimmode"), "auto");
7904%! assert (get (hax, "ylim"), [1, 10]);
7905%! assert (get (hax, "ylimmode"), "auto");
7906%! set (hax, "xlim", [1, 1], "ylim", [0, 0]);
7907%! assert (get (hax, "xlim"), [0.1, 10]);
7908%! assert (get (hax, "xlimmode"), "manual");
7909%! assert (get (hax, "ylim"), [0.1, 1.0]);
7910%! assert (get (hax, "ylimmode"), "manual");
7911%! set (hax, "xlim", [-Inf, Inf], "ylim", [-Inf, Inf]);
7912%! ## Matlab does not update the properties
7913%! assert (get (hax, "xlim"), [0.1, 1.0]);
7914%! assert (get (hax, "ylim"), [0.1, 10]*pi);
7915%! unwind_protect_cleanup
7916%! delete (hf);
7917%! end_unwind_protect
7918
7919%!test
7920%! hf = figure ("visible", "off");
7921%! unwind_protect
7922%! hax = axes ("parent", hf);
7923%! loglog ([0 -1], [0 1]);
7924%! assert (get (hax, "xlim"), [-10, -0.1]);
7925%! assert (get (hax, "xlimmode"), "auto");
7926%! assert (get (hax, "ylim"), [0.1, 10]);
7927%! assert (get (hax, "ylimmode"), "auto");
7928%! set (hax, "xlim", [-Inf, Inf], "ylim", [-Inf, Inf]);
7929%! ## Matlab does not update the properties
7930%! assert (get (hax, "xlim"), [-1.1, -0.9]);
7931%! assert (get (hax, "ylim"), [0.9, 1.1]);
7932%! unwind_protect_cleanup
7933%! delete (hf);
7934%! end_unwind_protect
7935
7936%!test
7937%! hf = figure ("visible", "off");
7938%! unwind_protect
7939%! hax = axes ("parent", hf);
7940%! loglog ([1 -1], [1 pi]);
7941%! assert (get (hax, "xlim"), [0.1, 10]);
7942%! assert (get (hax, "xlimmode"), "auto");
7943%! assert (get (hax, "ylim"), [1, 10]);
7944%! assert (get (hax, "ylimmode"), "auto");
7945%! set (hax, "xlim", [-Inf, Inf], "ylim", [-Inf, Inf]);
7946%! ## Matlab does not update the properties but uses tight limits on screen
7947%! assert (get (hax, "xlim"), [0.9, 1.1]);
7948%! assert (get (hax, "ylim"), [1, pi]);
7949%! unwind_protect_cleanup
7950%! delete (hf);
7951%! end_unwind_protect
7952
7953## Check that graphics objects with hidden handle visibility are included in
7954## axis limit calculation.
7955%!test <*63095>
7956%! hf = figure ("visible", "off");
7957%! unwind_protect
7958%! hax = axes ("parent", hf);
7959%! plot (hax, [0, 1]);
7960%! assert (get (hax, "ylim"), [0, 1]);
7961%! hold (hax, "on");
7962%! plot (hax, [2, 0], "handlevisibility", "off");
7963%! assert (get (hax, "ylim"), [0, 2]);
7964%! unwind_protect_cleanup
7965%! delete (hf);
7966%! end_unwind_protect
7967
7968## Check automatically determined axes ticks with "tickaligned" (default) and
7969## "tight" xlimitmethod.
7970%!test <*63624>
7971%! hf = figure ("visible", "off");
7972%! unwind_protect
7973%! hax = axes ("parent", hf);
7974%! plot (hax, [1, 201], [0, 1]);
7975%! assert (get (hax, "xtick"), 0:50:250);
7976%! axis (hax, "tight");
7977%! assert (get (hax, "xtick"), 50:50:200);
7978%! unwind_protect_cleanup
7979%! delete (hf);
7980%! end_unwind_protect
7981*/
7982
7983void
7984axes::properties::calc_ticks_and_lims (array_property& lims,
7985 array_property& ticks,
7986 array_property& mticks,
7987 bool limmode_is_auto,
7988 bool tickmode_is_auto,
7989 bool is_logscale,
7990 bool method_is_padded,
7991 bool method_is_tight)
7992{
7993 if (lims.get ().isempty ())
7994 return;
7995
7996 double lo = (lims.get ().matrix_value ())(0);
7997 double hi = (lims.get ().matrix_value ())(1);
7998
7999 double lo_lim = lo;
8000 double hi_lim = hi;
8001 bool is_negative = lo < 0 && hi < 0;
8002
8003 // FIXME: should this be checked for somewhere else? (i.e., set{x,y,z}lim)
8004 if (hi < lo)
8005 std::swap (hi, lo);
8006
8007 if (is_logscale)
8008 {
8009 if (is_negative)
8010 {
8011 double tmp = hi;
8012 hi = std::log10 (-lo);
8013 lo = std::log10 (-tmp);
8014 }
8015 else
8016 {
8017 hi = std::log10 (hi);
8018 lo = std::log10 (lo);
8019 }
8020 }
8021
8022 Matrix tmp_ticks;
8023 if (tickmode_is_auto)
8024 {
8025 double tick_sep;
8026
8027 if (is_logscale)
8028 {
8029 if (! (octave::math::isinf (hi) || octave::math::isinf (lo)))
8030 tick_sep = 1; // Tick is every order of magnitude (bug #39449)
8031 else
8032 tick_sep = 0;
8033 }
8034 else
8035 tick_sep = calc_tick_sep (lo, hi);
8036
8037 double i1 = std::floor (lo / tick_sep);
8038 double i2 = std::ceil (hi / tick_sep);
8039
8040 if (limmode_is_auto)
8041 {
8042 Matrix tmp_lims (1, 2);
8043
8044 if (! method_is_padded && ! method_is_tight)
8045 {
8046 // Adjust limits to include min and max ticks
8047 tmp_lims(0) = std::min (tick_sep * i1, lo);
8048 tmp_lims(1) = std::max (tick_sep * i2, hi);
8049 }
8050 else
8051 {
8052 tmp_lims(0) = lo;
8053 tmp_lims(1) = hi;
8054
8055 // adjust min and max ticks to be within limits
8056 if (i1*tick_sep < lo)
8057 i1++;
8058 if (i2*tick_sep > hi && i2 > i1)
8059 i2--;
8060 }
8061
8062 if (is_logscale)
8063 {
8064 tmp_lims(0) = std::pow (10., tmp_lims(0));
8065 tmp_lims(1) = std::pow (10., tmp_lims(1));
8066
8067 if (tmp_lims(0) <= 0)
8068 tmp_lims(0) = std::pow (10., lo);
8069
8070 if (is_negative)
8071 {
8072 double tmp = tmp_lims(0);
8073 tmp_lims(0) = -tmp_lims(1);
8074 tmp_lims(1) = -tmp;
8075 }
8076 }
8077
8078 lims = tmp_lims;
8079 }
8080 else
8081 {
8082 // adjust min and max ticks to be within limits
8083 if (i1*tick_sep < lo)
8084 i1++;
8085 if (i2*tick_sep > hi && i2 > i1)
8086 i2--;
8087 }
8088
8089 tmp_ticks = Matrix (1, i2-i1+1);
8090 for (int i = 0; i <= static_cast<int> (i2-i1); i++)
8091 {
8092 tmp_ticks(i) = tick_sep * (i+i1);
8093 if (is_logscale)
8094 tmp_ticks(i) = std::pow (10., tmp_ticks(i));
8095 }
8096 if (is_logscale && is_negative)
8097 {
8098 Matrix rev_ticks (1, i2-i1+1);
8099 rev_ticks = -tmp_ticks;
8100 for (int i = 0; i <= static_cast<int> (i2-i1); i++)
8101 tmp_ticks(i) = rev_ticks(i2-i1-i);
8102 }
8103
8104 ticks = tmp_ticks;
8105 }
8106 else
8107 tmp_ticks = ticks.get ().matrix_value ();
8108
8109 octave_idx_type n_ticks = tmp_ticks.numel ();
8110 if (n_ticks < 2)
8111 return;
8112
8113 // minor ticks between, above, and below min and max ticks
8114 const int MAX_MINOR_TICKS = 1000;
8115 int n = (is_logscale ? 8 : 4);
8116 double mult_below = (is_logscale ? tmp_ticks(1) / tmp_ticks(0) : 1);
8117 double mult_above = (is_logscale ? tmp_ticks(n_ticks-1) / tmp_ticks(n_ticks-2)
8118 : 1);
8119
8120 double d_below = (tmp_ticks(1) - tmp_ticks(0)) / mult_below / (n+1);
8121 int n_below = static_cast<int> (std::floor ((tmp_ticks(0)-lo_lim) / d_below));
8122 if (n_below < 0)
8123 n_below = 0;
8124 else if (n_below > MAX_MINOR_TICKS)
8125 n_below = MAX_MINOR_TICKS;
8126
8127 int n_between = n * (n_ticks - 1);
8128 double d_above = (tmp_ticks(n_ticks-1) - tmp_ticks(n_ticks-2)) * mult_above
8129 / (n+1);
8130 int n_above = static_cast<int> (std::floor ((hi_lim-tmp_ticks(n_ticks-1))
8131 / d_above));
8132 if (n_above < 0)
8133 n_above = 0;
8134 else if (n_above > MAX_MINOR_TICKS)
8135 n_above = MAX_MINOR_TICKS;
8136
8137 Matrix tmp_mticks (1, n_below + n_between + n_above);
8138 for (int i = 0; i < n_below; i++)
8139 tmp_mticks(i) = tmp_ticks(0) - (n_below-i) * d_below;
8140 for (int i = 0; i < n_ticks-1; i++)
8141 {
8142 double d = (tmp_ticks(i+1) - tmp_ticks(i)) / (n + 1);
8143 for (int j = 0; j < n; j++)
8144 tmp_mticks(n_below+n*i+j) = tmp_ticks(i) + d * (j+1);
8145 }
8146 for (int i = 0; i < n_above; i++)
8147 tmp_mticks(n_below+n_between+i) = tmp_ticks(n_ticks-1) + (i + 1) * d_above;
8148
8149 mticks = tmp_mticks;
8150}
8151
8152/*
8153%!test <*45356>
8154%! hf = figure ("visible", "off");
8155%! unwind_protect
8156%! plot (1:10);
8157%! xlim ([4.75, 8.5]);
8158%! tics = get (gca, "xtick");
8159%! assert (tics, [5 6 7 8]);
8160%! unwind_protect_cleanup
8161%! close (hf);
8162%! end_unwind_protect
8163*/
8164
8165void
8166axes::properties::calc_ticklabels (const array_property& ticks,
8167 any_property& labels, bool logscale,
8168 const bool is_origin,
8169 const int other_axislocation,
8170 const array_property& axis_lims)
8171{
8172 Matrix values = ticks.get ().matrix_value ();
8173 Matrix lims = axis_lims.get ().matrix_value ();
8174 Cell c (dim_vector (values.numel (), 1)); // column vector for ML compat.
8175 std::ostringstream os;
8176
8177 // omit tick labels depending on location of other axis
8178 ColumnVector omit_ticks (3, octave::numeric_limits<double>::NaN ());
8179 if (get_is2D () && is_origin)
8180 {
8181 if (other_axislocation == 0)
8182 {
8183 omit_ticks(0) = octave::math::max (octave::math::min (0., lims(1)),
8184 lims(0));
8185 }
8186 else if (other_axislocation == 1)
8187 omit_ticks(0) = lims(1);
8188 else if (other_axislocation == -1)
8189 omit_ticks(0) = lims(0);
8190 if (is_box ())
8191 {
8192 omit_ticks(1) = lims(0);
8193 omit_ticks(2) = lims(1);
8194 }
8195 }
8196
8197 if (logscale)
8198 {
8199 double significand;
8200 double exponent;
8201 bool is_2digit_exp = false;
8202
8203 for (int i = 0; i < values.numel (); i++)
8204 {
8205 double exp = std::abs (std::log10 (values(i)));
8206 if (exp >= 10.0)
8207 {
8208 is_2digit_exp = true;
8209 break;
8210 }
8211 }
8212
8213 for (int i = 0; i < values.numel (); i++)
8214 {
8215 bool omit_tick = false;
8216 for (int i_omit = 0; i_omit < omit_ticks.numel (); i_omit++)
8217 if (values(i) == omit_ticks(i_omit))
8218 omit_tick = true;
8219 if (omit_tick)
8220 {
8221 c(i) = "";
8222 continue;
8223 }
8224
8225 if (values(i) < 0.0)
8226 exponent = std::floor (std::log10 (-values(i)));
8227 else
8228 exponent = std::floor (std::log10 (values(i)));
8229 significand = values(i) * std::pow (10.0, -exponent);
8230
8231 os.precision (5);
8232 os.str ("");
8233 if ((std::abs (significand) - 1) >
8234 10*std::numeric_limits<double>::epsilon())
8235 os << significand << 'x';
8236 else if (significand < 0)
8237 os << '-';
8238
8239 os << "10^{";
8240
8241 if (exponent < 0.0)
8242 {
8243 os << '-';
8244 exponent = -exponent;
8245 }
8246 if (exponent < 10.0 && is_2digit_exp)
8247 os << '0';
8248 os << exponent << '}';
8249
8250 if (m_ticklabelinterpreter.is ("latex"))
8251 c(i) = "$" + os.str () + "$";
8252 else
8253 c(i) = os.str ();
8254 }
8255 }
8256 else
8257 {
8258 for (int i = 0; i < values.numel (); i++)
8259 {
8260 bool omit_tick = false;
8261 for (int i_omit = 0; i_omit < omit_ticks.numel (); i_omit++)
8262 if (values(i) == omit_ticks(i_omit))
8263 omit_tick = true;
8264 if (omit_tick)
8265 c(i) = "";
8266 else
8267 {
8268 os.str ("");
8269 // FIXME: Code should probably call out to display routines
8270 // within Octave, rather than hack things up with C++ library.
8271 // In particular, this fails for values much less than 1 where
8272 // 4 significant digits will be preceded by zeros making the
8273 // overall field length large. For example, pi/1000.
8274 if (std::abs (values(i)) < 1.0)
8275 os.precision (4);
8276 else
8277 os.precision (5);
8278 os << values(i);
8279 c(i) = os.str ();
8280 }
8281 }
8282 }
8283
8284 labels = c;
8285}
8286
8287Matrix
8288axes::properties::get_ticklabel_extents (const Matrix& ticks,
8289 const string_vector& ticklabels,
8290 const Matrix& limits)
8291{
8292 Matrix ext (1, 2, 0.0);
8293 double wmax, hmax;
8294 double dpr = device_pixel_ratio (get___myhandle__ ());
8295 wmax = hmax = 0.0;
8296 int n = std::min (ticklabels.numel (), ticks.numel ());
8297 for (int i = 0; i < n; i++)
8298 {
8299 double val = ticks(i);
8300 if (limits(0) <= val && val <= limits(1))
8301 {
8302 std::string label (ticklabels(i));
8303 label.erase (0, label.find_first_not_of (' '));
8304 label = label.substr (0, label.find_last_not_of (' ')+1);
8305
8306 if (m_txt_renderer.ok ())
8307 {
8308 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
8309
8310 octave::autolock guard (gh_mgr.graphics_lock ());
8311
8312 ext = m_txt_renderer.get_extent (label, 0.0,
8313 get_ticklabelinterpreter ());
8314
8315 wmax = std::max (wmax, ext(0) / dpr);
8316 hmax = std::max (hmax, ext(1) / dpr);
8317 }
8318 else
8319 {
8320 // FIXME: find a better approximation
8321 double fsize = get ("fontsize").double_value ();
8322 int len = label.length ();
8323
8324 wmax = std::max (wmax, 0.5*fsize*len);
8325 hmax = fsize;
8326 }
8327 }
8328 }
8329
8330 ext(0) = wmax;
8331 ext(1) = hmax;
8332 return ext;
8333}
8334
8335void
8336get_children_limits (double& min_val, double& max_val,
8337 double& min_pos, double& max_neg,
8338 const Matrix& kids, char limit_type)
8339{
8340 octave_idx_type n = kids.numel ();
8341
8342 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
8343
8344 switch (limit_type)
8345 {
8346 case 'x':
8347 for (octave_idx_type i = 0; i < n; i++)
8348 {
8349 graphics_object go = gh_mgr.get_object (kids(i));
8350
8351 if (go.is_xliminclude ())
8352 {
8353 octave_value lim = go.get_xlim ();
8354
8355 check_limit_vals (min_val, max_val, min_pos, max_neg, lim);
8356 }
8357 }
8358 break;
8359
8360 case 'y':
8361 for (octave_idx_type i = 0; i < n; i++)
8362 {
8363 graphics_object go = gh_mgr.get_object (kids(i));
8364
8365 if (go.is_yliminclude ())
8366 {
8367 octave_value lim = go.get_ylim ();
8368
8369 check_limit_vals (min_val, max_val, min_pos, max_neg, lim);
8370 }
8371 }
8372 break;
8373
8374 case 'z':
8375 for (octave_idx_type i = 0; i < n; i++)
8376 {
8377 graphics_object go = gh_mgr.get_object (kids(i));
8378
8379 if (go.is_zliminclude ())
8380 {
8381 octave_value lim = go.get_zlim ();
8382
8383 check_limit_vals (min_val, max_val, min_pos, max_neg, lim);
8384 }
8385 }
8386 break;
8387
8388 case 'c':
8389 for (octave_idx_type i = 0; i < n; i++)
8390 {
8391 graphics_object go = gh_mgr.get_object (kids(i));
8392
8393 if (go.is_climinclude ())
8394 {
8395 octave_value lim = go.get_clim ();
8396
8397 check_limit_vals (min_val, max_val, min_pos, max_neg, lim);
8398 }
8399 }
8400 break;
8401
8402 case 'a':
8403 for (octave_idx_type i = 0; i < n; i++)
8404 {
8405 graphics_object go = gh_mgr.get_object (kids(i));
8406
8407 if (go.is_aliminclude ())
8408 {
8409 octave_value lim = go.get_alim ();
8410
8411 check_limit_vals (min_val, max_val, min_pos, max_neg, lim);
8412 }
8413 }
8414 break;
8415
8416 default:
8417 break;
8418 }
8419}
8420
8421static std::set<double> updating_axis_limits;
8422
8423void
8424axes::update_axis_limits (const std::string& axis_type,
8425 const graphics_handle& h)
8426{
8427 if (updating_axis_limits.find (get_handle ().value ())
8428 != updating_axis_limits.end ())
8429 return;
8430
8431 Matrix kids = Matrix (1, 1, h.value ());
8432
8433 double min_val = octave::numeric_limits<double>::Inf ();
8434 double max_val = -octave::numeric_limits<double>::Inf ();
8435 double min_pos = octave::numeric_limits<double>::Inf ();
8436 double max_neg = -octave::numeric_limits<double>::Inf ();
8437
8438 char update_type = 0;
8439
8440 Matrix limits (1, 2);
8441 double val;
8442
8443#define FIX_LIMITS \
8444 val = limits(0); \
8445 if (octave::math::isfinite (val)) \
8446 min_val = val; \
8447 val = limits(1); \
8448 if (octave::math::isfinite (val)) \
8449 max_val = val;
8450
8451 if (axis_type == "xdata" || axis_type == "xscale"
8452 || axis_type == "xlimmode" || axis_type == "xliminclude"
8453 || axis_type == "xlim")
8454 {
8455 limits = m_properties.get_xlim ().matrix_value ();
8456 FIX_LIMITS;
8457
8458 update_type = 'x';
8459 if (m_properties.xlimmode_is ("auto"))
8460 {
8461 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'x');
8462
8463 std::string method = m_properties.get_xlimitmethod ();
8464 limits = m_properties.get_axis_limits (min_val, max_val,
8465 min_pos, max_neg,
8466 m_properties.xscale_is ("log"),
8467 method);
8468 }
8469 else
8470 m_properties.check_axis_limits (limits, kids,
8471 m_properties.xscale_is ("log"),
8472 update_type);
8473 }
8474 else if (axis_type == "ydata" || axis_type == "yscale"
8475 || axis_type == "ylimmode" || axis_type == "yliminclude"
8476 || axis_type == "ylim")
8477 {
8478 limits = m_properties.get_ylim ().matrix_value ();
8479 FIX_LIMITS;
8480
8481 update_type = 'y';
8482 if (m_properties.ylimmode_is ("auto"))
8483 {
8484 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'y');
8485
8486 std::string method = m_properties.get_ylimitmethod ();
8487 limits = m_properties.get_axis_limits (min_val, max_val,
8488 min_pos, max_neg,
8489 m_properties.yscale_is ("log"),
8490 method);
8491 }
8492 else
8493 m_properties.check_axis_limits (limits, kids,
8494 m_properties.yscale_is ("log"),
8495 update_type);
8496 }
8497 else if (axis_type == "zdata" || axis_type == "zscale"
8498 || axis_type == "zlimmode" || axis_type == "zliminclude"
8499 || axis_type == "zlim")
8500 {
8501 limits = m_properties.get_zlim ().matrix_value ();
8502 FIX_LIMITS;
8503
8504 update_type = 'z';
8505 if (m_properties.zlimmode_is ("auto"))
8506 {
8507 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'z');
8508
8509 m_properties.set_has3Dkids ((max_val - min_val) >
8510 std::numeric_limits<double>::epsilon ());
8511
8512 std::string method = m_properties.get_zlimitmethod ();
8513 limits = m_properties.get_axis_limits (min_val, max_val,
8514 min_pos, max_neg,
8515 m_properties.zscale_is ("log"),
8516 method);
8517 }
8518 else
8519 {
8520 // FIXME: get_children_limits is only needed here in order to know
8521 // if there are 3D children. Is there a way to avoid this call?
8522 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'z');
8523
8524 m_properties.set_has3Dkids ((max_val - min_val) >
8525 std::numeric_limits<double>::epsilon ());
8526
8527 m_properties.check_axis_limits (limits, kids,
8528 m_properties.zscale_is ("log"),
8529 update_type);
8530 }
8531 }
8532 else if (axis_type == "cdata" || axis_type == "climmode"
8533 || axis_type == "cdatamapping" || axis_type == "climinclude"
8534 || axis_type == "clim")
8535 {
8536 if (m_properties.climmode_is ("auto"))
8537 {
8538 limits = m_properties.get_clim ().matrix_value ();
8539 FIX_LIMITS;
8540
8541 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'c');
8542
8543 if (min_val > max_val)
8544 {
8545 min_val = min_pos = 0;
8546 max_val = 1;
8547 }
8548 else if (min_val == max_val)
8549 {
8550 max_val = min_val + 1;
8551 min_val -= 1;
8552 }
8553
8554 limits(0) = min_val;
8555 limits(1) = max_val;
8556
8557 update_type = 'c';
8558 }
8559 }
8560 else if (axis_type == "alphadata" || axis_type == "alimmode"
8561 || axis_type == "alphadatamapping" || axis_type == "aliminclude"
8562 || axis_type == "alim")
8563 {
8564 if (m_properties.alimmode_is ("auto"))
8565 {
8566 limits = m_properties.get_alim ().matrix_value ();
8567 FIX_LIMITS;
8568
8569 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'a');
8570
8571 if (min_val > max_val)
8572 {
8573 min_val = min_pos = 0;
8574 max_val = 1;
8575 }
8576 else if (min_val == max_val)
8577 max_val = min_val + 1;
8578
8579 limits(0) = min_val;
8580 limits(1) = max_val;
8581
8582 update_type = 'a';
8583 }
8584 }
8585
8586#undef FIX_LIMITS
8587
8588 octave::unwind_protect_var<std::set<double>>
8589 restore_var (updating_axis_limits);
8590
8591 updating_axis_limits.insert (get_handle ().value ());
8592 bool is_auto;
8593
8594 switch (update_type)
8595 {
8596 case 'x':
8597 is_auto = m_properties.xlimmode_is ("auto");
8598 m_properties.set_xlim (limits);
8599 if (is_auto)
8600 m_properties.set_xlimmode ("auto");
8601 m_properties.update_xlim ();
8602 break;
8603
8604 case 'y':
8605 is_auto = m_properties.ylimmode_is ("auto");
8606 m_properties.set_ylim (limits);
8607 if (is_auto)
8608 m_properties.set_ylimmode ("auto");
8609 m_properties.update_ylim ();
8610 break;
8611
8612 case 'z':
8613 is_auto = m_properties.zlimmode_is ("auto");
8614 m_properties.set_zlim (limits);
8615 if (is_auto)
8616 m_properties.set_zlimmode ("auto");
8617 m_properties.update_zlim ();
8618 break;
8619
8620 case 'c':
8621 m_properties.set_clim (limits);
8622 m_properties.set_climmode ("auto");
8623 break;
8624
8625 case 'a':
8626 m_properties.set_alim (limits);
8627 m_properties.set_alimmode ("auto");
8628 break;
8629
8630 default:
8631 break;
8632 }
8633
8634 m_properties.update_transform ();
8635}
8636
8637// FIXME: This function is called repeatedly while the axes are being set up.
8638// There is probably some way to make this more efficient.
8639
8640void
8641axes::update_axis_limits (const std::string& axis_type)
8642{
8643 if ((updating_axis_limits.find (get_handle ().value ())
8644 != updating_axis_limits.end ())
8645 || (updating_aspectratios.find (get_handle ().value ())
8646 != updating_aspectratios.end ()))
8647 return;
8648
8649 Matrix kids = m_properties.get_all_children ();
8650
8651 double min_val = octave::numeric_limits<double>::Inf ();
8652 double max_val = -octave::numeric_limits<double>::Inf ();
8653 double min_pos = octave::numeric_limits<double>::Inf ();
8654 double max_neg = -octave::numeric_limits<double>::Inf ();
8655
8656 char update_type = 0;
8657
8658 Matrix limits;
8659
8660 if (axis_type == "xdata" || axis_type == "xscale"
8661 || axis_type == "xlimmode" || axis_type == "xliminclude"
8662 || axis_type == "xlim")
8663 {
8664 update_type = 'x';
8665 if (m_properties.xlimmode_is ("auto"))
8666 {
8667 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'x');
8668
8669 std::string method = m_properties.get_xlimitmethod ();
8670 limits = m_properties.get_axis_limits (min_val, max_val,
8671 min_pos, max_neg,
8672 m_properties.xscale_is ("log"),
8673 method);
8674 }
8675 else
8676 {
8677 limits = m_properties.get_xlim ().matrix_value ();
8678 m_properties.check_axis_limits (limits, kids,
8679 m_properties.xscale_is ("log"),
8680 update_type);
8681 if (axis_type == "xscale")
8682 update_type = 'x';
8683 }
8684 }
8685 else if (axis_type == "ydata" || axis_type == "yscale"
8686 || axis_type == "ylimmode" || axis_type == "yliminclude"
8687 || axis_type == "ylim")
8688 {
8689 update_type = 'y';
8690 if (m_properties.ylimmode_is ("auto"))
8691 {
8692 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'y');
8693
8694 std::string method = m_properties.get_ylimitmethod ();
8695 limits = m_properties.get_axis_limits (min_val, max_val,
8696 min_pos, max_neg,
8697 m_properties.yscale_is ("log"),
8698 method);
8699 }
8700 else
8701 {
8702 limits = m_properties.get_ylim ().matrix_value ();
8703 m_properties.check_axis_limits (limits, kids,
8704 m_properties.yscale_is ("log"),
8705 update_type);
8706 if (axis_type == "yscale")
8707 update_type = 'y';
8708 }
8709 }
8710 else if (axis_type == "zdata" || axis_type == "zscale"
8711 || axis_type == "zlimmode" || axis_type == "zliminclude"
8712 || axis_type == "zlim")
8713 {
8714 update_type = 'z';
8715 if (m_properties.zlimmode_is ("auto"))
8716 {
8717 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'z');
8718
8719 m_properties.set_has3Dkids ((max_val - min_val) >
8720 std::numeric_limits<double>::epsilon ());
8721
8722 // FIXME: How to correctly handle (positive or negative) log scale?
8723 if ((! octave::math::isfinite (min_val)
8724 || ! octave::math::isfinite (max_val))
8725 && ! m_properties.zscale_is ("log"))
8726 min_val = max_val = 0.;
8727
8728 std::string method = m_properties.get_zlimitmethod ();
8729 limits = m_properties.get_axis_limits (min_val, max_val,
8730 min_pos, max_neg,
8731 m_properties.zscale_is ("log"),
8732 method);
8733 }
8734 else
8735 {
8736 // FIXME: get_children_limits is only needed here in order to know
8737 // if there are 3D children. Is there a way to avoid this call?
8738 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'z');
8739
8740 m_properties.set_has3Dkids ((max_val - min_val) >
8741 std::numeric_limits<double>::epsilon ());
8742
8743 limits = m_properties.get_zlim ().matrix_value ();
8744 m_properties.check_axis_limits (limits, kids,
8745 m_properties.zscale_is ("log"),
8746 update_type);
8747 if (axis_type == "zscale")
8748 update_type = 'z';
8749 }
8750 }
8751 else if (axis_type == "cdata" || axis_type == "climmode"
8752 || axis_type == "cdatamapping" || axis_type == "climinclude"
8753 || axis_type == "clim")
8754 {
8755 if (m_properties.climmode_is ("auto"))
8756 {
8757 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'c');
8758
8759 if (min_val > max_val)
8760 {
8761 min_val = min_pos = 0;
8762 max_val = 1;
8763 }
8764 else if (min_val == max_val)
8765 {
8766 max_val = min_val + 1;
8767 min_val -= 1;
8768 }
8769
8770 limits.resize (1, 2);
8771
8772 limits(0) = min_val;
8773 limits(1) = max_val;
8774
8775 update_type = 'c';
8776 }
8777
8778 }
8779 else if (axis_type == "alphadata" || axis_type == "alimmode"
8780 || axis_type == "alphadatamapping" || axis_type == "aliminclude"
8781 || axis_type == "alim")
8782 {
8783 if (m_properties.alimmode_is ("auto"))
8784 {
8785 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'a');
8786
8787 if (min_val > max_val)
8788 {
8789 min_val = min_pos = 0;
8790 max_val = 1;
8791 }
8792 else if (min_val == max_val)
8793 max_val = min_val + 1;
8794
8795 limits.resize (1, 2);
8796
8797 limits(0) = min_val;
8798 limits(1) = max_val;
8799
8800 update_type = 'a';
8801 }
8802
8803 }
8804
8805 octave::unwind_protect_var<std::set<double>>
8806 restore_var (updating_axis_limits);
8807
8808 updating_axis_limits.insert (get_handle ().value ());
8809 bool is_auto;
8810
8811 switch (update_type)
8812 {
8813 case 'x':
8814 is_auto = m_properties.xlimmode_is ("auto");
8815 m_properties.set_xlim (limits);
8816 if (is_auto)
8817 m_properties.set_xlimmode ("auto");
8818 m_properties.update_xlim ();
8819 break;
8820
8821 case 'y':
8822 is_auto = m_properties.ylimmode_is ("auto");
8823 m_properties.set_ylim (limits);
8824 if (is_auto)
8825 m_properties.set_ylimmode ("auto");
8826 m_properties.update_ylim ();
8827 break;
8828
8829 case 'z':
8830 is_auto = m_properties.zlimmode_is ("auto");
8831 m_properties.set_zlim (limits);
8832 if (is_auto)
8833 m_properties.set_zlimmode ("auto");
8834 m_properties.update_zlim ();
8835 break;
8836
8837 case 'c':
8838 m_properties.set_clim (limits);
8839 m_properties.set_climmode ("auto");
8840 break;
8841
8842 case 'a':
8843 m_properties.set_alim (limits);
8844 m_properties.set_alimmode ("auto");
8845 break;
8846
8847 default:
8848 break;
8849 }
8850
8851 m_properties.update_transform ();
8852}
8853
8854inline double
8855force_in_range (double x, double lower, double upper)
8856{
8857 if (x < lower)
8858 return lower;
8859 else if (x > upper)
8860 return upper;
8861 else
8862 return x;
8863}
8864
8865static Matrix
8866do_zoom (double val, double factor, const Matrix& lims, bool is_logscale)
8867{
8868 Matrix new_lims = lims;
8869
8870 double lo = lims(0);
8871 double hi = lims(1);
8872
8873 bool is_negative = lo < 0 && hi < 0;
8874
8875 if (is_logscale)
8876 {
8877 if (is_negative)
8878 {
8879 double tmp = hi;
8880 hi = std::log10 (-lo);
8881 lo = std::log10 (-tmp);
8882 val = std::log10 (-val);
8883 }
8884 else
8885 {
8886 hi = std::log10 (hi);
8887 lo = std::log10 (lo);
8888 val = std::log10 (val);
8889 }
8890 }
8891
8892 // Perform the zooming
8893 lo = val + (lo - val) / factor;
8894 hi = val + (hi - val) / factor;
8895
8896 if (is_logscale)
8897 {
8898 if (is_negative)
8899 {
8900 double tmp = -std::pow (10.0, hi);
8901 hi = -std::pow (10.0, lo);
8902 lo = tmp;
8903 }
8904 else
8905 {
8906 lo = std::pow (10.0, lo);
8907 hi = std::pow (10.0, hi);
8908 }
8909 }
8910
8911 new_lims(0) = lo;
8912 new_lims(1) = hi;
8913
8914 return new_lims;
8915}
8916
8917void
8918axes::properties::zoom_about_point (const std::string& mode,
8919 double x, double y, double factor,
8920 bool push_to_zoom_stack)
8921{
8922 // FIXME: Do we need error checking here?
8923 Matrix xlims = get_xlim ().matrix_value ();
8924 Matrix ylims = get_ylim ().matrix_value ();
8925
8926 // Get children axes limits
8927 Matrix kids = get_children ();
8928 double minx = octave::numeric_limits<double>::Inf ();
8929 double maxx = -octave::numeric_limits<double>::Inf ();
8930 double min_pos_x = octave::numeric_limits<double>::Inf ();
8931 double max_neg_x = -octave::numeric_limits<double>::Inf ();
8932 get_children_limits (minx, maxx, min_pos_x, max_neg_x, kids, 'x');
8933
8934 double miny = octave::numeric_limits<double>::Inf ();
8935 double maxy = -octave::numeric_limits<double>::Inf ();
8936 double min_pos_y = octave::numeric_limits<double>::Inf ();
8937 double max_neg_y = -octave::numeric_limits<double>::Inf ();
8938 get_children_limits (miny, maxy, min_pos_y, max_neg_y, kids, 'y');
8939
8940 xlims = do_zoom (x, factor, xlims, xscale_is ("log"));
8941 ylims = do_zoom (y, factor, ylims, yscale_is ("log"));
8942
8943 zoom (mode, xlims, ylims, push_to_zoom_stack);
8944}
8945
8946void
8947axes::properties::zoom (const std::string& mode, double factor,
8948 bool push_to_zoom_stack)
8949{
8950 // FIXME: Do we need error checking here?
8951 Matrix xlims = get_xlim ().matrix_value ();
8952 Matrix ylims = get_ylim ().matrix_value ();
8953
8954 double x = (xlims(0) + xlims(1)) / 2;
8955 double y = (ylims(0) + ylims(1)) / 2;
8956
8957 zoom_about_point (mode, x, y, factor, push_to_zoom_stack);
8958}
8959
8960void
8961axes::properties::push_zoom_stack ()
8962{
8963 if (m_zoom_stack.empty ())
8964 {
8965 m_zoom_stack.push_front (m_xlimmode.get ());
8966 m_zoom_stack.push_front (m_xlim.get ());
8967 m_zoom_stack.push_front (m_ylimmode.get ());
8968 m_zoom_stack.push_front (m_ylim.get ());
8969 m_zoom_stack.push_front (m_zlimmode.get ());
8970 m_zoom_stack.push_front (m_zlim.get ());
8971 m_zoom_stack.push_front (m_view.get ());
8972 }
8973}
8974
8975void
8976axes::properties::zoom (const std::string& mode,
8977 const Matrix& xl, const Matrix& yl,
8978 bool push_to_zoom_stack)
8979{
8980 if (xl(0) == xl(1) || yl(0) == yl(1))
8981 {
8982 warning ("invalid zoom region");
8983 return;
8984 }
8985
8986 if (push_to_zoom_stack)
8987 push_zoom_stack ();
8988
8989 if (mode == "horizontal" || mode == "both")
8990 {
8991 m_xlim = xl;
8992 m_xlimmode = "manual";
8993 }
8994
8995 if (mode == "vertical" || mode == "both")
8996 {
8997 m_ylim = yl;
8998 m_ylimmode = "manual";
8999 }
9000
9001 update_transform ();
9002
9003 if (mode == "horizontal" || mode == "both")
9004 update_xlim ();
9005
9006 if (mode == "vertical" || mode == "both")
9007 update_ylim ();
9008}
9009
9010static Matrix
9011do_translate (double x0, double x1, const Matrix& lims, bool is_logscale)
9012{
9013 Matrix new_lims = lims;
9014
9015 double lo = lims(0);
9016 double hi = lims(1);
9017
9018 bool is_negative = lo < 0 && hi < 0;
9019
9020 double delta;
9021
9022 if (is_logscale)
9023 {
9024 if (is_negative)
9025 {
9026 double tmp = hi;
9027 hi = std::log10 (-lo);
9028 lo = std::log10 (-tmp);
9029 x0 = -x0;
9030 x1 = -x1;
9031 }
9032 else
9033 {
9034 hi = std::log10 (hi);
9035 lo = std::log10 (lo);
9036 }
9037
9038 delta = std::log10 (x0) - std::log10 (x1);
9039 }
9040 else
9041 {
9042 delta = x0 - x1;
9043 }
9044
9045 // Perform the translation
9046 lo += delta;
9047 hi += delta;
9048
9049 if (is_logscale)
9050 {
9051 if (is_negative)
9052 {
9053 double tmp = -std::pow (10.0, hi);
9054 hi = -std::pow (10.0, lo);
9055 lo = tmp;
9056 }
9057 else
9058 {
9059 lo = std::pow (10.0, lo);
9060 hi = std::pow (10.0, hi);
9061 }
9062 }
9063
9064 new_lims(0) = lo;
9065 new_lims(1) = hi;
9066
9067 return new_lims;
9068}
9069
9070void
9071axes::properties::translate_view (const std::string& mode,
9072 double x0, double x1, double y0, double y1,
9073 bool push_to_zoom_stack)
9074{
9075 // FIXME: Do we need error checking here?
9076 Matrix xlims = get_xlim ().matrix_value ();
9077 Matrix ylims = get_ylim ().matrix_value ();
9078
9079 // Get children axes limits
9080 Matrix kids = get_children ();
9081 double minx = octave::numeric_limits<double>::Inf ();
9082 double maxx = -octave::numeric_limits<double>::Inf ();
9083 double min_pos_x = octave::numeric_limits<double>::Inf ();
9084 double max_neg_x = -octave::numeric_limits<double>::Inf ();
9085 get_children_limits (minx, maxx, min_pos_x, max_neg_x, kids, 'x');
9086
9087 double miny = octave::numeric_limits<double>::Inf ();
9088 double maxy = -octave::numeric_limits<double>::Inf ();
9089 double min_pos_y = octave::numeric_limits<double>::Inf ();
9090 double max_neg_y = -octave::numeric_limits<double>::Inf ();
9091 get_children_limits (miny, maxy, min_pos_y, max_neg_y, kids, 'y');
9092
9093 xlims = do_translate (x0, x1, xlims, xscale_is ("log"));
9094 ylims = do_translate (y0, y1, ylims, yscale_is ("log"));
9095
9096 zoom (mode, xlims, ylims, push_to_zoom_stack);
9097}
9098
9099void
9100axes::properties::pan (const std::string& mode, double factor,
9101 bool push_to_zoom_stack)
9102{
9103 // FIXME: Do we need error checking here?
9104 Matrix xlims = get_xlim ().matrix_value ();
9105 Matrix ylims = get_ylim ().matrix_value ();
9106
9107 double x0 = (xlims(0) + xlims(1)) / 2;
9108 double y0 = (ylims(0) + ylims(1)) / 2;
9109
9110 double x1 = x0 + (xlims(1) - xlims(0)) * factor;
9111 double y1 = y0 + (ylims(1) - ylims(0)) * factor;
9112
9113 translate_view (mode, x0, x1, y0, y1, push_to_zoom_stack);
9114}
9115
9116void
9117axes::properties::rotate3d (double x0, double x1, double y0, double y1,
9118 bool push_to_zoom_stack)
9119{
9120 if (push_to_zoom_stack)
9121 push_zoom_stack ();
9122
9123 Matrix bb = get_boundingbox (true);
9124 Matrix new_view = get_view ().matrix_value ();
9125
9126 // Compute new view angles
9127 new_view(0) += ((x0 - x1) * (180.0 / bb(2)));
9128 new_view(1) += ((y1 - y0) * (180.0 / bb(3)));
9129
9130 // Clipping
9131 new_view(1) = std::min (new_view(1), 90.0);
9132 new_view(1) = std::max (new_view(1), -90.0);
9133 if (new_view(0) > 180.0)
9134 new_view(0) -= 360.0;
9135 else if (new_view(0) < -180.0)
9136 new_view(0) += 360.0;
9137
9138 // Snapping
9139 double snapmargin = 1.0;
9140 for (int a = -90; a <= 90; a += 90)
9141 {
9142 if ((a - snapmargin) < new_view(1) && new_view(1) < (a + snapmargin))
9143 {
9144 new_view(1) = a;
9145 break;
9146 }
9147 }
9148
9149 for (int a = -180; a <= 180; a += 180)
9150 if ((a - snapmargin) < new_view(0) && new_view(0) < (a + snapmargin))
9151 {
9152 if (a == 180)
9153 new_view(0) = -180;
9154 else
9155 new_view(0) = a;
9156 break;
9157 }
9158
9159 // Update axes properties
9160 set_view (new_view);
9161}
9162
9163void
9164axes::properties::rotate_view (double delta_el, double delta_az,
9165 bool push_to_zoom_stack)
9166{
9167 if (push_to_zoom_stack)
9168 push_zoom_stack ();
9169
9170 Matrix v = get_view ().matrix_value ();
9171
9172 v(1) += delta_el;
9173
9174 if (v(1) > 90)
9175 v(1) = 90;
9176 if (v(1) < -90)
9177 v(1) = -90;
9178
9179 v(0) = fmod (v(0) - delta_az + 720, 360);
9180
9181 set_view (v);
9182
9183 update_transform ();
9184}
9185
9186void
9187axes::properties::unzoom ()
9188{
9189 if (m_zoom_stack.size () >= 7)
9190 {
9191 m_view = m_zoom_stack.front ();
9192 m_zoom_stack.pop_front ();
9193
9194 m_zlim = m_zoom_stack.front ();
9195 m_zoom_stack.pop_front ();
9196
9197 m_zlimmode = m_zoom_stack.front ();
9198 m_zoom_stack.pop_front ();
9199
9200 m_ylim = m_zoom_stack.front ();
9201 m_zoom_stack.pop_front ();
9202
9203 m_ylimmode = m_zoom_stack.front ();
9204 m_zoom_stack.pop_front ();
9205
9206 m_xlim = m_zoom_stack.front ();
9207 m_zoom_stack.pop_front ();
9208
9209 m_xlimmode = m_zoom_stack.front ();
9210 m_zoom_stack.pop_front ();
9211
9212 update_transform ();
9213
9214 update_xlim ();
9215 update_ylim ();
9216 update_zlim ();
9217
9218 update_view ();
9219 }
9220}
9221
9222void
9223axes::properties::update_handlevisibility ()
9224{
9225 if (! is_handle_visible ())
9226 {
9227 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
9228
9229 graphics_object go (gh_mgr.get_object (get___myhandle__ ()));
9230
9231 graphics_object fig (go.get_ancestor ("figure"));
9232 octave_value ca = fig.get ("currentaxes");
9233 if (! ca.isempty () && ca.double_value () == m___myhandle__)
9234 {
9235 octave::autolock guard (gh_mgr.graphics_lock ());
9236
9237 octave_value kids = fig.get ("children");
9238 if (kids.isempty ())
9239 fig.set ("currentaxes", Matrix ());
9240 else
9241 {
9242 NDArray kidsarray = kids.array_value ();
9243 fig.set ("currentaxes", kidsarray(0));
9244 }
9245 }
9246 }
9247
9248 base_properties::update_handlevisibility ();
9249}
9250
9251void
9252figure::properties::init_toolkit ()
9253{
9254 octave::gtk_manager& gtk_mgr = octave::__get_gtk_manager__ ();
9255
9256 m_toolkit = gtk_mgr.get_toolkit ();
9257}
9258
9259void
9260axes::properties::clear_zoom_stack (bool do_unzoom)
9261{
9262 std::size_t items_to_leave_on_stack = (do_unzoom ? 7 : 0);
9263
9264 while (m_zoom_stack.size () > items_to_leave_on_stack)
9265 m_zoom_stack.pop_front ();
9266
9267 if (do_unzoom)
9268 unzoom ();
9269}
9270
9271void
9272axes::properties::trigger_normals_calc ()
9273{
9274 // Find all patch (and surface) objects within axes
9275 std::list<graphics_object> children_list;
9276 std::list<graphics_object>::iterator children_list_iter;
9277 get_children_of_type ("patch", false, true, children_list);
9278 get_children_of_type ("surface", false, true, children_list);
9279
9280 // trigger normals calculation for these objects
9281 for (children_list_iter = children_list.begin ();
9282 children_list_iter != children_list.end (); children_list_iter++)
9283 {
9284 graphics_object kid = *children_list_iter;
9285 if (kid.isa ("patch"))
9286 {
9287 patch::properties& patch_props
9288 = dynamic_cast<patch::properties&> (kid.get_properties ());
9289 patch_props.update_normals (false);
9290 }
9291 else
9292 {
9293 surface::properties& surface_props
9294 = dynamic_cast<surface::properties&> (kid.get_properties ());
9295 surface_props.update_normals (false);
9296 }
9297 }
9298}
9299
9300void
9301axes::reset_default_properties ()
9302{
9303 // empty list of local defaults
9304 m_default_properties = property_list ();
9305
9306 // Save warning state of "Octave:deprecated-property"
9307 int state = toggle_warn ("Octave:deprecated-property", false);
9308
9309 // reset factory defaults
9310 remove_all_listeners ();
9311 set_defaults ("reset");
9312
9313 toggle_warn ("Octave:deprecated-property", true, state);
9314}
9315
9316void
9317axes::initialize (const graphics_object& go)
9318{
9319 base_graphics_object::initialize (go);
9320
9321 xinitialize (m_properties.get_title ());
9322 xinitialize (m_properties.get_xlabel ());
9323 xinitialize (m_properties.get_ylabel ());
9324 xinitialize (m_properties.get_zlabel ());
9325
9326 m_properties.sync_positions ();
9327}
9328
9329// ---------------------------------------------------------------------
9330
9331Matrix
9332line::properties::compute_xlim () const
9333{
9334 Matrix m (1, 4);
9335
9336 m(0) = m_xdata.min_val ();
9337 m(1) = m_xdata.max_val ();
9338 m(2) = m_xdata.min_pos ();
9339 m(3) = m_xdata.max_neg ();
9340
9341 return m;
9342}
9343
9344Matrix
9345line::properties::compute_ylim () const
9346{
9347 Matrix m (1, 4);
9348
9349 m(0) = m_ydata.min_val ();
9350 m(1) = m_ydata.max_val ();
9351 m(2) = m_ydata.min_pos ();
9352 m(3) = m_ydata.max_neg ();
9353
9354 return m;
9355}
9356
9357// ---------------------------------------------------------------------
9358
9359Matrix
9360text::properties::get_data_position () const
9361{
9362 Matrix pos = get_position ().matrix_value ();
9363
9364 if (! units_is ("data"))
9365 pos = convert_text_position (pos, *this, get_units (), "data");
9366
9367 return pos;
9368}
9369
9370Matrix
9371text::properties::get_extent_matrix (bool rotated) const
9372{
9373 // FIXME: Should this function also add the (x,y) base position?
9374 Matrix ext = m_extent.get ().matrix_value ();
9375
9376 if (rotated && get_rotation () != 0)
9377 {
9378 double rot = get_rotation () * 4.0 * atan (1.0) / 180;
9379 double x0 = ext(0) * cos (rot) - ext(1) * sin (rot);
9380 double x1 = x0;
9381 double y0 = ext(0) * sin (rot) + ext(1) * cos (rot);
9382 double y1 = y0;
9383
9384 double tmp = (ext(0)+ext(2)) * cos (rot) - ext(1) * sin (rot);
9385 x0 = std::min (x0, tmp);
9386 x1 = std::max (x1, tmp);
9387 tmp = (ext(0)+ext(2)) * sin (rot) + ext(1) * cos (rot);
9388 y0 = std::min (y0, tmp);
9389 y1 = std::max (y1, tmp);
9390
9391 tmp = (ext(0)+ext(2)) * cos (rot) - (ext(1)+ext(3)) * sin (rot);
9392 x0 = std::min (x0, tmp);
9393 x1 = std::max (x1, tmp);
9394 tmp = (ext(0)+ext(2)) * sin (rot) + (ext(1)+ext(3)) * cos (rot);
9395 y0 = std::min (y0, tmp);
9396 y1 = std::max (y1, tmp);
9397
9398 tmp = ext(0) * cos (rot) - (ext(1)+ext(3)) * sin (rot);
9399 x0 = std::min (x0, tmp);
9400 x1 = std::max (x1, tmp);
9401 tmp = ext(0) * sin (rot) + (ext(1)+ext(3)) * cos (rot);
9402 y0 = std::min (y0, tmp);
9403 y1 = std::max (y1, tmp);
9404
9405 ext(0) = x0;
9406 ext(1) = y0;
9407 ext(2) = x1 - x0;
9408 ext(3) = y1 - y0;
9409 }
9410
9411 return ext;
9412}
9413
9415text::properties::get_extent () const
9416{
9417 // FIXME: This doesn't work right for 3D plots.
9418 // (It doesn't in Matlab either, at least not in version 6.5.)
9419 Matrix m = get_extent_matrix (true);
9420 Matrix pos = get_position ().matrix_value ();
9421 Matrix p = convert_text_position (pos, *this, get_units (), "pixels");
9422
9423 m(0) += p(0);
9424 m(1) += p(1);
9425
9426 Matrix bbox = convert_text_position (m, *this, "pixels", get_units ());
9427
9428 double dpr = device_pixel_ratio (get___myhandle__ ());
9429
9430 for (octave_idx_type ii = 0; ii < bbox.numel (); ii++)
9431 bbox(ii) = bbox(ii) / dpr;
9432
9433 return bbox;
9434}
9435
9436void
9437text::properties::set_fontunits (const octave_value& val)
9438{
9439 caseless_str old_fontunits = get_fontunits ();
9440
9441 if (m_fontunits.set (val, true))
9442 {
9443 update_fontunits (old_fontunits);
9444 mark_modified ();
9445 }
9446}
9447
9448void
9449text::properties::update_fontunits (const caseless_str& old_units)
9450{
9451 caseless_str new_units = get_fontunits ();
9452 double parent_height = 0;
9453 double fontsz = get_fontsize ();
9454
9455 if (new_units == "normalized" || old_units == "normalized")
9456 {
9457 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
9458
9459 graphics_object go (gh_mgr.get_object (get___myhandle__ ()));
9460
9461 graphics_object ax (go.get_ancestor ("axes"));
9462
9463 parent_height = ax.get_properties ().get_boundingbox (true).elem (3);
9464 }
9465
9466 fontsz = convert_font_size (fontsz, old_units, new_units, parent_height);
9467
9468 set_fontsize (octave_value (fontsz));
9469}
9470
9471void
9472text::properties::update_font ()
9473{
9474 double dpr = device_pixel_ratio (get___myhandle__ ());
9475
9476 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
9477
9478 octave::autolock guard (gh_mgr.graphics_lock ());
9479
9480 m_txt_renderer.set_font (get ("fontname").string_value (),
9481 get ("fontweight").string_value (),
9482 get ("fontangle").string_value (),
9483 get ("__fontsize_points__").double_value () * dpr);
9484
9485 m_txt_renderer.set_anti_aliasing (is_fontsmoothing ());
9486
9487 Matrix c = get_color_rgb ();
9488 if (! c.isempty ())
9489 m_txt_renderer.set_color (c);
9490
9491}
9492
9493void
9494text::properties::update_text_extent ()
9495{
9496 int halign = 0;
9497 int valign = 0;
9498
9499 if (horizontalalignment_is ("center"))
9500 halign = 1;
9501 else if (horizontalalignment_is ("right"))
9502 halign = 2;
9503
9504 if (verticalalignment_is ("middle"))
9505 valign = 1;
9506 else if (verticalalignment_is ("top"))
9507 valign = 2;
9508 else if (verticalalignment_is ("baseline"))
9509 valign = 3;
9510 else if (verticalalignment_is ("cap"))
9511 valign = 4;
9512
9513 Matrix bbox;
9514
9515 // FIXME: string should be parsed only when modified, for efficiency
9516
9517 octave_value string_prop = get_string ();
9518
9519 string_vector sv = string_prop.string_vector_value ();
9520
9521 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
9522
9523 octave::autolock guard (gh_mgr.graphics_lock ());
9524
9525 m_txt_renderer.text_to_pixels (sv.join ("\n"), m_pixels, bbox,
9526 halign, valign, 0.0, get_interpreter ());
9527 // The bbox is relative to the text's position. We'll leave it that
9528 // way, because get_position does not return valid results when the
9529 // text is first constructed. Conversion to proper coordinates is
9530 // performed in get_extent.
9531 set_extent (bbox);
9532
9533 if (__autopos_tag___is ("xlabel") || __autopos_tag___is ("ylabel")
9534 || __autopos_tag___is ("zlabel") || __autopos_tag___is ("title"))
9535 update_autopos ("sync");
9536}
9537
9538void
9539text::properties::request_autopos ()
9540{
9541 if (__autopos_tag___is ("xlabel") || __autopos_tag___is ("ylabel")
9542 || __autopos_tag___is ("zlabel") || __autopos_tag___is ("title"))
9543 update_autopos (get___autopos_tag__ ());
9544}
9545
9546void
9547text::properties::update_units ()
9548{
9549 if (! units_is ("data"))
9550 {
9551 set_xliminclude ("off");
9552 set_yliminclude ("off");
9553 set_zliminclude ("off");
9554 }
9555
9556 Matrix pos = get_position ().matrix_value ();
9557
9558 pos = convert_text_position (pos, *this, m_cached_units, get_units ());
9559
9560 // FIXME: if the current axes view is 2D, then one should probably drop
9561 // the z-component of "pos" and leave "zliminclude" to "off".
9562
9563 bool autopos = positionmode_is ("auto");
9564
9565 set_position (pos);
9566
9567 if (autopos)
9568 set_positionmode ("auto");
9569
9570 if (units_is ("data"))
9571 {
9572 set_xliminclude ("on");
9573 set_yliminclude ("on");
9574 // FIXME: see above
9575 set_zliminclude ("off");
9576 }
9577
9578 m_cached_units = get_units ();
9579}
9580
9581double
9582text::properties::get___fontsize_points__ (double box_pix_height) const
9583{
9584 double fontsz = get_fontsize ();
9585 double parent_height = box_pix_height;
9586
9587 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
9588
9589 graphics_object go (gh_mgr.get_object (get___myhandle__ ()));
9590
9591 if (fontunits_is ("normalized") && parent_height <= 0)
9592 {
9593 graphics_object ax (go.get_ancestor ("axes"));
9594
9595 parent_height = ax.get_properties ().get_boundingbox (true).elem (3);
9596 }
9597
9598 return convert_font_size (fontsz, get_fontunits (), "points", parent_height);
9599}
9600
9601// ---------------------------------------------------------------------
9602
9604image::properties::get_color_data () const
9605{
9606 return convert_cdata (*this, get_cdata (), cdatamapping_is ("scaled"), 3);
9607}
9608
9609// ---------------------------------------------------------------------
9610
9611void
9612light::initialize (const graphics_object& go)
9613{
9614 base_graphics_object::initialize (go);
9615
9616 // trigger normals calculation for the respective children of this axes object
9617 axes::properties& parent_axes_prop
9618 = dynamic_cast<axes::properties&> (go.get_ancestor ("axes").get_properties ());
9619 parent_axes_prop.trigger_normals_calc ();
9620}
9621
9622void
9623light::properties::update_visible ()
9624{
9625 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
9626
9627 graphics_object go = gh_mgr.get_object (get___myhandle__ ());
9628
9629 axes::properties& ax_props = dynamic_cast<axes::properties&>
9630 (go.get_ancestor ("axes").get_properties ());
9631 if (is_visible ())
9632 ax_props.increase_num_lights ();
9633 else
9634 ax_props.decrease_num_lights ();
9635}
9636
9637// ---------------------------------------------------------------------
9638
9639bool
9640patch::properties::get_do_lighting () const
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
9649 return (ax_props.get_num_lights () > 0);
9650}
9651
9653patch::properties::get_color_data () const
9654{
9655 octave_value fvc = get_facevertexcdata ();
9656 if (fvc.is_undefined () || fvc.isempty ())
9657 return Matrix ();
9658 else
9659 return convert_cdata (*this, fvc, cdatamapping_is ("scaled"), 2);
9660}
9661
9662static bool updating_patch_data = false;
9663
9664void
9665patch::properties::update_fvc ()
9666{
9667 if (updating_patch_data)
9668 return;
9669
9670 Matrix xd = get_xdata ().matrix_value ();
9671 Matrix yd = get_ydata ().matrix_value ();
9672 Matrix zd = get_zdata ().matrix_value ();
9673 NDArray cd = get_cdata ().array_value ();
9674
9675 m_bad_data_msg = "";
9676 if (xd.dims () != yd.dims ()
9677 || (xd.dims () != zd.dims () && ! zd.isempty ()))
9678 {
9679 m_bad_data_msg = "x/y/zdata must have the same dimensions";
9680 return;
9681 }
9682
9683 octave_idx_type nv = xd.rows ();
9684 octave_idx_type nf = xd.columns ();
9685 octave_idx_type ncv = cd.rows ();
9686 octave_idx_type ncf = cd.columns ();
9687 if ((ncf > 1 || ncv > 1) && ((ncf != nf) || (ncv != 1 && ncv != nv)))
9688 {
9689 m_bad_data_msg = "cdata does not match number of faces "
9690 "or number of vertices per face";
9691 return;
9692 }
9693
9694 bool isRGB = false;
9695 if (cd.ndims () == 3)
9696 isRGB = true;
9697
9698 // Faces and Vertices
9699 dim_vector dv;
9700 bool is3D = false;
9701 if (nv == 1 && nf > 1)
9702 {
9703 nv = nf;
9704 nf = 1;
9705 xd = xd.as_column ();
9706 yd = yd.as_column ();
9707 zd = zd.as_column ();
9708 }
9709
9710 bool pervertex = false;
9711 if (ncv == nv)
9712 pervertex = true;
9713
9714 dv(0) = nv * nf;
9715 if (zd.isempty ())
9716 dv(1) = 2;
9717 else
9718 {
9719 dv(1) = 3;
9720 is3D = true;
9721 }
9722
9723 Matrix vert (dv);
9724 Matrix idx (nf, nv);
9725
9726 Matrix fvc;
9727 if (pervertex)
9728 {
9729 // "facevertexcdata" holds color data per vertex
9730 fvc.resize (nv * nf, (isRGB ? 3 : 1));
9731 }
9732 else if (! cd.isempty ())
9733 {
9734 // "facevertexcdata" holds color data per face
9735 dv(0) = ncf;
9736 dv(1) = (isRGB ? 3 : 1);
9737 fvc = cd.reshape (dv);
9738 }
9739
9740 // create list of vertices from x/y/zdata
9741 // FIXME: It might be possible to share vertices between adjacent faces.
9742 octave_idx_type kk = 0;
9743 for (octave_idx_type jj = 0; jj < nf; jj++)
9744 {
9745 for (octave_idx_type ii = 0; ii < nv; ii++)
9746 {
9747 vert(kk, 0) = xd(ii, jj);
9748 vert(kk, 1) = yd(ii, jj);
9749 if (is3D)
9750 vert(kk, 2) = zd(ii, jj);
9751
9752 if (pervertex)
9753 {
9754 fvc(kk, 0) = cd(ii, jj, 0);
9755 if (isRGB)
9756 {
9757 fvc(kk, 1) = cd(ii, jj, 1);
9758 fvc(kk, 2) = cd(ii, jj, 2);
9759 }
9760 }
9761
9762 idx(jj, ii) = static_cast<double> (kk+1);
9763
9764 kk++;
9765 }
9766 }
9767
9768 // FIXME: shouldn't we update facevertexalphadata here ?
9769
9770 octave::unwind_protect_var<bool> restore_var (updating_patch_data, true);
9771
9772 m_faces.set (idx);
9773 m_vertices.set (vert);
9774 m_facevertexcdata.set (fvc);
9775}
9776
9777// core coplanar tester
9778bool
9780{
9781 // Accuracy note: this test will also accept single precision input (although
9782 // stored in double precision). This is because the error threshold is
9783 // sqrt(tol) = 1.5e-7.
9784 double tol = 100 * std::numeric_limits<double>::epsilon ();
9785 EIG eig (cov, false, false, true);
9786 ColumnVector ev = real (eig.eigenvalues ());
9787 return ev.min () <= tol * ev.max ();
9788}
9789
9790std::vector<octave_idx_type>
9791coplanar_partition (const Matrix& vert, const Matrix& idx,
9793{
9794 std::vector<octave_idx_type> coplanar_ends;
9795
9796 Matrix plane_pivot = Matrix (1, 3, 0.0);
9797 for (octave_idx_type i = 0; i < 3; i++)
9798 plane_pivot(0, i) = vert(idx(0, jj)-1, i);
9799
9800 Matrix fc = Matrix (0, 3, 0.0); // face corner vertex coordinates
9801 Matrix fa = Matrix (1, 3, 0.0); // for append face corner
9802 Matrix coor_cov = Matrix (3, 3, 0.0);
9803
9804 if (nc >= 5)
9805 {
9806 // Coplanar test that involves all points.
9807 // For nc == 4, this initial test is not beneficial at all.
9808 // If the probability of coplanar input is more than half, for
9809 // the best average performance, we should use nc >= 5.
9810 // Higher threshold is meaningful only when input is known to be
9811 // non-coplanar and nc is small.
9812
9813 fc.resize (nc - 1, 3);
9814 for (octave_idx_type j = 1; j < nc; j++)
9815 for (octave_idx_type i = 0; i < 3; i++)
9816 fc(j-1, i) = vert(idx(j, jj)-1, i) - plane_pivot(i);
9817
9818 coor_cov = fc.transpose () * fc;
9819 if (is_coplanar (coor_cov))
9820 {
9821 coplanar_ends.push_back (nc - 1);
9822 return coplanar_ends;
9823 }
9824 }
9825
9826 fc.resize (3, 3);
9827 octave_idx_type i_start = 1;
9828 octave_idx_type i_end = 2;
9829
9830 // Split the polygon into coplanar segments.
9831 // The first point is common corner of all planes.
9832 while (i_start < nc - 1)
9833 {
9834 i_end = i_start + 2;
9835 if (i_end > nc - 1)
9836 {
9837 coplanar_ends.push_back (nc - 1);
9838 break;
9839 }
9840
9841 // Algorithm: Start from 3 points, keep adding points until the point set
9842 // is no more in a plane. Record the coplanar point set, then advance
9843 // i_start.
9844
9845 // Prepare 1+3 points for coplanar test.
9846 // The first point is implicitly included.
9847 for (octave_idx_type j = 0; j < 3; j++)
9848 for (octave_idx_type i = 0; i < 3; i++)
9849 fc(j, i) = vert(idx(j+i_start, jj)-1, i) - plane_pivot(i);
9850
9851 // covariance matrix between coordinates of vertices
9852 coor_cov = fc.transpose () * fc;
9853
9854 while (true)
9855 {
9856 // coplanar test
9857 if (! is_coplanar (coor_cov))
9858 break;
9859
9860 i_end++;
9861 if (i_end > nc - 1)
9862 break;
9863
9864 // add a point to plane
9865 for (octave_idx_type i = 0; i < 3; i++)
9866 fa(0, i) = vert(idx(i_end, jj)-1, i) - plane_pivot(i);
9867 coor_cov += fa.transpose () * fa;
9868 }
9869
9870 i_start = i_end - 1;
9871 coplanar_ends.push_back (i_start);
9872 }
9873 return coplanar_ends;
9874}
9875
9876void
9877patch::properties::update_data ()
9878{
9879 if (updating_patch_data)
9880 return;
9881
9882 Matrix idx = get_faces ().matrix_value ().transpose ();
9883 Matrix vert = get_vertices ().matrix_value ();
9884 NDArray fvc = get_facevertexcdata ().array_value ();
9885
9886 octave_idx_type nfaces = idx.columns ();
9887 octave_idx_type nvert = vert.rows ();
9888
9889 // Check all vertices in faces are defined
9890 m_bad_data_msg = "";
9891 if (static_cast<double> (nvert) < idx.row_max ().max ())
9892 {
9893 m_bad_data_msg = R"(some vertices in "faces" property are undefined)";
9894 return;
9895 }
9896
9897 // Check if number of color values matches number of faces or vertices
9898 octave_idx_type nfvc = fvc.rows ();
9899 if (nfvc > 1 && nfvc != nfaces && nfvc != nvert)
9900 {
9901 m_bad_data_msg = "number of facevertexcdata values matches "
9902 "neither number of faces nor number of vertices";
9903 return;
9904 }
9905
9906 // Replace NaNs
9907 if (idx.any_element_is_inf_or_nan ())
9908 {
9909 for (octave_idx_type jj = 0; jj < idx.columns (); jj++)
9910 {
9911 double valid_vert = idx(0, jj);
9912 bool turn_valid = false;
9913 for (octave_idx_type ii = 0; ii < idx.rows (); ii++)
9914 {
9915 if (octave::math::isnan (idx(ii, jj)) || turn_valid)
9916 {
9917 idx(ii, jj) = valid_vert;
9918 turn_valid = true;
9919 }
9920 else
9921 valid_vert = idx(ii, jj);
9922 }
9923 }
9924 }
9925
9926 // check coplanarity for 3D-faces with more than 3 corners
9927 int fcmax = idx.rows ();
9928 if (fcmax > 3 && vert.columns () > 2
9929 && ! (facecolor_is ("none") && edgecolor_is ("none")))
9930 {
9931 m_coplanar_last_idx.resize (idx.columns ());
9932 for (octave_idx_type jj = 0; jj < idx.columns (); jj++)
9933 {
9934 if (octave::math::isnan (idx(3, jj)))
9935 continue;
9936
9937 // find first element that is NaN to get number of corners
9938 octave_idx_type nc = 3;
9939 while (nc < fcmax && ! octave::math::isnan (idx(nc, jj)))
9940 nc++;
9941
9942 // If any of the corners is NaN or Inf, skip coplanar test.
9943 // FIXME: Add support for non-coplanar faces with unclosed contour.
9944 bool is_unclosed = false;
9945 for (octave_idx_type j = 0; j < nc; j++)
9946 {
9947 const octave_idx_type k = idx(j, jj) - 1;
9948 if (! (octave::math::isfinite (vert(k, 0))
9949 && octave::math::isfinite (vert(k, 1))
9950 && octave::math::isfinite (vert(k, 2))))
9951 {
9952 is_unclosed = true;
9953 break;
9954 }
9955 }
9956 if (is_unclosed)
9957 continue;
9958
9959 m_coplanar_last_idx[jj] = coplanar_partition (vert, idx, nc, jj);
9960 }
9961 }
9962 else
9963 m_coplanar_last_idx.resize (0);
9964
9965 // Build cdata
9967 NDArray cd;
9968 bool pervertex = false;
9969
9970 if (fvc.rows () == nfaces || fvc.rows () == 1)
9971 {
9972 // "facevertexcdata" holds color data per face or same color for all
9973 dv(0) = 1;
9974 dv(1) = fvc.rows ();
9975 dv(2) = fvc.columns ();
9976 cd = fvc.reshape (dv);
9977 }
9978 else if (! fvc.isempty ())
9979 {
9980 // "facevertexcdata" holds color data per vertex
9981 dv(0) = idx.rows ();
9982 dv(1) = nfaces;
9983 dv(2) = fvc.columns ();
9984 cd.resize (dv);
9985 pervertex = true;
9986 }
9987
9988 // Build x,y,zdata and eventually per vertex cdata
9989 Matrix xd (idx.dims ());
9990 Matrix yd (idx.dims ());
9991 Matrix zd;
9992 bool has_zd = false;
9993 if (vert.columns () > 2)
9994 {
9995 zd = Matrix (idx.dims ());
9996 has_zd = true;
9997 }
9998
9999 for (octave_idx_type jj = 0; jj < nfaces; jj++)
10000 {
10001 for (octave_idx_type ii = 0; ii < idx.rows (); ii++)
10002 {
10003 octave_idx_type row = static_cast<octave_idx_type> (idx(ii, jj)-1);
10004 xd(ii, jj) = vert(row, 0);
10005 yd(ii, jj) = vert(row, 1);
10006
10007 if (has_zd)
10008 zd(ii, jj) = vert(row, 2);
10009
10010 if (pervertex)
10011 for (int kk = 0; kk < fvc.columns (); kk++)
10012 cd(ii, jj, kk) = fvc(row, kk);
10013 }
10014 }
10015
10016 // Update normals
10017 update_normals (true);
10018
10019 octave::unwind_protect_var<bool> restore_var (updating_patch_data, true);
10020
10021 set_xdata (xd);
10022 set_ydata (yd);
10023 set_zdata (zd);
10024 set_cdata (cd);
10025}
10026
10027inline void
10028cross_product (double x1, double y1, double z1,
10029 double x2, double y2, double z2,
10030 double& x, double& y, double& z)
10031{
10032 x += (y1 * z2 - z1 * y2);
10033 y += (z1 * x2 - x1 * z2);
10034 z += (x1 * y2 - y1 * x2);
10035}
10036
10037void
10038patch::properties::calc_face_normals (Matrix& fn)
10039{
10040 Matrix v = get_vertices ().matrix_value ();
10041 Matrix f = get_faces ().matrix_value ();
10042
10043 bool is_3D = (v.columns () == 3); // 2D or 3D patches
10044 octave_idx_type num_f = f.rows (); // number of faces
10045 octave_idx_type max_nc = f.columns (); // max. number of polygon corners
10046
10047 // In which cases can we skip updating the normals?
10048 if (max_nc < 3)
10049 {
10050 fn = Matrix ();
10051 return;
10052 }
10053
10054 // Calculate normals for all faces
10055 octave_idx_type i1, i2, i3;
10056 octave_idx_type j1, j2;
10057 for (octave_idx_type i = 0; i < num_f; i++)
10058 {
10059 bool is_coplanar = true;
10060 if (m_coplanar_last_idx.size () > 0 && m_coplanar_last_idx[i].size () > 1)
10061 is_coplanar = false;
10062
10063 // get number of corners
10064 octave_idx_type nc = 3;
10065 if (max_nc > 3)
10066 {
10067 while (nc < max_nc && ! octave::math::isnan (f(i, nc)))
10068 nc++;
10069 }
10070
10071 RowVector fnc (3, 0.0);
10072 double& nx = fnc(0);
10073 double& ny = fnc(1);
10074 double& nz = fnc(2);
10075
10076 if (is_coplanar)
10077 {
10078 // fast way for coplanar polygons
10079 i1 = f(i, 0) - 1; i2 = f(i, 1) - 1; i3 = f(i, nc-1) - 1;
10080
10081 if (is_3D)
10083 (v(i3, 0) - v(i1, 0), v(i3, 1) - v(i1, 1), v(i3, 2) - v(i1, 2),
10084 v(i2, 0) - v(i1, 0), v(i2, 1) - v(i1, 1), v(i2, 2) - v(i1, 2),
10085 nx, ny, nz);
10086 else
10087 {
10088 nz = (v(i2, 0) - v(i1, 0)) * (v(i3, 1) - v(i1, 1)) -
10089 (v(i2, 1) - v(i1, 1)) * (v(i3, 0) - v(i1, 0));
10090 // 2-d vertices always point towards +z
10091 nz = (nz < 0) ? -nz : nz;
10092 }
10093 }
10094 else
10095 {
10096 // more general for non-planar polygons
10097
10098 // calculate face normal with Newell's method
10099 // https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal#Newell.27s_Method
10100
10101 j1 = nc - 1; j2 = 0;
10102 i1 = f(i, j1) - 1; i2 = f(i, j2) - 1;
10103
10104 nx = (v(i2, 1) - v(i1, 1)) * (v(i1, 2) + v(i2, 2));
10105 ny = (v(i2, 2) - v(i1, 2)) * (v(i1, 0) + v(i2, 0));
10106 nz = (v(i2, 0) - v(i1, 0)) * (v(i1, 1) + v(i2, 1));
10107
10108 for (octave_idx_type j = 1; j < nc; j++)
10109 {
10110 j1 = j-1; j2 = j;
10111 i1 = f(i, j1) - 1; i2 = f(i, j2) - 1;
10112
10113 nx += (v(i2, 1) - v(i1, 1)) * (v(i1, 2) + v(i2, 2));
10114 ny += (v(i2, 2) - v(i1, 2)) * (v(i1, 0) + v(i2, 0));
10115 nz += (v(i2, 0) - v(i1, 0)) * (v(i1, 1) + v(i2, 1));
10116 }
10117 }
10118
10119 // normalize normal vector
10120 double n_len = sqrt (nx*nx+ny*ny+nz*nz);
10121
10122 // assign normal to current face
10123 if ( n_len < std::numeric_limits<double>::epsilon () )
10124 for (octave_idx_type j = 0; j < 3; j++)
10125 fn(i, j) = 0.0;
10126 else
10127 for (octave_idx_type j = 0; j < 3; j++)
10128 fn(i, j) = fnc(j) / n_len;
10129 }
10130}
10131
10132void
10133patch::properties::update_face_normals (bool reset, bool force)
10134{
10135 if (updating_patch_data || ! facenormalsmode_is ("auto"))
10136 return;
10137
10138 if (force || ((facelighting_is ("flat") || edgelighting_is ("flat"))
10139 && get_do_lighting ()))
10140 {
10141 Matrix f = get_faces ().matrix_value ();
10142
10143 octave_idx_type num_f = f.rows (); // number of faces
10144 Matrix fn (num_f, 3, 0.0);
10145
10146 calc_face_normals (fn);
10147 m_facenormals = fn;
10148 }
10149 else if (reset)
10150 m_facenormals = Matrix ();
10151}
10152
10153void
10154patch::properties::update_vertex_normals (bool reset, bool force)
10155{
10156 if (updating_patch_data || ! vertexnormalsmode_is ("auto"))
10157 return;
10158
10159 if (force || ((facelighting_is ("gouraud") || facelighting_is ("phong")
10160 || edgelighting_is ("gouraud") || edgelighting_is ("phong"))
10161 && get_do_lighting ()))
10162 {
10163 Matrix v = get_vertices ().matrix_value ();
10164 Matrix f = get_faces ().matrix_value ();
10165
10166 octave_idx_type num_v = v.rows (); // number of vertices
10167 octave_idx_type num_f = f.rows (); // number of faces
10168 octave_idx_type max_nc = f.columns (); // max. number of polygon corners
10169
10170 // In which cases can we skip updating the normals?
10171 if (max_nc < 3)
10172 return;
10173
10174 // First step: Calculate the normals for all faces
10175 Matrix fn = get_facenormals ().matrix_value ();
10176 if ( fn.isempty () )
10177 {
10178 // calculate facenormals here
10179 fn = Matrix (num_f, 3, 0.0);
10180 calc_face_normals (fn);
10181 }
10182
10183 // Second step: assign normals to the respective vertices
10184
10185 // The following code collects the face normals for all faces adjacent to
10186 // each vertex. For this, a std::vector of length NUM_V (which might be
10187 // very large) is used so that memory is allocated from the heap rather
10188 // than the stack. Each element of this vector corresponds to one vertex
10189 // of the patch. The element itself is a variable length std::vector.
10190 // This second vector contains the face normals (of type RowVector) of
10191 // the adjacent faces.
10192 std::vector<std::vector<RowVector>> vec_vn (num_v);
10193 for (octave_idx_type i = 0; i < num_f; i++)
10194 {
10195 // get number of corners
10196 octave_idx_type nc = 3;
10197 if (max_nc > 3)
10198 {
10199 while (nc < max_nc && ! octave::math::isnan (f(i, nc)))
10200 nc++;
10201 }
10202
10203 for (octave_idx_type j = 0; j < nc; j++)
10204 vec_vn[static_cast<octave_idx_type> (f(i, j) - 1)].push_back (fn.row (i));
10205 }
10206
10207 // Third step: Calculate the normal for the vertices taking the average
10208 // of the normals determined from all adjacent faces
10209 Matrix vn (num_v, 3, 0.0);
10210 for (octave_idx_type i = 0; i < num_v; i++)
10211 {
10212 std::vector<RowVector>::iterator it = vec_vn[i].begin ();
10213
10214 // The normal of unused vertices is NaN.
10215 RowVector vn0 (3, octave_NaN);
10216
10217 if (it != vec_vn[i].end ())
10218 {
10219 // FIXME: Currently, the first vector also determines the
10220 // direction of the normal. How to determine the inner and outer
10221 // faces of all parts of the patch and point the normals outwards?
10222 // (Necessary for correct lighting with "backfacelighting" set to
10223 // "lit" or "unlit".) Matlab does not seem to do it correctly
10224 // either. So should we bother?
10225
10226 vn0 = *it;
10227
10228 for (++it; it != vec_vn[i].end (); ++it)
10229 {
10230 RowVector vn1 = *it;
10231 // Use sign of dot product to point vectors in a similar
10232 // direction before taking the average.
10233 double dir = (vn0(0)*vn1(0) + vn0(1)*vn1(1) + vn0(2)*vn1(2) < 0) ? -1 : 1;
10234 for (octave_idx_type j = 0; j < 3; j++)
10235 vn0(j) += dir * vn1(j);
10236 }
10237
10238 // normalize normal vector
10239 double n_len = sqrt (vn0(0)*vn0(0)+vn0(1)*vn0(1)+vn0(2)*vn0(2));
10240
10241 // save normal in matrix
10242 for (octave_idx_type j = 0; j < 3; j++)
10243 vn(i, j) = vn0(j)/n_len;
10244 }
10245 }
10246
10247 m_vertexnormals = vn;
10248 }
10249 else if (reset)
10250 m_vertexnormals = Matrix ();
10251}
10252
10253void
10254patch::initialize (const graphics_object& go)
10255{
10256 base_graphics_object::initialize (go);
10257
10258 // calculate normals for default data
10259 // This is done because the normals for the default data do not match
10260 // get(0, "DefaultPatchVertexNormals") in Matlab.
10261 m_properties.update_normals (true);
10262}
10263
10264
10265void
10266patch::reset_default_properties ()
10267{
10268 // empty list of local defaults
10269 m_default_properties = property_list ();
10270 xreset_default_properties (get_handle (), m_properties.factory_defaults ());
10271
10272 // calculate normals for default data
10273 // This is done because the normals for the default data do not match
10274 // get(0, "DefaultPatchVertexNormals") in Matlab.
10275 m_properties.update_normals (true);
10276}
10277
10278// ---------------------------------------------------------------------
10279
10281scatter::properties::get_color_data () const
10282{
10283 octave_value c = get_cdata ();
10284 if (c.is_undefined () || c.isempty ())
10285 return Matrix ();
10286 else
10287 return convert_cdata (*this, c, c.columns () == 1, 2);
10288}
10289
10290void
10291scatter::properties::update_data ()
10292{
10293 Matrix xd = get_xdata ().matrix_value ();
10294 Matrix yd = get_ydata ().matrix_value ();
10295 Matrix zd = get_zdata ().matrix_value ();
10296 Matrix cd = get_cdata ().matrix_value ();
10297 Matrix sd = get_sizedata ().matrix_value ();
10298
10299 m_bad_data_msg = "";
10300 if (xd.dims () != yd.dims ()
10301 || (xd.dims () != zd.dims () && ! zd.isempty ()))
10302 {
10303 m_bad_data_msg = "x/y/zdata must have the same dimensions";
10304 return;
10305 }
10306
10307 octave_idx_type x_rows = xd.rows ();
10308 octave_idx_type c_cols = cd.columns ();
10309 octave_idx_type c_rows = cd.rows ();
10310
10311 if (! cd.isempty () && (c_rows != 1 || c_cols != 3)
10312 && (c_rows != x_rows || (c_cols != 1 && c_cols != 3)))
10313 {
10314 m_bad_data_msg = "cdata must be an rgb triplet or have the same number "
10315 "of rows as X and one or three columns";
10316 return;
10317 }
10318
10319 octave_idx_type s_rows = sd.rows ();
10320 if (s_rows != 1 && s_rows != x_rows)
10321 {
10322 m_bad_data_msg = "sizedata must be a scalar or a vector with the same "
10323 "dimensions as X";
10324 return;
10325 }
10326}
10327
10328static bool updating_scatter_cdata = false;
10329
10330void
10331scatter::properties::update_color ()
10332{
10333 if (updating_scatter_cdata)
10334 return;
10335
10336 Matrix series_idx = get_seriesindex ().matrix_value ();
10337 if (series_idx.isempty ())
10338 return;
10339
10340 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
10341
10342 graphics_object go = gh_mgr.get_object (get___myhandle__ ());
10343
10344 axes::properties& parent_axes_prop
10345 = dynamic_cast<axes::properties&>
10346 (go.get_ancestor ("axes").get_properties ());
10347
10348 Matrix color_order = parent_axes_prop.get_colororder ().matrix_value ();
10349 octave_idx_type s = (static_cast<octave_idx_type> (series_idx(0)) - 1)
10350 % color_order.rows ();
10351
10352 Matrix color = Matrix (1, 3, 0.);
10353 color(0) = color_order(s, 0);
10354 color(1) = color_order(s, 1);
10355 color(2) = color_order(s, 2);
10356
10357 octave::unwind_protect_var<bool> restore_var (updating_scatter_cdata, true);
10358
10359 set_cdata (color);
10360 set_cdatamode ("auto");
10361}
10362
10363void
10364scatter::initialize (const graphics_object& go)
10365{
10366 base_graphics_object::initialize (go);
10367
10368 Matrix series_idx = m_properties.get_seriesindex ().matrix_value ();
10369 if (series_idx.isempty ())
10370 {
10371 // Increment series index counter in parent axes
10372 axes::properties& parent_axes_prop
10373 = dynamic_cast<axes::properties&>
10374 (go.get_ancestor ("axes").get_properties ());
10375
10376 if (! parent_axes_prop.nextplot_is ("add"))
10377 parent_axes_prop.set_nextseriesindex (1);
10378
10379 series_idx.resize (1, 1);
10380 series_idx(0) = parent_axes_prop.get_nextseriesindex ();
10381 m_properties.set_seriesindex (series_idx);
10382
10383 parent_axes_prop.set_nextseriesindex
10384 (parent_axes_prop.get_nextseriesindex () + 1);
10385 }
10386
10387 if (m_properties.cdatamode_is ("auto"))
10388 m_properties.update_color ();
10389}
10390
10391// ---------------------------------------------------------------------
10392
10394surface::properties::get_color_data () const
10395{
10396 return convert_cdata (*this, get_cdata (), cdatamapping_is ("scaled"), 3);
10397}
10398
10399bool
10400surface::properties::get_do_lighting () const
10401{
10402 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
10403
10404 graphics_object go = gh_mgr.get_object (get___myhandle__ ());
10405
10406 axes::properties& ax_prop = dynamic_cast<axes::properties&>
10407 (go.get_ancestor ("axes").get_properties ());
10408
10409 return (ax_prop.get_num_lights () > 0);
10410}
10411
10412void
10413surface::properties::update_face_normals (bool reset, bool force)
10414{
10415 if (! facenormalsmode_is ("auto"))
10416 return;
10417
10418 if (force || ((facelighting_is ("flat") || edgelighting_is ("flat"))
10419 && get_do_lighting ()))
10420 {
10421 Matrix x = get_xdata ().matrix_value ();
10422 Matrix y = get_ydata ().matrix_value ();
10423 Matrix z = get_zdata ().matrix_value ();
10424
10425 int p = z.columns ();
10426 int q = z.rows ();
10427
10428 // FIXME: There might be a cleaner way to do this. When data is changed
10429 // the update_xdata, update_ydata, update_zdata routines are called in a
10430 // serial fashion. Until the final call to update_zdata the matrices
10431 // will be of mismatched dimensions which can cause an out-of-bound
10432 // indexing in the code below. This one-liner prevents calculating
10433 // normals until dimensions match.
10434 if (x.columns () != p || y.rows () != q)
10435 return;
10436
10437 bool x_mat = (x.rows () == q);
10438 bool y_mat = (y.columns () == p);
10439
10440 NDArray n (dim_vector (q-1, p-1, 3), 1);
10441
10442 int i1, i2, j1, j2;
10443 i1 = i2 = 0;
10444 j1 = j2 = 0;
10445 double x0, x1, x2, x3, y0, y1, y2, y3, z0, z1, z2, z3;
10446 double x1m0, x2m1, x3m2, x0m3, y1m0, y2m1, y3m2, y0m3;
10447 double x1p0, x2p1, x3p2, x0p3, y1p0, y2p1, y3p2, y0p3;
10448 x3m2 = y0m3 = -1;
10449 x2m1 = x0m3 = y1m0 = y3m2 = 0;
10450 x1m0 = y2m1 = 1;
10451 x0p3 = y1p0 = 0;
10452 x1p0 = x3p2 = y2p1 = y0p3 = 1;
10453 x2p1 = y3p2 = 2;
10454
10455 for (int i = 0; i < p-1; i++)
10456 {
10457 i1 = i;
10458 i2 = i + 1;
10459
10460 for (int j = 0; j < q-1; j++)
10461 {
10462 j1 = j;
10463 j2 = j + 1;
10464
10465 if (x_mat || y_mat)
10466 {
10467 x0 = x(x_mat?j1:0, y_mat?i1:0);
10468 x1 = x(x_mat?j1:0, y_mat?i2:0);
10469 x2 = x(x_mat?j2:0, y_mat?i2:0);
10470 x3 = x(x_mat?j2:0, y_mat?i1:0);
10471 x1m0 = x1 - x0;
10472 x2m1 = x2 - x1;
10473 x3m2 = x3 - x2;
10474 x0m3 = x0 - x3;
10475 x1p0 = x1 + x0;
10476 x2p1 = x2 + x1;
10477 x3p2 = x3 + x2;
10478 x0p3 = x0 + x3;
10479 y0 = y(x_mat?j1:0, y_mat?i1:0);
10480 y1 = y(x_mat?j1:0, y_mat?i2:0);
10481 y2 = y(x_mat?j2:0, y_mat?i2:0);
10482 y3 = y(x_mat?j2:0, y_mat?i1:0);
10483 y1m0 = y1 - y0;
10484 y2m1 = y2 - y1;
10485 y3m2 = y3 - y2;
10486 y0m3 = y0 - y3;
10487 y1p0 = y1 + y0;
10488 y2p1 = y2 + y1;
10489 y3p2 = y3 + y2;
10490 y0p3 = y0 + y3;
10491 }
10492
10493 double& nx = n(j, i, 0);
10494 double& ny = n(j, i, 1);
10495 double& nz = n(j, i, 2);
10496
10497 z0 = z(j1, i1);
10498 z1 = z(j1, i2);
10499 z2 = z(j2, i2);
10500 z3 = z(j2, i1);
10501
10502 // calculate face normal with Newell's method
10503 // https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal#Newell.27s_Method
10504
10505 nx = y1m0 * (z1 + z0) + y2m1 * (z2 + z1)
10506 + y3m2 * (z3 + z2) + y0m3 * (z0 + z3);
10507 ny = (z1 - z0) * x1p0 + (z2 - z1) * x2p1
10508 + (z3 - z2) * x3p2 + (z0 - z3) * x0p3;
10509 nz = x1m0 * y1p0 + x2m1 * y2p1 + x3m2 * y3p2 + x0m3 * y0p3;
10510
10511 double d = std::max (std::max (fabs (nx), fabs (ny)), fabs (nz));
10512
10513 nx /= d;
10514 ny /= d;
10515 nz /= d;
10516 }
10517 }
10518 m_facenormals = n;
10519 }
10520 else if (reset)
10521 m_facenormals = Matrix ();
10522}
10523
10524void
10525surface::properties::update_vertex_normals (bool reset, bool force)
10526{
10527 if (! vertexnormalsmode_is ("auto"))
10528 return;
10529
10530 if (force || ((facelighting_is ("gouraud") || facelighting_is ("phong")
10531 || edgelighting_is ("gouraud") || edgelighting_is ("phong"))
10532 && get_do_lighting ()))
10533 {
10534 Matrix x = get_xdata ().matrix_value ();
10535 Matrix y = get_ydata ().matrix_value ();
10536 Matrix z = get_zdata ().matrix_value ();
10537
10538 int p = z.columns ();
10539 int q = z.rows ();
10540
10541 // FIXME: There might be a cleaner way to do this. When data is changed
10542 // the update_xdata, update_ydata, update_zdata routines are called in a
10543 // serial fashion. Until the final call to update_zdata the matrices
10544 // will be of mismatched dimensions which can cause an out-of-bound
10545 // indexing in the code below. This one-liner prevents calculating
10546 // normals until dimensions match.
10547 if (x.columns () != p || y.rows () != q)
10548 return;
10549
10550 NDArray n (dim_vector (q, p, 3), 0.0);
10551
10552 bool x_mat = (x.rows () == q);
10553 bool y_mat = (y.columns () == p);
10554
10555 int i1, i2, i3, j1, j2, j3;
10556 i1 = i2 = i3 = 0;
10557 j1 = j2 = j3 = 0;
10558
10559 for (int i = 0; i < p; i++)
10560 {
10561 if (y_mat)
10562 {
10563 i1 = i - 1;
10564 i2 = i;
10565 i3 = i + 1;
10566 }
10567
10568 for (int j = 0; j < q; j++)
10569 {
10570 if (x_mat)
10571 {
10572 j1 = j - 1;
10573 j2 = j;
10574 j3 = j + 1;
10575 }
10576
10577 double& nx = n(j, i, 0);
10578 double& ny = n(j, i, 1);
10579 double& nz = n(j, i, 2);
10580
10581 if ((j > 0) && (i > 0))
10582 // upper left quadrangle
10584 (x(j1, i-1)-x(j2, i), y(j-1, i1)-y(j, i2), z(j-1, i-1)-z(j, i),
10585 x(j2, i-1)-x(j1, i), y(j, i1)-y(j-1, i2), z(j, i-1)-z(j-1, i),
10586 nx, ny, nz);
10587
10588 if ((j > 0) && (i < (p -1)))
10589 // upper right quadrangle
10591 (x(j1, i+1)-x(j2, i), y(j-1, i3)-y(j, i2), z(j-1, i+1)-z(j, i),
10592 x(j1, i)-x(j2, i+1), y(j-1, i2)-y(j, i3), z(j-1, i)-z(j, i+1),
10593 nx, ny, nz);
10594
10595 if ((j < (q - 1)) && (i > 0))
10596 // lower left quadrangle
10598 (x(j2, i-1)-x(j3, i), y(j, i1)-y(j+1, i2), z(j, i-1)-z(j+1, i),
10599 x(j3, i-1)-x(j2, i), y(j+1, i1)-y(j, i2), z(j+1, i-1)-z(j, i),
10600 nx, ny, nz);
10601
10602 if ((j < (q - 1)) && (i < (p -1)))
10603 // lower right quadrangle
10605 (x(j3, i)-x(j2, i+1), y(j+1, i2)-y(j, i3), z(j+1, i)-z(j, i+1),
10606 x(j3, i+1)-x(j2, i), y(j+1, i3)-y(j, i2), z(j+1, i+1)-z(j, i),
10607 nx, ny, nz);
10608
10609 double d = -std::max (std::max (fabs (nx), fabs (ny)), fabs (nz));
10610
10611 nx /= d;
10612 ny /= d;
10613 nz /= d;
10614 }
10615 }
10616 m_vertexnormals = n;
10617 }
10618 else if (reset)
10619 m_vertexnormals = Matrix ();
10620}
10621
10622DEFMETHOD (__update_normals__, interp, args, ,
10623 doc: /* -*- texinfo -*-
10624@deftypefn {} {} __update_normals__ (@var{h})
10625Update FaceNormals and VertexNormals of the patch or surface referred to by
10626@var{h}.
10627
10628@end deftypefn */)
10629{
10630 gh_manager& gh_mgr = interp.get_gh_manager ();
10631
10632 octave::autolock guard (gh_mgr.graphics_lock ());
10633
10634 if (args.length () != 1)
10635 print_usage ();
10636
10637 octave_value val = args(0);
10638
10639 graphics_object go = gh_mgr.get_object (val);
10640
10641 if (go.isa ("surface"))
10642 {
10643 surface::properties& props
10644 = dynamic_cast <surface::properties&> (go.get_properties ());
10645 props.update_normals (false, true);
10646 }
10647 else if (go.isa ("patch"))
10648 {
10649 patch::properties& props
10650 = dynamic_cast <patch::properties&> (go.get_properties ());
10651 props.update_normals (false, true);
10652 }
10653 else
10654 error ("__update_normals__: "
10655 "H must be a handle to a valid surface or patch object.");
10656
10657 return ovl ();
10658}
10659
10660/*
10661%!test
10662%! hf = figure ("visible", "off");
10663%! unwind_protect
10664%! Z = peaks ();
10665%! hs = surf (Z, "facelighting", "none");
10666%! assert (isempty (get (hs, "vertexnormals")));
10667%! assert (isempty (get (hs, "facenormals")));
10668%! __update_normals__ (hs);
10669%! assert (! isempty (get (hs, "vertexnormals")));
10670%! assert (! isempty (get (hs, "facenormals")));
10671%! unwind_protect_cleanup
10672%! close (hf);
10673%! end_unwind_protect
10674
10675%!test
10676%! hf = figure ("visible", "off");
10677%! unwind_protect
10678%! hp = patch ("facelighting", "none");
10679%! assert (isempty (get (hp, "vertexnormals")));
10680%! assert (isempty (get (hp, "facenormals")));
10681%! __update_normals__ (hp);
10682%! assert (! isempty (get (hp, "vertexnormals")));
10683%! assert (! isempty (get (hp, "facenormals")));
10684%! unwind_protect_cleanup
10685%! close (hf);
10686%! end_unwind_protect
10687*/
10688
10689// ---------------------------------------------------------------------
10690
10691void
10692hggroup::properties::remove_child (const graphics_handle& h, bool from_root)
10693{
10694 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
10695
10696 graphics_object go = gh_mgr.get_object (h);
10697
10698 if (! from_root && go.isa ("light") && go.get_properties ().is_visible ())
10699 {
10700 axes::properties& ax_props
10701 = dynamic_cast<axes::properties&> (go.get_ancestor ("axes").get_properties ());
10702 ax_props.decrease_num_lights ();
10703 }
10704 base_properties::remove_child (h, from_root);
10705 update_limits ();
10706}
10707
10708void
10709hggroup::properties::adopt (const graphics_handle& h)
10710{
10711 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
10712
10713 graphics_object go = gh_mgr.get_object (h);
10714
10715 if (go.isa ("light") && go.get_properties ().is_visible ())
10716 {
10717 axes::properties& ax_props
10718 = dynamic_cast<axes::properties&> (go.get_ancestor ("axes").get_properties ());
10719 ax_props.increase_num_lights ();
10720 }
10721 base_properties::adopt (h);
10722 update_limits (h);
10723}
10724
10725void
10726hggroup::properties::update_limits () const
10727{
10728 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
10729
10730 graphics_object go = gh_mgr.get_object (m___myhandle__);
10731
10732 if (go)
10733 {
10734 go.update_axis_limits ("xlim");
10735 go.update_axis_limits ("ylim");
10736 go.update_axis_limits ("zlim");
10737 go.update_axis_limits ("clim");
10738 go.update_axis_limits ("alim");
10739 }
10740}
10741
10742void
10743hggroup::properties::update_limits (const graphics_handle& h) const
10744{
10745 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
10746
10747 graphics_object go = gh_mgr.get_object (m___myhandle__);
10748
10749 if (go)
10750 {
10751 go.update_axis_limits ("xlim", h);
10752 go.update_axis_limits ("ylim", h);
10753 go.update_axis_limits ("zlim", h);
10754 go.update_axis_limits ("clim", h);
10755 go.update_axis_limits ("alim", h);
10756 }
10757}
10758
10759static bool updating_hggroup_limits = false;
10760
10761void
10762hggroup::update_axis_limits (const std::string& axis_type,
10763 const graphics_handle& h)
10764{
10765 if (updating_hggroup_limits)
10766 return;
10767
10768 Matrix kids = Matrix (1, 1, h.value ());
10769
10770 double min_val = octave::numeric_limits<double>::Inf ();
10771 double max_val = -octave::numeric_limits<double>::Inf ();
10772 double min_pos = octave::numeric_limits<double>::Inf ();
10773 double max_neg = -octave::numeric_limits<double>::Inf ();
10774
10775 Matrix limits;
10776 double val;
10777
10778 char update_type = 0;
10779
10780 if (axis_type == "xlim" || axis_type == "xliminclude")
10781 {
10782 limits = m_properties.get_xlim ().matrix_value ();
10783 update_type = 'x';
10784 }
10785 else if (axis_type == "ylim" || axis_type == "yliminclude")
10786 {
10787 limits = m_properties.get_ylim ().matrix_value ();
10788 update_type = 'y';
10789 }
10790 else if (axis_type == "zlim" || axis_type == "zliminclude")
10791 {
10792 limits = m_properties.get_zlim ().matrix_value ();
10793 update_type = 'z';
10794 }
10795 else if (axis_type == "clim" || axis_type == "climinclude")
10796 {
10797 limits = m_properties.get_clim ().matrix_value ();
10798 update_type = 'c';
10799 }
10800 else if (axis_type == "alim" || axis_type == "aliminclude")
10801 {
10802 limits = m_properties.get_alim ().matrix_value ();
10803 update_type = 'a';
10804 }
10805
10806 if (limits.numel () == 4)
10807 {
10808 val = limits(0);
10809 if (octave::math::isfinite (val))
10810 min_val = val;
10811 val = limits(1);
10812 if (octave::math::isfinite (val))
10813 max_val = val;
10814 val = limits(2);
10815 if (octave::math::isfinite (val))
10816 min_pos = val;
10817 val = limits(3);
10818 if (octave::math::isfinite (val))
10819 max_neg = val;
10820 }
10821 else
10822 {
10823 limits.resize (1, 4);
10824 limits(0) = min_val;
10825 limits(1) = max_val;
10826 limits(2) = min_pos;
10827 limits(3) = max_neg;
10828 }
10829
10830 get_children_limits (min_val, max_val, min_pos, max_neg, kids, update_type);
10831
10832 octave::unwind_protect_var<bool> restore_var (updating_hggroup_limits, true);
10833
10834 if (limits(0) != min_val || limits(1) != max_val
10835 || limits(2) != min_pos || limits(3) != max_neg)
10836 {
10837 limits(0) = min_val;
10838 limits(1) = max_val;
10839 limits(2) = min_pos;
10840 limits(3) = max_neg;
10841
10842 switch (update_type)
10843 {
10844 case 'x':
10845 m_properties.set_xlim (limits);
10846 break;
10847
10848 case 'y':
10849 m_properties.set_ylim (limits);
10850 break;
10851
10852 case 'z':
10853 m_properties.set_zlim (limits);
10854 break;
10855
10856 case 'c':
10857 m_properties.set_clim (limits);
10858 break;
10859
10860 case 'a':
10861 m_properties.set_alim (limits);
10862 break;
10863
10864 default:
10865 break;
10866 }
10867
10868 graphics_handle hg = m_properties.get___myhandle__ ();
10869 base_graphics_object::update_axis_limits (axis_type, hg);
10870 }
10871}
10872
10873void
10874hggroup::update_axis_limits (const std::string& axis_type)
10875{
10876 if (updating_hggroup_limits)
10877 return;
10878
10879 Matrix kids = m_properties.get_children ();
10880
10881 double min_val = octave::numeric_limits<double>::Inf ();
10882 double max_val = -octave::numeric_limits<double>::Inf ();
10883 double min_pos = octave::numeric_limits<double>::Inf ();
10884 double max_neg = -octave::numeric_limits<double>::Inf ();
10885
10886 char update_type = 0;
10887
10888 if (axis_type == "xlim" || axis_type == "xliminclude")
10889 {
10890 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'x');
10891
10892 update_type = 'x';
10893 }
10894 else if (axis_type == "ylim" || axis_type == "yliminclude")
10895 {
10896 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'y');
10897
10898 update_type = 'y';
10899 }
10900 else if (axis_type == "zlim" || axis_type == "zliminclude")
10901 {
10902 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'z');
10903
10904 update_type = 'z';
10905 }
10906 else if (axis_type == "clim" || axis_type == "climinclude")
10907 {
10908 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'c');
10909
10910 update_type = 'c';
10911 }
10912 else if (axis_type == "alim" || axis_type == "aliminclude")
10913 {
10914 get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'a');
10915
10916 update_type = 'a';
10917 }
10918
10919 octave::unwind_protect_var<bool> restore_var (updating_hggroup_limits, true);
10920
10921 Matrix limits (1, 4);
10922
10923 limits(0) = min_val;
10924 limits(1) = max_val;
10925 limits(2) = min_pos;
10926 limits(3) = max_neg;
10927
10928 switch (update_type)
10929 {
10930 case 'x':
10931 m_properties.set_xlim (limits);
10932 break;
10933
10934 case 'y':
10935 m_properties.set_ylim (limits);
10936 break;
10937
10938 case 'z':
10939 m_properties.set_zlim (limits);
10940 break;
10941
10942 case 'c':
10943 m_properties.set_clim (limits);
10944 break;
10945
10946 case 'a':
10947 m_properties.set_alim (limits);
10948 break;
10949
10950 default:
10951 break;
10952 }
10953
10954 base_graphics_object::update_axis_limits (axis_type);
10955}
10956
10957// ---------------------------------------------------------------------
10958
10959void
10960uicontextmenu::properties::update_beingdeleted ()
10961{
10962 // Clear the uicontextmenu property of dependent objects
10963 if (m_beingdeleted.is ("on"))
10964 {
10965 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
10966
10967 std::list<graphics_handle> lst = get_dependent_obj_list ();
10968
10969 for (auto& hobj : lst)
10970 {
10971 graphics_object go = gh_mgr.get_object (hobj);
10972
10973 if (go.valid_object ()
10974 && go.get ("contextmenu") == get___myhandle__ ())
10975 go.set ("contextmenu", Matrix ());
10976 }
10977 }
10978}
10979
10980/*
10981## Test deletion/reset of uicontextmenu
10982%!test
10983%! hf = figure ("visible", "off");
10984%! hax = axes ("parent", hf);
10985%! unwind_protect
10986%! hctx1 = uicontextmenu ("parent", hf);
10987%! hctx2 = uicontextmenu ("parent", hf);
10988%! set (hf, "uicontextmenu", hctx2);
10989%! set (hax, "uicontextmenu", hctx2);
10990%! assert (get (hf, "uicontextmenu"), hctx2);
10991%! assert (get (hax, "uicontextmenu"), hctx2);
10992%! assert (get (hf, "children"), [hctx2; hctx1; hax]);
10993%! delete (hctx2);
10994%! assert (get (hf, "uicontextmenu"), []);
10995%! assert (get (hax, "uicontextmenu"), []);
10996%! assert (get (hf, "children"), [hctx1; hax]);
10997%! set (hf, "uicontextmenu", hctx1);
10998%! assert (get (hf, "uicontextmenu"), hctx1);
10999%! set (hf, "uicontextmenu", []);
11000%! assert (get (hf, "uicontextmenu"), []);
11001%! assert (get (hf, "children"), [hctx1; hax]);
11002%! unwind_protect_cleanup
11003%! close (hf);
11004%! end_unwind_protect
11005*/
11006
11007// ---------------------------------------------------------------------
11008
11010uicontrol::properties::get_extent () const
11011{
11012 Matrix m = m_extent.get ().matrix_value ();
11013
11014 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11015
11016 graphics_object parent_go = gh_mgr.get_object (get_parent ());
11017
11018 Matrix parent_bbox = parent_go.get_properties ().get_boundingbox (true);
11019 Matrix parent_size = parent_bbox.extract_n (0, 2, 1, 2);
11020
11021 return convert_position (m, "pixels", get_units (), parent_size);
11022}
11023
11024void
11025uicontrol::properties::update_text_extent ()
11026{
11027 // FIXME: support multiline text
11028
11029 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11030
11031 graphics_object go = gh_mgr.get_object (get___myhandle__ ());
11032
11033 set_extent (go.get_toolkit ().get_text_extent (go));
11034}
11035
11036void
11037uicontrol::properties::update_units ()
11038{
11039 Matrix pos = get_position ().matrix_value ();
11040
11041 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11042
11043 graphics_object parent_go = gh_mgr.get_object (get_parent ());
11044
11045 Matrix parent_bbox = parent_go.get_properties ().get_boundingbox (true);
11046 Matrix parent_size = parent_bbox.extract_n (0, 2, 1, 2);
11047
11048 pos = convert_position (pos, m_cached_units, get_units (), parent_size);
11049 set_position (pos);
11050
11051 m_cached_units = get_units ();
11052}
11053
11054void
11055uicontrol::properties::set_style (const octave_value& st)
11056{
11057 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11058
11059 graphics_object go_parent = gh_mgr.get_object (get_parent ());
11060 if (go_parent.valid_object () && go_parent.isa ("uibuttongroup"))
11061 {
11062 bool was_button = style_is ("radiobutton") || style_is ("togglebutton");
11063 m_style = st;
11064 bool now_button = style_is ("radiobutton") || style_is ("togglebutton");
11065 uibuttongroup::properties& props =
11066 dynamic_cast<uibuttongroup::properties&> (go_parent.get_properties ());
11067 // update selectedobject
11068 if (! was_button && now_button && ! props.get_selectedobject ().ok ())
11069 {
11070 props.set_selectedobject (get___myhandle__ ().value ());
11071 m_value.set (octave_value (1));
11072 }
11073 else if (was_button && ! now_button
11074 && (props.get_selectedobject ().value ()
11075 == get___myhandle__ ().value ()))
11076 props.set_selectedobject (Matrix ());
11077 }
11078
11079 // Don't notify the style change until the "value" property is fixed
11080 bool modified = m_style.set (st, true, false);
11081
11082 // Override "value" property for listbox and popupmenu.
11083 if (modified)
11084 {
11085 if (style_is ("listbox") || style_is ("popupmenu"))
11086 {
11087 Matrix v = m_value.get ().matrix_value ();
11088 if (v.numel () == 1 && v(0) == 0)
11089 m_value.set (octave_value (1), true, false);
11090 }
11091
11092 // Notify toolkit
11093
11094 graphics_object go = gh_mgr.get_object (get___myhandle__ ());
11095
11096 if (go)
11097 go.update (m_style.get_id ());
11098 }
11099}
11100
11101Matrix
11102uicontrol::properties::get_boundingbox (bool,
11103 const Matrix& parent_pix_size) const
11104{
11105 Matrix pos = get_position ().matrix_value ();
11106 Matrix parent_size (parent_pix_size);
11107
11108 if (parent_size.isempty ())
11109 {
11110 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11111
11112 graphics_object go = gh_mgr.get_object (get_parent ());
11113
11114 if (go.valid_object ())
11115 parent_size = go.get_properties ().get_boundingbox (true).extract_n (0, 2, 1, 2);
11116 else
11117 parent_size = default_figure_position ();
11118 }
11119
11120 pos = convert_position (pos, get_units (), "pixels", parent_size);
11121
11122 pos(0)--;
11123 pos(1)--;
11124 pos(1) = parent_size(1) - pos(1) - pos(3);
11125
11126 return pos;
11127}
11128
11129void
11130uicontrol::properties::set_fontunits (const octave_value& val)
11131{
11132 caseless_str old_fontunits = get_fontunits ();
11133
11134 if (m_fontunits.set (val, true))
11135 {
11136 update_fontunits (old_fontunits);
11137 mark_modified ();
11138 }
11139}
11140
11141void
11142uicontrol::properties::update_fontunits (const caseless_str& old_units)
11143{
11144 caseless_str new_units = get_fontunits ();
11145 double parent_height = get_boundingbox (false).elem (3);
11146 double fontsz = get_fontsize ();
11147
11148 fontsz = convert_font_size (fontsz, old_units, new_units, parent_height);
11149
11150 m_fontsize.set (octave_value (fontsz), true);
11151}
11152
11153double
11154uicontrol::properties::get___fontsize_points__ (double box_pix_height) const
11155{
11156 double fontsz = get_fontsize ();
11157 double parent_height = box_pix_height;
11158
11159 if (fontunits_is ("normalized") && parent_height <= 0)
11160 parent_height = get_boundingbox (false).elem (3);
11161
11162 return convert_font_size (fontsz, get_fontunits (), "points", parent_height);
11163}
11164
11165// ---------------------------------------------------------------------
11166
11167Matrix
11168uibuttongroup::properties::get_boundingbox (bool internal,
11169 const Matrix& parent_pix_size) const
11170{
11171 Matrix pos = get_position ().matrix_value ();
11172 Matrix parent_size (parent_pix_size);
11173
11174 if (parent_size.isempty ())
11175 {
11176 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11177
11178 graphics_object go = gh_mgr.get_object (get_parent ());
11179
11180 parent_size = go.get_properties ().get_boundingbox (true).extract_n (0, 2, 1, 2);
11181 }
11182
11183 pos = convert_position (pos, get_units (), "pixels", parent_size);
11184
11185 pos(0)--;
11186 pos(1)--;
11187 pos(1) = parent_size(1) - pos(1) - pos(3);
11188
11189 if (internal)
11190 {
11191 double outer_height = pos(3);
11192
11193 pos(0) = pos(1) = 0;
11194
11195 if (! bordertype_is ("none"))
11196 {
11197 double bw = get_borderwidth ();
11198 double mul = 1.0;
11199
11200 if (bordertype_is ("etchedin") || bordertype_is ("etchedout"))
11201 mul = 2.0;
11202
11203 pos(0) += mul * bw;
11204 pos(1) += mul * bw;
11205 pos(2) -= 2 * mul * bw;
11206 pos(3) -= 2 * mul * bw;
11207 }
11208
11209 if (! get_title ().empty ())
11210 {
11211 double fontsz = get_fontsize ();
11212
11213 if (! fontunits_is ("pixels"))
11214 {
11215 double res = xget (0, "screenpixelsperinch").double_value ();
11216
11217 if (fontunits_is ("points"))
11218 fontsz *= (res / 72.0);
11219 else if (fontunits_is ("inches"))
11220 fontsz *= res;
11221 else if (fontunits_is ("centimeters"))
11222 fontsz *= (res / 2.54);
11223 else if (fontunits_is ("normalized"))
11224 fontsz *= outer_height;
11225 }
11226
11227 if (titleposition_is ("lefttop") || titleposition_is ("centertop")
11228 || titleposition_is ("righttop"))
11229 pos(1) += (fontsz / 2);
11230 pos(3) -= (fontsz / 2);
11231 }
11232 }
11233
11234 return pos;
11235}
11236
11237void
11238uibuttongroup::properties::set_position (const octave_value& v)
11239{
11240 Matrix old_bb, new_bb;
11241 bool modified = false;
11242
11243 old_bb = get_boundingbox (true);
11244 modified = m_position.set (v, false);
11245 new_bb = get_boundingbox (true);
11246
11247 if (old_bb != new_bb)
11248 {
11249 if (old_bb(2) != new_bb(2) || old_bb(3) != new_bb(3))
11250 {
11251 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11252
11253 if (! get_resizefcn ().isempty ())
11254 gh_mgr.post_callback (m___myhandle__, "resizefcn");
11255
11256 if (! get_sizechangedfcn ().isempty ())
11257 gh_mgr.post_callback (m___myhandle__, "sizechangedfcn");
11258
11259 update_boundingbox ();
11260 }
11261 }
11262
11263 if (modified)
11264 {
11265 m_position.run_listeners (GCB_POSTSET);
11266 mark_modified ();
11267 }
11268}
11269
11270void
11271uibuttongroup::properties::set_units (const octave_value& val)
11272{
11273 caseless_str old_units = get_units ();
11274
11275 if (m_units.set (val, true))
11276 {
11277 update_units (old_units);
11278 mark_modified ();
11279 }
11280}
11281
11282void
11283uibuttongroup::properties::update_units (const caseless_str& old_units)
11284{
11285 Matrix pos = get_position ().matrix_value ();
11286
11287 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11288
11289 graphics_object parent_go = gh_mgr.get_object (get_parent ());
11290
11291 Matrix parent_bbox = parent_go.get_properties ().get_boundingbox (true);
11292 Matrix parent_size = parent_bbox.extract_n (0, 2, 1, 2);
11293
11294 pos = convert_position (pos, old_units, get_units (), parent_size);
11295 set_position (pos);
11296}
11297
11298void
11299uibuttongroup::properties::set_fontunits (const octave_value& val)
11300{
11301 caseless_str old_fontunits = get_fontunits ();
11302
11303 if (m_fontunits.set (val, true))
11304 {
11305 update_fontunits (old_fontunits);
11306 mark_modified ();
11307 }
11308}
11309
11310void
11311uibuttongroup::properties::update_fontunits (const caseless_str& old_units)
11312{
11313 caseless_str new_units = get_fontunits ();
11314 double parent_height = get_boundingbox (false).elem (3);
11315 double fontsz = get_fontsize ();
11316
11317 fontsz = convert_font_size (fontsz, old_units, new_units, parent_height);
11318
11319 set_fontsize (octave_value (fontsz));
11320}
11321
11322double
11323uibuttongroup::properties::get___fontsize_points__ (double box_pix_height) const
11324{
11325 double fontsz = get_fontsize ();
11326 double parent_height = box_pix_height;
11327
11328 if (fontunits_is ("normalized") && parent_height <= 0)
11329 parent_height = get_boundingbox (false).elem (3);
11330
11331 return convert_font_size (fontsz, get_fontunits (), "points", parent_height);
11332}
11333
11334void
11335uibuttongroup::properties::set_selectedobject (const octave_value& v)
11336{
11337 graphics_handle current_selectedobject = get_selectedobject();
11338 m_selectedobject = current_selectedobject;
11339 if (v.isempty ())
11340 {
11341 if (current_selectedobject.ok ())
11342 {
11343 m_selectedobject = graphics_handle ();
11344 mark_modified ();
11345 }
11346 return;
11347 }
11348
11349 graphics_handle val (v);
11350 if (val.ok ())
11351 {
11352 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11353
11354 graphics_object go (gh_mgr.get_object (val));
11355
11356 base_properties& gop = go.get_properties ();
11357
11358 if (go.valid_object ()
11359 && gop.get_parent () == get___myhandle__ ()
11360 && go.isa ("uicontrol"))
11361 {
11362 uicontrol::properties& cop
11363 = dynamic_cast<uicontrol::properties&> (go.get_properties ());
11364 const caseless_str& style = cop.get_style ();
11365 if (style.compare ("radiobutton") || style.compare ("togglebutton"))
11366 {
11367 m_selectedobject = val;
11368 mark_modified ();
11369 return;
11370 }
11371 }
11372 }
11373 err_set_invalid ("selectedobject");
11374}
11375
11376void
11377uibuttongroup::properties::remove_child (const graphics_handle& h,
11378 bool from_root)
11379{
11380 graphics_handle current_selected = get_selectedobject ();
11381 if (h.value () == current_selected.value ())
11382 set_selectedobject (Matrix ());
11383
11384 base_properties::remove_child (h, from_root);
11385}
11386
11387void
11388uibuttongroup::properties::adopt (const graphics_handle& h)
11389{
11390 base_properties::adopt (h);
11391
11392 graphics_handle current_selected = get_selectedobject ();
11393 bool has_selected = current_selected.ok ();
11394
11395 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11396
11397 graphics_object go = gh_mgr.get_object (h);
11398
11399 if (! has_selected && go.valid_object () && go.isa ("uicontrol"))
11400 {
11401 const uicontrol::properties& props =
11402 dynamic_cast<const uicontrol::properties&> (go.get_properties ());
11403 if (props.style_is ("radiobutton") || props.style_is ("togglebutton"))
11404 set_selectedobject (h.value ());
11405 }
11406}
11407
11408// ---------------------------------------------------------------------
11409
11410Matrix
11411uipanel::properties::get_boundingbox (bool internal,
11412 const Matrix& parent_pix_size) const
11413{
11414 Matrix pos = get_position ().matrix_value ();
11415 Matrix parent_size (parent_pix_size);
11416
11417 if (parent_size.isempty ())
11418 {
11419 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11420
11421 graphics_object go = gh_mgr.get_object (get_parent ());
11422
11423 parent_size = go.get_properties ().get_boundingbox (true).extract_n (0, 2, 1, 2);
11424 }
11425
11426 pos = convert_position (pos, get_units (), "pixels", parent_size);
11427
11428 pos(0)--;
11429 pos(1)--;
11430 pos(1) = parent_size(1) - pos(1) - pos(3);
11431
11432 if (internal)
11433 {
11434 double outer_height = pos(3);
11435
11436 pos(0) = pos(1) = 0;
11437
11438 if (! bordertype_is ("none"))
11439 {
11440 double bw = get_borderwidth ();
11441 double mul = 1.0;
11442
11443 if (bordertype_is ("etchedin") || bordertype_is ("etchedout"))
11444 mul = 2.0;
11445
11446 pos(0) += mul * bw;
11447 pos(1) += mul * bw;
11448 pos(2) -= 2 * mul * bw;
11449 pos(3) -= 2 * mul * bw;
11450 }
11451
11452 if (! get_title ().empty ())
11453 {
11454 double fontsz = get_fontsize ();
11455
11456 if (! fontunits_is ("pixels"))
11457 {
11458 double res = xget (0, "screenpixelsperinch").double_value ();
11459
11460 if (fontunits_is ("points"))
11461 fontsz *= (res / 72.0);
11462 else if (fontunits_is ("inches"))
11463 fontsz *= res;
11464 else if (fontunits_is ("centimeters"))
11465 fontsz *= (res / 2.54);
11466 else if (fontunits_is ("normalized"))
11467 fontsz *= outer_height;
11468 }
11469
11470 if (titleposition_is ("lefttop") || titleposition_is ("centertop")
11471 || titleposition_is ("righttop"))
11472 pos(1) += (fontsz / 2);
11473 pos(3) -= (fontsz / 2);
11474 }
11475 }
11476
11477 return pos;
11478}
11479
11480void
11481uipanel::properties::set_position (const octave_value& v)
11482{
11483 Matrix old_bb, new_bb;
11484 bool modified = false;
11485
11486 old_bb = get_boundingbox (true);
11487 modified = m_position.set (v, false);
11488 new_bb = get_boundingbox (true);
11489
11490 if (old_bb != new_bb)
11491 {
11492 if (old_bb(2) != new_bb(2) || old_bb(3) != new_bb(3))
11493 {
11494 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11495
11496 if (! get_resizefcn ().isempty ())
11497 gh_mgr.post_callback (m___myhandle__, "resizefcn");
11498
11499 if (! get_sizechangedfcn ().isempty ())
11500 gh_mgr.post_callback (m___myhandle__, "sizechangedfcn");
11501
11502 update_boundingbox ();
11503 }
11504 }
11505
11506 if (modified)
11507 {
11508 m_position.run_listeners (GCB_POSTSET);
11509 mark_modified ();
11510 }
11511}
11512
11513
11514void
11515uipanel::properties::set_units (const octave_value& val)
11516{
11517 caseless_str old_units = get_units ();
11518
11519 if (m_units.set (val, true))
11520 {
11521 update_units (old_units);
11522 mark_modified ();
11523 }
11524}
11525
11526void
11527uipanel::properties::update_units (const caseless_str& old_units)
11528{
11529 Matrix pos = get_position ().matrix_value ();
11530
11531 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11532
11533 graphics_object parent_go = gh_mgr.get_object (get_parent ());
11534
11535 Matrix parent_bbox = parent_go.get_properties ().get_boundingbox (true);
11536 Matrix parent_size = parent_bbox.extract_n (0, 2, 1, 2);
11537
11538 pos = convert_position (pos, old_units, get_units (), parent_size);
11539 set_position (pos);
11540}
11541
11542void
11543uipanel::properties::set_fontunits (const octave_value& val)
11544{
11545 caseless_str old_fontunits = get_fontunits ();
11546
11547 if (m_fontunits.set (val, true))
11548 {
11549 update_fontunits (old_fontunits);
11550 mark_modified ();
11551 }
11552}
11553
11554void
11555uipanel::properties::update_fontunits (const caseless_str& old_units)
11556{
11557 caseless_str new_units = get_fontunits ();
11558 double parent_height = get_boundingbox (false).elem (3);
11559 double fontsz = get_fontsize ();
11560
11561 fontsz = convert_font_size (fontsz, old_units, new_units, parent_height);
11562
11563 set_fontsize (octave_value (fontsz));
11564}
11565
11566double
11567uipanel::properties::get___fontsize_points__ (double box_pix_height) const
11568{
11569 double fontsz = get_fontsize ();
11570 double parent_height = box_pix_height;
11571
11572 if (fontunits_is ("normalized") && parent_height <= 0)
11573 parent_height = get_boundingbox (false).elem (3);
11574
11575 return convert_font_size (fontsz, get_fontunits (), "points", parent_height);
11576}
11577
11578// ---------------------------------------------------------------------
11579
11580Matrix
11581uitable::properties::get_boundingbox (bool,
11582 const Matrix& parent_pix_size) const
11583{
11584 Matrix pos = get_position ().matrix_value ();
11585 Matrix parent_size (parent_pix_size);
11586
11587 if (parent_size.isempty ())
11588 {
11589 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11590
11591 graphics_object go = gh_mgr.get_object (get_parent ());
11592
11593 parent_size = go.get_properties ().get_boundingbox (true).extract_n (0, 2, 1, 2);
11594 }
11595
11596 pos = convert_position (pos, get_units (), "pixels", parent_size);
11597
11598 pos(0)--;
11599 pos(1)--;
11600 pos(1) = parent_size(1) - pos(1) - pos(3);
11601
11602 return pos;
11603}
11604
11605void
11606uitable::properties::set_columnformat (const octave_value& val)
11607{
11608 /* Matlab only allows certain values for ColumnFormat. Here we only check the
11609 * structure of the argument. Values will be checked in Table.cc */
11610
11611 if (val.iscellstr ())
11612 {
11613 if (m_columnformat.set (val, true))
11614 mark_modified ();
11615 }
11616 else if (val.iscell ())
11617 {
11618 Cell cell_value = val.cell_value ();
11619
11620 for (int i = 0; i < cell_value.numel (); i++)
11621 {
11622 octave_value v = cell_value(i);
11623 if (v.iscell ())
11624 {
11625 /* We are in a pop-up menu selection.
11626 * Matlab only allows non-empty strings here. */
11627 Cell popup = v.cell_value ();
11628 for (int j = 0; j < popup.numel (); j++)
11629 {
11630 octave_value p = popup(j);
11631 if (! p.is_string () || p.isempty ())
11632 error ("set: pop-up menu definitions must be non-empty strings");
11633 }
11634 }
11635 else if (! (v.is_string () || v.isempty ()))
11636 {
11637 error ("set: columnformat definintions must be a cellstr of "
11638 "either 'char', 'short [e|g|eng]?', 'long [e|g|eng]?', "
11639 "'numeric', 'bank', '+', 'rat', 'logical', "
11640 "or a cellstr of non-empty pop-up menu definitions.");
11641 }
11642 }
11643
11644 if (m_columnformat.set (val, true))
11645 mark_modified ();
11646 }
11647 else if (val.isempty ())
11648 {
11649 if (m_columnformat.set (Cell (), true))
11650 mark_modified ();
11651 }
11652 else
11653 {
11654 error ("set: expecting cell of strings");
11655 }
11656}
11657
11658void
11659uitable::properties::set_columnwidth (const octave_value& val)
11660{
11661 bool error_exists = false;
11662
11663 if (val.is_string () && val.string_value (false) == "auto")
11664 error_exists = false;
11665 else if (val.iscell ())
11666 {
11667 Cell cell_value = val.cell_value ();
11668 for (int i = 0; i < cell_value.numel (); i++)
11669 {
11670 octave_value v = cell_value(i);
11671 if (v.is_string ())
11672 {
11673 if (v.string_value (false) != "auto")
11674 error_exists = true;
11675 }
11676 else if (v.iscell ())
11677 {
11678 error_exists = true;
11679 }
11680 else if (! v.is_scalar_type ())
11681 {
11682 error_exists = true;
11683 }
11684 }
11685 }
11686 else
11687 error_exists = true;
11688
11689 if (error_exists)
11690 error ("set: expecting either 'auto' or a cell of pixel values or auto");
11691 else
11692 {
11693 if (m_columnwidth.set (val, true))
11694 mark_modified ();
11695 }
11696}
11697
11698void
11699uitable::properties::set_units (const octave_value& val)
11700{
11701 caseless_str old_units = get_units ();
11702
11703 if (m_units.set (val, true))
11704 {
11705 update_units (old_units);
11706 mark_modified ();
11707 }
11708}
11709
11710void
11711uitable::properties::update_units (const caseless_str& old_units)
11712{
11713 Matrix pos = get_position ().matrix_value ();
11714
11715 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11716
11717 graphics_object parent_go = gh_mgr.get_object (get_parent ());
11718
11719 Matrix parent_bbox = parent_go.get_properties ().get_boundingbox (true);
11720 Matrix parent_size = parent_bbox.extract_n (0, 2, 1, 2);
11721
11722 pos = convert_position (pos, old_units, get_units (), parent_size);
11723 set_position (pos);
11724}
11725
11726void
11727uitable::properties::set_fontunits (const octave_value& val)
11728{
11729 caseless_str old_fontunits = get_fontunits ();
11730
11731 if (m_fontunits.set (val, true))
11732 {
11733 update_fontunits (old_fontunits);
11734 mark_modified ();
11735 }
11736}
11737
11738void
11739uitable::properties::update_fontunits (const caseless_str& old_units)
11740{
11741 caseless_str new_units = get_fontunits ();
11742 double parent_height = get_boundingbox (false).elem (3);
11743 double fontsz = get_fontsize ();
11744
11745 fontsz = convert_font_size (fontsz, old_units, new_units, parent_height);
11746
11747 set_fontsize (octave_value (fontsz));
11748}
11749
11750double
11751uitable::properties::get___fontsize_points__ (double box_pix_height) const
11752{
11753 double fontsz = get_fontsize ();
11754 double parent_height = box_pix_height;
11755
11756 if (fontunits_is ("normalized") && parent_height <= 0)
11757 parent_height = get_boundingbox (false).elem (3);
11758
11759 return convert_font_size (fontsz, get_fontunits (), "points", parent_height);
11760}
11761
11762double
11763uitable::properties::get_fontsize_pixels (double box_pix_height) const
11764{
11765 double fontsz = get_fontsize ();
11766 double parent_height = box_pix_height;
11767
11768 if (fontunits_is ("normalized") && parent_height <= 0)
11769 parent_height = get_boundingbox (false).elem (3);
11770
11771 return convert_font_size (fontsz, get_fontunits (), "pixels", parent_height);
11772}
11773
11774Matrix
11775uitable::properties::get_backgroundcolor_rgb ()
11776{
11777 Matrix bg = m_backgroundcolor.get ().matrix_value ();
11778 return bg.row (0);
11779}
11780
11781Matrix
11782uitable::properties::get_alternatebackgroundcolor_rgb ()
11783{
11784 int i = 0;
11785 Matrix bg = m_backgroundcolor.get ().matrix_value ();
11786 if (bg.rows () > 1)
11787 i = 1;
11788
11789 return bg.row (i);
11790}
11791
11792Matrix
11793uitable::properties::get_extent_matrix () const
11794{
11795 return m_extent.get ().matrix_value ();
11796}
11797
11799uitable::properties::get_extent () const
11800{
11801 // FIXME: Is it really acceptable to just let the toolkit update the extent?
11802 Matrix m = m_extent.get ().matrix_value ();
11803
11804 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11805
11806 graphics_object parent_go = gh_mgr.get_object (get_parent ());
11807
11808 if (parent_go)
11809 {
11810 Matrix parent_bbox = parent_go.get_properties ().get_boundingbox (true);
11811 Matrix parent_size = parent_bbox.extract_n (0, 2, 1, 2);
11812
11813 return convert_position (m, "pixels", get_units (), parent_size);
11814 }
11815
11816 return m;
11817}
11818
11819// ---------------------------------------------------------------------
11820
11822uitoolbar::get_default (const caseless_str& pname) const
11823{
11824 octave_value retval = m_default_properties.lookup (pname);
11825
11826 if (retval.is_undefined ())
11827 {
11828 graphics_handle parent_h = get_parent ();
11829
11830 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11831
11832 graphics_object parent_go = gh_mgr.get_object (parent_h);
11833
11834 retval = parent_go.get_default (pname);
11835 }
11836
11837 return retval;
11838}
11839
11840void
11841uitoolbar::reset_default_properties ()
11842{
11843 // empty list of local defaults
11844 m_default_properties = property_list ();
11845
11846 remove_all_listeners ();
11847 xreset_default_properties (get_handle (), m_properties.factory_defaults ());
11848}
11849
11850// ---------------------------------------------------------------------
11851
11853base_graphics_object::get_default (const caseless_str& pname) const
11854{
11855 graphics_handle parent_h = get_parent ();
11856
11857 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11858
11859 graphics_object parent_go = gh_mgr.get_object (parent_h);
11860
11861 return parent_go.get_default (type () + pname);
11862}
11863
11865base_graphics_object::get_factory_default (const caseless_str& name) const
11866{
11867 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11868
11869 graphics_object parent_go = gh_mgr.get_object (0);
11870
11871 return parent_go.get_factory_default (type () + name);
11872}
11873
11874class callback_event : public base_graphics_event
11875{
11876public:
11877 callback_event (const graphics_handle& h, const std::string& name,
11878 const octave_value& data = Matrix (),
11879 int busyaction = base_graphics_event::QUEUE)
11880 : base_graphics_event (busyaction), m_handle (h), m_callback_name (name),
11881 m_callback (), m_callback_data (data) { }
11882
11883 callback_event (const graphics_handle& h, const octave_value& cb,
11884 const octave_value& data = Matrix (),
11885 int busyaction = base_graphics_event::QUEUE)
11886 : base_graphics_event (busyaction), m_handle (h), m_callback_name (),
11887 m_callback (cb), m_callback_data (data) { }
11888
11889 OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (callback_event)
11890
11891 ~callback_event () = default;
11892
11893 void execute ()
11894 {
11895 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11896
11897 if (m_callback.is_defined ())
11898 gh_mgr.execute_callback (m_handle, m_callback, m_callback_data);
11899 else
11900 gh_mgr.execute_callback (m_handle, m_callback_name, m_callback_data);
11901 }
11902
11903private:
11904 graphics_handle m_handle;
11905 std::string m_callback_name;
11906 octave_value m_callback;
11907 octave_value m_callback_data;
11908};
11909
11910class mcode_event : public base_graphics_event
11911{
11912public:
11913 mcode_event (const graphics_handle& h, const std::string& cmd,
11914 int busyaction = base_graphics_event::QUEUE)
11915 : base_graphics_event (busyaction), m_handle (h), m_mcode (cmd)
11916 { }
11917
11918 OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (mcode_event)
11919
11920 ~mcode_event () = default;
11921
11922 void execute ()
11923 {
11924 if (! m_mcode.empty ())
11925 {
11926 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11927
11928 graphics_object go = gh_mgr.get_object (m_handle);
11929
11930 if (go.valid_object ())
11931 {
11932 octave_value cb (m_mcode);
11933 gh_mgr.execute_callback (m_handle, cb);
11934 }
11935 }
11936 }
11937
11938private:
11939 graphics_handle m_handle;
11940 std::string m_mcode;
11941};
11942
11943class function_event : public base_graphics_event
11944{
11945public:
11946
11947 function_event (graphics_event::event_fcn fcn, void *data = nullptr)
11948 : base_graphics_event (), m_function (fcn), m_function_data (data)
11949 { }
11950
11951 OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (function_event)
11952
11953 ~function_event () = default;
11954
11955 void execute ()
11956 {
11957 m_function (m_function_data);
11958 }
11959
11960private:
11961
11962 graphics_event::event_fcn m_function;
11963
11964 void *m_function_data;
11965};
11966
11967class set_event : public base_graphics_event
11968{
11969public:
11970 set_event (const graphics_handle& h, const std::string& name,
11971 const octave_value& value, bool do_notify_toolkit = true,
11972 bool redraw_figure = false)
11973 : base_graphics_event (), m_handle (h), m_property_name (name),
11974 m_property_value (value), m_notify_toolkit (do_notify_toolkit),
11975 m_redraw_figure (redraw_figure)
11976 { }
11977
11978 OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (set_event)
11979
11980 ~set_event () = default;
11981
11982 void execute ()
11983 {
11984 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
11985
11986 octave::autolock guard (gh_mgr.graphics_lock ());
11987
11988 graphics_object go = gh_mgr.get_object (m_handle);
11989
11990 if (go)
11991 {
11992 property p = go.get_properties ().get_property (m_property_name);
11993
11994 if (p.ok ())
11995 {
11996 // FIXME: figure position and outerposition properties set_xxx have
11997 // a signature that allows passing the notify_toolkit argument.
11998 // Should we change all set_xxx signatures and allow
11999 // base_properties::set to accept this also? This would allow for
12000 // the use of high level set_xxx instead of directly changing the
12001 // property value.
12002 if (go.isa ("figure") && m_property_name == "position")
12003 {
12004 figure::properties& fprops
12005 = dynamic_cast<figure::properties&> (go.get_properties ());
12006 fprops.set_position (m_property_value, m_notify_toolkit);
12007 }
12008 else if (go.isa ("figure") && m_property_name == "outerposition")
12009 {
12010 figure::properties& fprops
12011 = dynamic_cast<figure::properties&> (go.get_properties ());
12012 fprops.set_outerposition (m_property_value, m_notify_toolkit);
12013 }
12014 else
12015 p.set (m_property_value, true, m_notify_toolkit);
12016
12017 if (m_redraw_figure)
12018 {
12019 if (! go.isa ("figure"))
12020 go = go.get_ancestor ("figure");
12021
12022 if (go.valid_object ())
12023 {
12024 figure::properties& fprops
12025 = dynamic_cast<figure::properties&> (go.get_properties ());
12026 fprops.get_toolkit ().redraw_figure (go);
12027 }
12028 }
12029 }
12030 }
12031 }
12032
12033private:
12034 graphics_handle m_handle;
12035 std::string m_property_name;
12036 octave_value m_property_value;
12037 bool m_notify_toolkit;
12038 bool m_redraw_figure;
12039};
12040
12041graphics_event
12042graphics_event::create_callback_event (const graphics_handle& h,
12043 const std::string& name,
12044 const octave_value& data,
12045 int busyaction)
12046{
12047 return graphics_event (new callback_event (h, name, data, busyaction));
12048}
12049
12050graphics_event
12051graphics_event::create_callback_event (const graphics_handle& h,
12052 const octave_value& cb,
12053 const octave_value& data,
12054 int busyaction)
12055{
12056 return graphics_event (new callback_event (h, cb, data, busyaction));
12057}
12058
12059graphics_event
12060graphics_event::create_mcode_event (const graphics_handle& h,
12061 const std::string& cmd,
12062 int busyaction)
12063{
12064 return graphics_event (new mcode_event (h, cmd, busyaction));
12065}
12066
12067graphics_event
12068graphics_event::create_function_event (graphics_event::event_fcn fcn,
12069 void *data)
12070{
12071 return graphics_event (new function_event (fcn, data));
12072}
12073
12074graphics_event
12075graphics_event::create_set_event (const graphics_handle& h,
12076 const std::string& name,
12077 const octave_value& data,
12078 bool notify_toolkit, bool redraw_figure)
12079{
12080 return graphics_event (new set_event (h, name, data, notify_toolkit,
12081 redraw_figure));
12082}
12083
12084property_list::plist_map_type
12085root_figure::init_factory_properties ()
12086{
12087 property_list::plist_map_type plist_map;
12088
12089 plist_map["figure"] = figure::properties::factory_defaults ();
12090 plist_map["axes"] = axes::properties::factory_defaults ();
12091 plist_map["line"] = line::properties::factory_defaults ();
12092 plist_map["text"] = text::properties::factory_defaults ();
12093 plist_map["image"] = image::properties::factory_defaults ();
12094 plist_map["patch"] = patch::properties::factory_defaults ();
12095 plist_map["scatter"] = scatter::properties::factory_defaults ();
12096 plist_map["surface"] = surface::properties::factory_defaults ();
12097 plist_map["light"] = light::properties::factory_defaults ();
12098 plist_map["hggroup"] = hggroup::properties::factory_defaults ();
12099 plist_map["uimenu"] = uimenu::properties::factory_defaults ();
12100 plist_map["uicontrol"] = uicontrol::properties::factory_defaults ();
12101 plist_map["uibuttongroup"] = uibuttongroup::properties::factory_defaults ();
12102 plist_map["uipanel"] = uipanel::properties::factory_defaults ();
12103 plist_map["uicontextmenu"] = uicontextmenu::properties::factory_defaults ();
12104 plist_map["uitoolbar"] = uitoolbar::properties::factory_defaults ();
12105 plist_map["uipushtool"] = uipushtool::properties::factory_defaults ();
12106 plist_map["uitoggletool"] = uitoggletool::properties::factory_defaults ();
12107
12108 return plist_map;
12109}
12110
12111// ---------------------------------------------------------------------
12112
12113DEFMETHOD (ishghandle, interp, args, ,
12114 doc: /* -*- texinfo -*-
12115@deftypefn {} {@var{tf} =} ishghandle (@var{h})
12116Return true if @var{h} is a graphics handle and false otherwise.
12117
12118@var{h} may also be a matrix of handles in which case a logical array is
12119returned that is true where the elements of @var{h} are graphics handles and
12120false where they are not.
12121@seealso{isgraphics, isaxes, isfigure, ishandle}
12122@end deftypefn */)
12123{
12124 gh_manager& gh_mgr = interp.get_gh_manager ();
12125
12126 octave::autolock guard (gh_mgr.graphics_lock ());
12127
12128 if (args.length () != 1)
12129 print_usage ();
12130
12131 return ovl (ishghandle (args(0)));
12132}
12133
12134/*
12135%!test
12136%! hf = figure ("visible", "off");
12137%! unwind_protect
12138%! assert (ishghandle (hf));
12139%! assert (! ishghandle (-hf));
12140%! ax = gca ();
12141%! l = line ();
12142%! assert (ishghandle (ax));
12143%! assert (! ishghandle (-ax));
12144%! assert (ishghandle ([l, -1, ax, hf]), logical ([1, 0, 1, 1]));
12145%! assert (ishghandle ([l, -1, ax, hf]'), logical ([1, 0, 1, 1]'));
12146%! unwind_protect_cleanup
12147%! close (hf);
12148%! end_unwind_protect
12149
12150%!assert (ishghandle ([-1 0]), [false true])
12151*/
12152
12153static bool
12154is_handle_visible (const graphics_handle& h)
12155{
12156 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
12157
12158 return h.ok () && gh_mgr.is_handle_visible (h);
12159}
12160
12161static bool
12162is_handle_visible (double val)
12163{
12164 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
12165
12166 return is_handle_visible (gh_mgr.lookup (val));
12167}
12168
12169static octave_value
12170is_handle_visible (const octave_value& val)
12171{
12172 octave_value retval = false;
12173
12174 if (val.is_real_scalar () && is_handle_visible (val.double_value ()))
12175 retval = true;
12176 else if (val.isnumeric () && val.isreal ())
12177 {
12178 const NDArray handles = val.array_value ();
12179
12180 boolNDArray result (handles.dims ());
12181
12182 for (octave_idx_type i = 0; i < handles.numel (); i++)
12183 result.xelem (i) = is_handle_visible (handles(i));
12184
12185 retval = result;
12186 }
12187
12188 return retval;
12189}
12190
12191DEFUN (__is_handle_visible__, args, ,
12192 doc: /* -*- texinfo -*-
12193@deftypefn {} {@var{tf} =} __is_handle_visible__ (@var{h})
12194Undocumented internal function.
12195@end deftypefn */)
12196{
12197 if (args.length () != 1)
12198 print_usage ();
12199
12200 return ovl (is_handle_visible (args(0)));
12201}
12202
12203DEFMETHOD (reset, interp, args, ,
12204 doc: /* -*- texinfo -*-
12205@deftypefn {} {} reset (@var{h})
12206Reset the properties of the graphic object @var{h} to their default values.
12207
12208For figures, the properties @qcode{"position"}, @qcode{"units"},
12209@qcode{"windowstyle"}, and @qcode{"paperunits"} are not affected.
12210For axes, the properties @qcode{"position"} and @qcode{"units"} are
12211not affected.
12212
12213The input @var{h} may also be a vector of graphic handles in which case
12214each individual object will be reset.
12215@seealso{cla, clf, newplot}
12216@end deftypefn */)
12217{
12218 if (args.length () != 1)
12219 print_usage ();
12220
12221 // get vector of graphics handles
12222 ColumnVector hcv = args(0).xvector_value ("reset: H must be a graphics handle");
12223
12224 gh_manager& gh_mgr = interp.get_gh_manager ();
12225
12226 // loop over graphics objects
12227 for (octave_idx_type n = 0; n < hcv.numel (); n++)
12228 gh_mgr.get_object (hcv(n)).reset_default_properties ();
12229
12230 Vdrawnow_requested = true;
12231
12232 return ovl ();
12233}
12234
12235/*
12236
12237%!test # line object
12238%! hf = figure ("visible", "off");
12239%! unwind_protect
12240%! tol = 20 * eps;
12241%! hax = axes ("defaultlinelinewidth", 3);
12242%!
12243%! hli = line (1:10, 1:10, 1:10, "marker", "o",
12244%! "markerfacecolor", "b", "linestyle", ":");
12245%!
12246%! reset (hli);
12247%! assert (get (hli, "marker"), get (0, "defaultlinemarker"));
12248%! assert (get (hli, "markerfacecolor"),
12249%! get (0, "defaultlinemarkerfacecolor"));
12250%! assert (get (hli, "linestyle"), get (0, "defaultlinelinestyle"));
12251%! assert (get (hli, "linewidth"), 3, tol); # parent axes defaults
12252%!
12253%! unwind_protect_cleanup
12254%! close (hf);
12255%! end_unwind_protect
12256
12257%!test # patch object
12258%! hf = figure ("visible", "off");
12259%! unwind_protect
12260%! tol = 20 * eps;
12261%! t1 = (1/16:1/8:1)' * 2*pi;
12262%! t2 = ((1/16:1/16:1)' + 1/32) * 2*pi;
12263%! x1 = sin (t1) - 0.8;
12264%! y1 = cos (t1);
12265%! x2 = sin (t2) + 0.8;
12266%! y2 = cos (t2);
12267%! vert = [x1, y1; x2, y2];
12268%! fac = [1:8,NaN(1,8);9:24];
12269%! hpa = patch ("Faces",fac, "Vertices",vert, "FaceColor","r");
12270%!
12271%! reset (hpa);
12272%! assert (get (hpa, "faces"), get (0, "defaultpatchfaces"), tol);
12273%! assert (get (hpa, "vertices"), get (0, "defaultpatchvertices"), tol);
12274%! assert (get (hpa, "facevertexcdata"),
12275%! get (0, "defaultpatchfacevertexcdata"), tol);
12276%! unwind_protect_cleanup
12277%! close (hf);
12278%! end_unwind_protect
12279
12280%!test # surface object
12281%! hf = figure ("visible", "off");
12282%! unwind_protect
12283%! tol = 20 * eps;
12284%! hsu = surface (peaks, "edgecolor", "none");
12285%!
12286%! reset (hsu);
12287%! assert (get (hsu, "xdata"), get (0, "defaultsurfacexdata"), tol);
12288%! assert (get (hsu, "ydata"), get (0, "defaultsurfaceydata"), tol);
12289%! assert (get (hsu, "zdata"), get (0, "defaultsurfacezdata"), tol);
12290%! assert (get (hsu, "edgecolor"), get (0, "defaultsurfaceedgecolor"), tol);
12291%! unwind_protect_cleanup
12292%! close (hf);
12293%! end_unwind_protect
12294
12295%!test # image object
12296%! hf = figure ("visible", "off");
12297%! unwind_protect
12298%! tol = 20 * eps;
12299%! him = image (rand (10,10), "cdatamapping", "scaled");
12300%!
12301%! reset (him);
12302%! assert (get (him, "cdata"), get (0, "defaultimagecdata"), tol);
12303%! assert (get (him, "cdatamapping"),
12304%! get (0, "defaultimagecdatamapping"), tol);
12305%! unwind_protect_cleanup
12306%! close (hf);
12307%! end_unwind_protect
12308
12309%!test # text object
12310%! hf = figure ("visible", "off");
12311%! unwind_protect
12312%! tol = 20 * eps;
12313%! hte = text (5, 5, "Hi!", "fontsize", 20 ,"color", "r");
12314%!
12315%! reset (hte);
12316%! assert (get (hte, "position"), get (0, "defaulttextposition"), tol);
12317%! assert (get (hte, "fontsize"), get (0, "defaulttextfontsize"), tol);
12318%! assert (get (hte, "color"), get (0, "defaulttextcolor"), tol);
12319%! unwind_protect_cleanup
12320%! close (hf);
12321%! end_unwind_protect
12322
12323%!test # axes object
12324%! hf = figure ("visible", "off");
12325%! unwind_protect
12326%! tol = 20 * eps;
12327%! pos = get (0, "defaultaxesposition") * .5;
12328%! hax = axes ("linewidth", 2, "position", pos);
12329%! title ("Reset me, please!");
12330%!
12331%! reset (hax);
12332%! assert (get (hax, "linewidth"), get (0, "defaultaxeslinewidth"), tol);
12333%! assert (get (hax, "position"), pos, tol); # axes position is unchanged
12334%! assert (get (hax, "default"), struct ()); # no more axes' defaults
12335%! assert (get (get (hax, "title"), "string"), "");
12336%! unwind_protect_cleanup
12337%! close (hf);
12338%! end_unwind_protect
12339
12340%!test # figure object
12341%! set (0, "defaultfigurevisible", "off");
12342%! hf = figure ("visible", "off",
12343%! "units", "normalized",
12344%! "position", [0, 0, pi/10, e/10],
12345%! "paperunits", "normalized",
12346%! "paperposition", [0.1, 0.1, 0.9, 0.9],
12347%! "tag", "foobar");
12348%! unwind_protect
12349%! reset (hf);
12350%! ## Ordinary property is reset
12351%! assert (get (hf, "tag"), "");
12352%! ## Following 4 special properties are not reset
12353%! assert (get (hf, "units"), "normalized");
12354%! assert (get (hf, "position"), [0, 0, pi/10, e/10]);
12355%! assert (get (hf, "paperunits"), "normalized");
12356%! assert (get (hf, "paperposition"), [0.1, 0.1, 0.9, 0.9]);
12357%! unwind_protect_cleanup
12358%! close (hf);
12359%! set (0, "defaultfigurevisible", "remove");
12360%! end_unwind_protect
12361
12362*/
12363
12364DEFMETHOD (set, interp, args, nargout,
12365 doc: /* -*- texinfo -*-
12366@deftypefn {} {} set (@var{h}, @var{property}, @var{value}, @dots{})
12367@deftypefnx {} {} set (@var{h}, @{@var{properties}@}, @{@var{values}@})
12368@deftypefnx {} {} set (@var{h}, @var{pv})
12369@deftypefnx {} {@var{value_list} =} set (@var{h}, @var{property})
12370@deftypefnx {} {@var{all_value_list} =} set (@var{h})
12371Set named property values for the graphics handle (or vector of graphics
12372handles) @var{h}.
12373
12374There are three ways to give the property names and values:
12375
12376@itemize
12377@item as a comma-separated list of @var{property}, @var{value} pairs
12378
12379Each @var{property} is a string containing the property name, each @var{value}
12380is a value of the appropriate type for the property. When there are multiple
12381handles in @var{h}, each one is assigned the same @var{value}. For example:
12382
12383@example
12384@group
12385h = plot ([0, 1]);
12386set (h, 'color', 'green');
12387@end group
12388@end example
12389
12390@item as a cell array of strings @var{properties} containing property names
12391and a cell array @var{values} containing property values.
12392
12393In this case, the number of columns of @var{values} must match the number of
12394elements in @var{properties}. The first column of @var{values} contains
12395values for the first entry in @var{properties}, etc. The number of rows of
12396@var{values} must be 1 or match the number of elements of @var{h}. In the
12397first case, each handle in @var{h} will be assigned the same values. In the
12398second case, the first handle in @var{h} will be assigned the values from
12399the first row of @var{values} and so on. For example:
12400
12401@example
12402@group
12403h = plot ([0, 1; 1, 0]);
12404set (h, @{'color'@}, @{'green'; 'red'@});
12405@end group
12406@end example
12407
12408@item as a structure @var{pv}
12409
12410This is the same as the first case where the field names of @var{pv} represent
12411the property names, and the field values give the property values. As with
12412the first case, it is only possible to set one value for a property which will
12413be applied to all handles in @var{h}. For example:
12414
12415@example
12416@group
12417h = plot ([0, 1]);
12418props.color = 'green';
12419set (h, props);
12420@end group
12421@end example
12422
12423@end itemize
12424
12425The three syntaxes for setting properties may appear in any combination.
12426
12427@code{set} is also used to query the list of values a named property will
12428take. @code{@var{clist} = set (@var{h}, "property")} will return the list
12429of possible values for @qcode{"property"} in the cell list @var{clist}.
12430If no output variable is used then the list is formatted and printed to the
12431screen.
12432
12433If no property is specified (@code{@var{slist} = set (@var{h})}) then a
12434structure @var{slist} is returned where the fieldnames are the properties of
12435the object @var{h} and the fields are the list of possible values for each
12436property. If no output variable is used then the list is formatted and
12437printed to the screen.
12438
12439When querying properties only a single graphics handle @var{h} for a single
12440graphics object is permitted.
12441
12442Example Query
12443
12444@example
12445@group
12446hf = figure ();
12447set (hf, "paperorientation")
12448@result{} [ landscape | @{portrait@} ]
12449@end group
12450@end example
12451
12452@noindent
12453shows the paperorientation property can take two values with the default
12454being @qcode{"portrait"}.
12455@seealso{get}
12456@end deftypefn */)
12457{
12458 int nargin = args.length ();
12459
12460 if (nargin == 0)
12461 print_usage ();
12462
12463 // get vector of graphics handles
12464 ColumnVector hcv = args(0).xvector_value ("set: H must be a graphics handle");
12465
12466 gh_manager& gh_mgr = interp.get_gh_manager ();
12467
12468 octave::autolock guard (gh_mgr.graphics_lock ());
12469
12470 octave_value retval;
12471
12472 // Process requests for default value(s)
12473 if (nargin == 1)
12474 {
12475 if (hcv.numel () > 1)
12476 error ("set: H must be a single graphics handle when querying properties");
12477
12478 graphics_object go = gh_mgr.get_object (hcv(0));
12479 if (! go)
12480 error ("set: invalid handle (= %g)", hcv(0));
12481
12482 if (nargout > 0)
12483 retval = go.values_as_struct ();
12484 else
12485 {
12486 std::string s = go.values_as_string ();
12487
12488 octave_stdout << s;
12489 }
12490
12491 return retval;
12492 }
12493 else if (nargin == 2 && args(1).is_string ())
12494 {
12495 if (hcv.numel () > 1)
12496 error ("set: H must be a single graphics handle when querying properties");
12497
12498 std::string property = args(1).string_value ();
12499 std::transform (property.begin (), property.end (),
12500 property.begin (), tolower);
12501
12502 graphics_object go = gh_mgr.get_object (hcv(0));
12503
12504 if (! go)
12505 error ("set: invalid handle (= %g)", hcv(0));
12506
12507 octave_map pmap = go.values_as_struct ();
12508
12509 if (go.has_readonly_property (property))
12510 {
12511 if (nargout > 0)
12512 retval = Matrix ();
12513 else
12514 octave_stdout << "set: " << property << " is read-only"
12515 << std::endl;
12516 }
12517 else if (pmap.isfield (property))
12518 {
12519 if (nargout != 0)
12520 retval = pmap.getfield (property)(0);
12521 else
12522 {
12523 std::string s = go.value_as_string (property);
12524
12525 octave_stdout << s;
12526 }
12527 }
12528 else
12529 error (R"(set: unknown property "%s")", property.c_str ());
12530
12531 return retval;
12532 }
12533
12534 bool request_drawnow = false;
12535
12536 // Loop over graphics objects
12537 for (octave_idx_type n = 0; n < hcv.numel (); n++)
12538 {
12539 graphics_object go = gh_mgr.get_object (hcv(n));
12540
12541 if (! go)
12542 error ("set: invalid handle (= %g)", hcv(n));
12543
12544 // Loop over input arguments
12545 for (octave_idx_type i = 1; i < args.length (); )
12546 {
12547 if (args(i).is_string ())
12548 {
12549 if (i == nargin - 1)
12550 error ("set: PROPERTY name must be followed by a VALUE");
12551 const caseless_str pname = args(i).string_value ();
12552 const octave_value val = args(i+1);
12553 go.set_value_or_default (pname, val);
12554 i += 2;
12555 }
12556 else if (args(i).iscellstr ())
12557 {
12558 if ((i == nargin - 1) || ! args(i+1).iscell ())
12559 error ("set: cell array of PROPERTIES must be followed by cell array of VALUES");
12560 if (args(i+1).cell_value ().rows () == 1)
12561 go.set (args(i).cellstr_value (), args(i+1).cell_value (), 0);
12562 else if (hcv.numel () == args(i+1).cell_value ().rows ())
12563 go.set (args(i).cellstr_value (), args(i+1).cell_value (), n);
12564 else
12565 error ("set: number of graphics handles must match number of "
12566 "value rows (%" OCTAVE_IDX_TYPE_FORMAT " != "
12567 "%" OCTAVE_IDX_TYPE_FORMAT ")",
12568 hcv.numel (), args(i+1).cell_value ().rows ());
12569 i += 2;
12570 }
12571 else if (args(i).isstruct ())
12572 {
12573 go.set (args(i).map_value ());
12574 i += 1;
12575 }
12576 else
12577 error ("set: invalid syntax");
12578 }
12579
12580 request_drawnow = true;
12581 }
12582
12583 if (request_drawnow)
12584 Vdrawnow_requested = true;
12585
12586 return retval;
12587}
12588
12589/*
12590## test setting ticklabels for compatibility
12591%!test
12592%! hf = figure ("visible", "off");
12593%! set (gca (), "xticklabel", [0, 0.2, 0.4, 0.6, 0.8, 1]);
12594%! xticklabel = get (gca (), "xticklabel");
12595%! close (hf);
12596%! assert (class (xticklabel), "char");
12597%! assert (size (xticklabel), [6, 3]);
12598
12599%!test
12600%! hf = figure ("visible", "off");
12601%! set (gca (), "xticklabel", "0|0.2|0.4|0.6|0.8|1");
12602%! xticklabel = get (gca (), "xticklabel");
12603%! close (hf);
12604%! assert (class (xticklabel), "char");
12605%! assert (size (xticklabel), [6, 3]);
12606
12607%!test
12608%! hf = figure ("visible", "off");
12609%! set (gca (), "xticklabel", ["0 "; "0.2"; "0.4"; "0.6"; "0.8"; "1 "]);
12610%! xticklabel = get (gca (), "xticklabel");
12611%! close (hf);
12612%! assert (class (xticklabel), "char");
12613%! assert (size (xticklabel), [6, 3]);
12614
12615%!test
12616%! hf = figure ("visible", "off");
12617%! set (gca (), "xticklabel", {"0", "0.2", "0.4", "0.6", "0.8", "1"});
12618%! xticklabel = get (gca (), "xticklabel");
12619%! close (hf);
12620%! assert (class (xticklabel), "cell");
12621%! assert (size (xticklabel), [6, 1]);
12622*/
12623
12624static std::string
12625get_graphics_object_type (double val)
12626{
12627 std::string retval;
12628
12629 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
12630
12631 graphics_object go = gh_mgr.get_object (val);
12632
12633 if (! go)
12634 error ("get: invalid handle (= %g)", val);
12635
12636 return go.type ();
12637}
12638
12639DEFMETHOD (get, interp, args, ,
12640 doc: /* -*- texinfo -*-
12641@deftypefn {} {@var{val} =} get (@var{h})
12642@deftypefnx {} {@var{val} =} get (@var{h}, @var{p})
12643Return the value of the named property @var{p} from the graphics handle
12644@var{h}.
12645
12646If @var{p} is omitted, return the complete property list for @var{h}.
12647
12648If @var{h} is a vector, return a cell array including the property values or
12649lists respectively.
12650@seealso{set}
12651@end deftypefn */)
12652{
12653 gh_manager& gh_mgr = interp.get_gh_manager ();
12654
12655 octave::autolock guard (gh_mgr.graphics_lock ());
12656
12657 int nargin = args.length ();
12658
12659 if (nargin < 1 || nargin > 2)
12660 print_usage ();
12661
12662 if (args(0).isempty ())
12663 return ovl (Matrix ());
12664
12665 ColumnVector hcv = args(0).xvector_value ("get: H must be a graphics handle");
12666
12667 octave_idx_type hcv_len = hcv.numel ();
12668
12669 if (nargin == 1 && hcv_len > 1)
12670 {
12671 std::string typ0 = get_graphics_object_type (hcv(0));
12672
12673 for (octave_idx_type n = 1; n < hcv_len; n++)
12674 {
12675 std::string typ = get_graphics_object_type (hcv(n));
12676
12677 if (typ != typ0)
12678 error ("get: vector of handles must all have the same type");
12679 }
12680 }
12681
12682 octave_value retval;
12683 Cell vals;
12684 bool use_cell_format = false;
12685
12686 if (nargin > 1 && args(1).iscellstr ())
12687 {
12688 Array<std::string> plist = args(1).cellstr_value ();
12689
12690 octave_idx_type plen = plist.numel ();
12691
12692 use_cell_format = true;
12693
12694 vals.resize (dim_vector (hcv_len, plen));
12695
12696 for (octave_idx_type n = 0; n < hcv_len; n++)
12697 {
12698 graphics_object go = gh_mgr.get_object (hcv(n));
12699
12700 if (! go)
12701 error ("get: invalid handle (= %g)", hcv(n));
12702
12703 for (octave_idx_type m = 0; m < plen; m++)
12704 {
12705 caseless_str property = plist(m);
12706
12707 vals(n, m) = go.get (property);
12708 }
12709 }
12710 }
12711 else
12712 {
12713 caseless_str property;
12714
12715 if (nargin > 1)
12716 property = args(1).xstring_value ("get: second argument must be property name or cell array of property names");
12717
12718 vals.resize (dim_vector (hcv_len, 1));
12719
12720 for (octave_idx_type n = 0; n < hcv_len; n++)
12721 {
12722 graphics_object go = gh_mgr.get_object (hcv(n));
12723
12724 if (! go)
12725 error ("get: invalid handle (= %g)", hcv(n));
12726
12727 if (nargin == 1)
12728 vals(n) = go.get ();
12729 else
12730 vals(n) = go.get (property);
12731 }
12732 }
12733
12734 if (use_cell_format)
12735 retval = vals;
12736 else
12737 {
12738 octave_idx_type vals_len = vals.numel ();
12739
12740 if (vals_len == 0)
12741 retval = Matrix ();
12742 else if (vals_len == 1)
12743 retval = vals(0);
12744 else if (vals_len > 1 && nargin == 1)
12745 {
12746 OCTAVE_LOCAL_BUFFER (octave_scalar_map, tmp, vals_len);
12747
12748 for (octave_idx_type n = 0; n < vals_len; n++)
12749 tmp[n] = vals(n).scalar_map_value ();
12750
12751 retval = octave_map::cat (0, vals_len, tmp);
12752 }
12753 else
12754 retval = vals;
12755 }
12756
12757 return retval;
12758}
12759
12760/*
12761%!assert (get (findobj (0, "Tag", "nonexistenttag"), "nonexistentproperty"), [])
12762*/
12763
12764// Return all properties from the graphics handle @var{h}.
12765// If @var{h} is a vector, return a cell array including the
12766// property values or lists respectively.
12767
12768DEFMETHOD (__get__, interp, args, ,
12769 doc: /* -*- texinfo -*-
12770@deftypefn {} {@var{props} =} __get__ (@var{h})
12771Undocumented internal function.
12772@end deftypefn */)
12773{
12774 gh_manager& gh_mgr = interp.get_gh_manager ();
12775
12776 octave::autolock guard (gh_mgr.graphics_lock ());
12777
12778 if (args.length () != 1)
12779 print_usage ();
12780
12781 ColumnVector hcv = args(0).xvector_value ("get: H must be a graphics handle");
12782
12783 octave_idx_type hcv_len = hcv.numel ();
12784
12785 Cell vals (dim_vector (hcv_len, 1));
12786
12787// vals.resize (dim_vector (hcv_len, 1));
12788
12789 for (octave_idx_type n = 0; n < hcv_len; n++)
12790 {
12791 graphics_object go = gh_mgr.get_object (hcv(n));
12792
12793 if (! go)
12794 error ("get: invalid handle (= %g)", hcv(n));
12795
12796 // Disable "Octave:deprecated-property" warnings
12797 int state = toggle_warn ("Octave:deprecated-property", false);
12798
12799 vals(n) = go.get (true);
12800
12801 toggle_warn ("Octave:deprecated-property", true, state);
12802 }
12803
12804 octave_idx_type vals_len = vals.numel ();
12805
12806 if (vals_len > 1)
12807 return ovl (vals);
12808 else if (vals_len == 1)
12809 return ovl (vals(0));
12810 else
12811 return ovl ();
12812}
12813
12814static octave_value
12815make_graphics_object (const std::string& go_name,
12816 bool integer_figure_handle,
12817 const octave_value_list& args)
12818{
12819 octave_value retval;
12820
12821 double val = octave::numeric_limits<double>::NaN ();
12822
12823 octave_value_list xargs = args.splice (0, 1);
12824
12825 caseless_str p ("parent");
12826
12827 // Remove all "parent" property overrides of the first argument to function
12828 // and accept only the last one (bug #55322).
12829 for (int i = 0; i < xargs.length (); i += 2)
12830 {
12831 if (xargs(i).is_string () && p.compare (xargs(i).string_value ()))
12832 {
12833 if (i >= (xargs.length () - 1))
12834 error ("__go_%s__: missing value for parent property",
12835 go_name.c_str ());
12836
12837 val = xargs(i+1).double_value ();
12838
12839 xargs = xargs.splice (i, 2);
12840 i -= 2;
12841 }
12842 }
12843
12844 if (octave::math::isnan (val))
12845 val = args(0).xdouble_value ("__go_%s__: invalid parent", go_name.c_str ());
12846
12847 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
12848
12849 graphics_handle parent = gh_mgr.lookup (val);
12850
12851 if (! parent.ok ())
12852 error ("__go_%s__: invalid parent", go_name.c_str ());
12853
12855
12856 try
12857 {
12858 h = gh_mgr.make_graphics_handle (go_name, parent,
12859 integer_figure_handle, false, false);
12860 }
12861 catch (octave::execution_exception& ee)
12862 {
12863 error (ee, "__go_%s__: %s, unable to create graphics handle",
12864 go_name.c_str (), ee.message ().c_str ());
12865 }
12866
12867 try
12868 {
12869 xset (h, xargs);
12870 }
12871 catch (octave::execution_exception& ee)
12872 {
12874 error (ee, "__go_%s__: %s, unable to create graphics handle",
12875 go_name.c_str (), ee.message ().c_str ());
12876 }
12877
12878 adopt (parent, h);
12879
12880 xcreatefcn (h);
12881 xinitialize (h);
12882
12883 retval = h.value ();
12884
12885 Vdrawnow_requested = true;
12886
12887 return retval;
12888}
12889
12890DEFMETHOD (__go_figure__, interp, args, ,
12891 doc: /* -*- texinfo -*-
12892@deftypefn {} {@var{hfig} =} __go_figure__ (@var{fignum})
12893Undocumented internal function.
12894@end deftypefn */)
12895{
12896 gh_manager& gh_mgr = interp.get_gh_manager ();
12897
12898 octave::autolock guard (gh_mgr.graphics_lock ());
12899
12900 if (args.length () == 0)
12901 print_usage ();
12902
12903 double val = args(0).xdouble_value ("__go_figure__: figure number must be a double value");
12904
12905 octave_value retval;
12906
12907 if (isfigure (val))
12908 {
12909 graphics_handle h = gh_mgr.lookup (val);
12910
12911 xset (h, args.splice (0, 1));
12912
12913 retval = h.value ();
12914 }
12915 else
12916 {
12917 bool int_fig_handle = true;
12918
12919 octave_value_list xargs = args.splice (0, 1);
12920
12921 graphics_handle h = octave::numeric_limits<double>::NaN ();
12922
12923 if (octave::math::isnan (val))
12924 {
12925 caseless_str pname ("integerhandle");
12926
12927 for (int i = 0; i < xargs.length (); i++)
12928 {
12929 if (xargs(i).is_string ()
12930 && pname.compare (xargs(i).string_value ()))
12931 {
12932 if (i < (xargs.length () - 1))
12933 {
12934 std::string pval = xargs(i+1).string_value ();
12935
12936 caseless_str on ("on");
12937 int_fig_handle = on.compare (pval);
12938 xargs = xargs.splice (i, 2);
12939
12940 break;
12941 }
12942 }
12943 }
12944
12945 h = gh_mgr.make_graphics_handle ("figure", 0, int_fig_handle,
12946 false, false);
12947
12948 if (! int_fig_handle)
12949 {
12950 // We need to initialize the integerhandle property
12951 // without calling the set_integerhandle method,
12952 // because doing that will generate a new handle value...
12953 graphics_object go = gh_mgr.get_object (h);
12954 go.get_properties ().init_integerhandle ("off");
12955 }
12956 }
12957 else if (val > 0 && octave::math::x_nint (val) == val)
12958 h = gh_mgr.make_figure_handle (val, false);
12959
12960 if (! h.ok ())
12961 error ("__go_figure__: failed to create figure handle");
12962
12963 try
12964 {
12965 xset (h, xargs);
12966 }
12967 catch (octave::execution_exception& ee)
12968 {
12970 error (ee, "__go_figure__: unable to create figure handle");
12971 }
12972
12973 adopt (0, h);
12974
12975 gh_mgr.push_figure (h);
12976
12977 xcreatefcn (h);
12978 xinitialize (h);
12979
12980 retval = h.value ();
12981 }
12982
12983 return retval;
12984}
12985
12986#define GO_BODY(TYPE) \
12987 gh_manager& gh_mgr = interp.get_gh_manager (); \
12988 \
12989 octave::autolock guard (gh_mgr.graphics_lock ()); \
12990 \
12991 if (args.length () == 0) \
12992 print_usage (); \
12993 \
12994 return octave_value (make_graphics_object (#TYPE, false, args)); \
12995
12996int
12997calc_dimensions (const graphics_object& go)
12998{
12999 int nd = 2;
13000
13001 if (go.isa ("surface"))
13002 nd = 3;
13003 else if ((go.isa ("line") || go.isa ("patch") || go.isa ("scatter"))
13004 && ! go.get ("zdata").isempty ())
13005 nd = 3;
13006 else
13007 {
13008 Matrix kids = go.get_properties ().get_children ();
13009
13010 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
13011
13012 for (octave_idx_type i = 0; i < kids.numel (); i++)
13013 {
13014 graphics_handle hkid = gh_mgr.lookup (kids(i));
13015
13016 if (hkid.ok ())
13017 {
13018 const graphics_object& kid = gh_mgr.get_object (hkid);
13019
13020 if (kid.valid_object ())
13021 nd = calc_dimensions (kid);
13022
13023 if (nd == 3)
13024 break;
13025 }
13026 }
13027 }
13028
13029 return nd;
13030}
13031
13032DEFMETHOD (__calc_dimensions__, interp, args, ,
13033 doc: /* -*- texinfo -*-
13034@deftypefn {} {@var{ndims} =} __calc_dimensions__ (@var{axes})
13035Internal function.
13036
13037Determine the number of dimensions in a graphics object, either 2 or 3.
13038@end deftypefn */)
13039{
13040 gh_manager& gh_mgr = interp.get_gh_manager ();
13041
13042 octave::autolock guard (gh_mgr.graphics_lock ());
13043
13044 if (args.length () != 1)
13045 print_usage ();
13046
13047 double h = args(0).xdouble_value ("__calc_dimensions__: first argument must be a graphics handle");
13048
13049 return ovl (calc_dimensions (gh_mgr.get_object (h)));
13050}
13051
13052DEFMETHOD (__go_axes__, interp, args, ,
13053 doc: /* -*- texinfo -*-
13054@deftypefn {} {@var{hax} =} __go_axes__ (@var{parent})
13055Undocumented internal function.
13056@end deftypefn */)
13057{
13058 GO_BODY (axes);
13059}
13060
13061DEFMETHOD (__go_line__, interp, args, ,
13062 doc: /* -*- texinfo -*-
13063@deftypefn {} {@var{hl} =} __go_line__ (@var{parent})
13064Undocumented internal function.
13065@end deftypefn */)
13066{
13067 GO_BODY (line);
13068}
13069
13070DEFMETHOD (__go_text__, interp, args, ,
13071 doc: /* -*- texinfo -*-
13072@deftypefn {} {@var{ht} =} __go_text__ (@var{parent})
13073Undocumented internal function.
13074@end deftypefn */)
13075{
13076 GO_BODY (text);
13077}
13078
13079DEFMETHOD (__go_image__, interp, args, ,
13080 doc: /* -*- texinfo -*-
13081@deftypefn {} {@var{hi} =} __go_image__ (@var{parent})
13082Undocumented internal function.
13083@end deftypefn */)
13084{
13085 GO_BODY (image);
13086}
13087
13088DEFMETHOD (__go_surface__, interp, args, ,
13089 doc: /* -*- texinfo -*-
13090@deftypefn {} {@var{hs} =} __go_surface__ (@var{parent})
13091Undocumented internal function.
13092@end deftypefn */)
13093{
13094 GO_BODY (surface);
13095}
13096
13097DEFMETHOD (__go_patch__, interp, args, ,
13098 doc: /* -*- texinfo -*-
13099@deftypefn {} {@var{hp} =} __go_patch__ (@var{parent})
13100Undocumented internal function.
13101@end deftypefn */)
13102{
13103 GO_BODY (patch);
13104}
13105
13106DEFMETHOD (__go_scatter__, interp, args, ,
13107 doc: /* -*- texinfo -*-
13108@deftypefn {} {@var{hs} =} __go_scatter__ (@var{parent})
13109Undocumented internal function.
13110@end deftypefn */)
13111{
13112 GO_BODY (scatter);
13113}
13114
13115DEFMETHOD (__go_light__, interp, args, ,
13116 doc: /* -*- texinfo -*-
13117@deftypefn {} {@var{hl} =} __go_light__ (@var{parent})
13118Undocumented internal function.
13119@end deftypefn */)
13120{
13121 GO_BODY (light);
13122}
13123
13124DEFMETHOD (__go_hggroup__, interp, args, ,
13125 doc: /* -*- texinfo -*-
13126@deftypefn {} {@var{hgg} =} __go_hggroup__ (@var{parent})
13127Undocumented internal function.
13128@end deftypefn */)
13129{
13130 GO_BODY (hggroup);
13131}
13132
13133DEFMETHOD (__go_uimenu__, interp, args, ,
13134 doc: /* -*- texinfo -*-
13135@deftypefn {} {@var{hui} =} __go_uimenu__ (@var{parent})
13136Undocumented internal function.
13137@end deftypefn */)
13138{
13139 GO_BODY (uimenu);
13140}
13141
13142DEFMETHOD (__go_uicontrol__, interp, args, ,
13143 doc: /* -*- texinfo -*-
13144@deftypefn {} {@var{hui} =} __go_uicontrol__ (@var{parent})
13145Undocumented internal function.
13146@end deftypefn */)
13147{
13148 GO_BODY (uicontrol);
13149}
13150
13151DEFMETHOD (__go_uibuttongroup__, interp, args, ,
13152 doc: /* -*- texinfo -*-
13153@deftypefn {} {@var{hui} =} __go_uibuttongroup__ (@var{parent})
13154Undocumented internal function.
13155@end deftypefn */)
13156{
13157 GO_BODY (uibuttongroup);
13158}
13159
13160DEFMETHOD (__go_uipanel__, interp, args, ,
13161 doc: /* -*- texinfo -*-
13162@deftypefn {} {@var{hui} =} __go_uipanel__ (@var{parent})
13163Undocumented internal function.
13164@end deftypefn */)
13165{
13166 GO_BODY (uipanel);
13167}
13168
13169DEFMETHOD (__go_uicontextmenu__, interp, args, ,
13170 doc: /* -*- texinfo -*-
13171@deftypefn {} {@var{hui} =} __go_uicontextmenu__ (@var{parent})
13172Undocumented internal function.
13173@end deftypefn */)
13174{
13175 GO_BODY (uicontextmenu);
13176}
13177
13178DEFMETHOD (__go_uitable__, interp, args, ,
13179 doc: /* -*- texinfo -*-
13180@deftypefn {} {@var{hui} =} __go_uitable__ (@var{parent})
13181Undocumented internal function.
13182@end deftypefn */)
13183{
13184 GO_BODY (uitable);
13185}
13186
13187DEFMETHOD (__go_uitoolbar__, interp, args, ,
13188 doc: /* -*- texinfo -*-
13189@deftypefn {} {@var{hui} =} __go_uitoolbar__ (@var{parent})
13190Undocumented internal function.
13191@end deftypefn */)
13192{
13193 GO_BODY (uitoolbar);
13194}
13195
13196DEFMETHOD (__go_uipushtool__, interp, args, ,
13197 doc: /* -*- texinfo -*-
13198@deftypefn {} {@var{hui} =} __go_uipushtool__ (@var{parent})
13199Undocumented internal function.
13200@end deftypefn */)
13201{
13202 GO_BODY (uipushtool);
13203}
13204
13205DEFMETHOD (__go_uitoggletool__, interp, args, ,
13206 doc: /* -*- texinfo -*-
13207@deftypefn {} {@var{hui} =} __go_uitoggletool__ (@var{parent})
13208Undocumented internal function.
13209@end deftypefn */)
13210{
13211 GO_BODY (uitoggletool);
13212}
13213
13214DEFMETHOD (__go_delete__, interp, args, ,
13215 doc: /* -*- texinfo -*-
13216@deftypefn {} {} __go_delete__ (@var{h})
13217Undocumented internal function.
13218@end deftypefn */)
13219{
13220 gh_manager& gh_mgr = interp.get_gh_manager ();
13221
13222 octave::autolock guard (gh_mgr.graphics_lock ());
13223
13224 if (args.length () != 1)
13225 print_usage ();
13226
13227 graphics_handle h = octave::numeric_limits<double>::NaN ();
13228
13229 const NDArray vals = args(0).xarray_value ("delete: invalid graphics object");
13230
13231 // Check all the handles to delete are valid first,
13232 // as callbacks might delete one of the handles we later want to delete.
13233 for (octave_idx_type i = 0; i < vals.numel (); i++)
13234 {
13235 h = gh_mgr.lookup (vals(i));
13236
13237 if (! h.ok ())
13238 error ("delete: invalid graphics object (= %g)", vals(i));
13239 }
13240
13242
13243 return ovl ();
13244}
13245
13246DEFMETHOD (__go_handles__, interp, args, ,
13247 doc: /* -*- texinfo -*-
13248@deftypefn {} {@var{hg_list} =} __go_handles__ (@var{show_hidden})
13249Undocumented internal function.
13250@end deftypefn */)
13251{
13252 gh_manager& gh_mgr = interp.get_gh_manager ();
13253
13254 octave::autolock guard (gh_mgr.graphics_lock ());
13255
13256 bool show_hidden = false;
13257
13258 if (args.length () > 0)
13259 show_hidden = args(0).bool_value ();
13260
13261 return ovl (gh_mgr.handle_list (show_hidden));
13262}
13263
13264DEFMETHOD (__go_figure_handles__, interp, args, ,
13265 doc: /* -*- texinfo -*-
13266@deftypefn {} {@var{hfig_list} =} __go_figure_handles__ (@var{show_hidden})
13267Undocumented internal function.
13268@end deftypefn */)
13269{
13270 gh_manager& gh_mgr = interp.get_gh_manager ();
13271
13272 octave::autolock guard (gh_mgr.graphics_lock ());
13273
13274 bool show_hidden = false;
13275
13276 if (args.length () > 0)
13277 show_hidden = args(0).bool_value ();
13278
13279 return ovl (gh_mgr.figure_handle_list (show_hidden));
13280}
13281
13282DEFMETHOD (__go_execute_callback__, interp, args, ,
13283 doc: /* -*- texinfo -*-
13284@deftypefn {} {} __go_execute_callback__ (@var{h}, @var{name})
13285@deftypefnx {} {} __go_execute_callback__ (@var{h}, @var{name}, @var{param})
13286Undocumented internal function.
13287@end deftypefn */)
13288{
13289 int nargin = args.length ();
13290
13291 if (nargin < 2 || nargin > 3)
13292 print_usage ();
13293
13294 const NDArray vals = args(0).xarray_value ("__go_execute_callback__: invalid graphics object");
13295
13296 std::string name = args(1).xstring_value ("__go_execute_callback__: invalid callback name");
13297
13298 gh_manager& gh_mgr = interp.get_gh_manager ();
13299
13300 for (octave_idx_type i = 0; i < vals.numel (); i++)
13301 {
13302 double val = vals(i);
13303
13304 graphics_handle h = gh_mgr.lookup (val);
13305
13306 if (! h.ok ())
13307 error ("__go_execute_callback__: invalid graphics object (= %g)", val);
13308
13309 if (nargin == 2)
13310 gh_mgr.execute_callback (h, name);
13311 else
13312 gh_mgr.execute_callback (h, name, args(2));
13313 }
13314
13315 return ovl ();
13316}
13317
13318DEFMETHOD (__go_post_callback__, interp, args, ,
13319 doc: /* -*- texinfo -*-
13320@deftypefn {} {} __go_post_callback__ (@var{h}, @var{name})
13321@deftypefnx {} {} __go_post_callback__ (@var{h}, @var{name}, @var{param})
13322Undocumented internal function.
13323@end deftypefn */)
13324{
13325 int nargin = args.length ();
13326
13327 if (nargin < 2 || nargin > 3)
13328 print_usage ();
13329
13330 const NDArray vals = args(0).xarray_value ("__go_post_callback__: invalid graphics object");
13331
13332 std::string name = args(1).xstring_value ("__go_post_callback__: invalid callback name");
13333
13334 gh_manager& gh_mgr = interp.get_gh_manager ();
13335
13336 for (octave_idx_type i = 0; i < vals.numel (); i++)
13337 {
13338 double val = vals(i);
13339
13340 graphics_handle h = gh_mgr.lookup (val);
13341
13342 if (! h.ok ())
13343 error ("__go_execute_callback__: invalid graphics object (= %g)", val);
13344
13345 if (nargin == 2)
13346 gh_mgr.post_callback (h, name);
13347 else
13348 gh_mgr.post_callback (h, name, args(2));
13349 }
13350
13351 return ovl ();
13352}
13353
13354DEFMETHOD (__image_pixel_size__, interp, args, ,
13355 doc: /* -*- texinfo -*-
13356@deftypefn {} {@var{sz} =} __image_pixel_size__ (@var{h})
13357Internal function: returns the pixel size of the image in normalized units.
13358@end deftypefn */)
13359{
13360 if (args.length () != 1)
13361 print_usage ();
13362
13363 gh_manager& gh_mgr = interp.get_gh_manager ();
13364
13365 double h = args(0).xdouble_value ("__image_pixel_size__: argument is not a handle");
13366
13367 graphics_object go = gh_mgr.get_object (h);
13368
13369 if (! go || ! go.isa ("image"))
13370 error ("__image_pixel_size__: object is not an image");
13371
13372 image::properties& ip
13373 = dynamic_cast<image::properties&> (go.get_properties ());
13374
13375 Matrix dp = Matrix (1, 2);
13376 dp(0) = ip.pixel_xsize ();
13377 dp(1) = ip.pixel_ysize ();
13378 return ovl (dp);
13379}
13380
13381DEFMETHOD (available_graphics_toolkits, interp, , ,
13382 doc: /* -*- texinfo -*-
13383@deftypefn {} {@var{toolkits} =} available_graphics_toolkits ()
13384Return a cell array of registered graphics toolkits.
13385@seealso{graphics_toolkit, register_graphics_toolkit}
13386@end deftypefn */)
13387{
13388 gh_manager& gh_mgr = interp.get_gh_manager ();
13389
13390 octave::autolock guard (gh_mgr.graphics_lock ());
13391
13392 octave::gtk_manager& gtk_mgr = interp.get_gtk_manager ();
13393
13394 return ovl (gtk_mgr.available_toolkits_list ());
13395}
13396
13397DEFMETHOD (register_graphics_toolkit, interp, args, ,
13398 doc: /* -*- texinfo -*-
13399@deftypefn {} {} register_graphics_toolkit ("@var{toolkit}")
13400List @var{toolkit} as an available graphics toolkit.
13401
13402Programming Note: No input validation is done on the input string; it is simply
13403added to the list of possible graphics toolkits.
13404@seealso{available_graphics_toolkits}
13405@end deftypefn */)
13406{
13407 gh_manager& gh_mgr = interp.get_gh_manager ();
13408
13409 octave::autolock guard (gh_mgr.graphics_lock ());
13410
13411 if (args.length () != 1)
13412 print_usage ();
13413
13414 std::string name = args(0).xstring_value ("register_graphics_toolkit: TOOLKIT must be a string");
13415
13416 octave::gtk_manager& gtk_mgr = interp.get_gtk_manager ();
13417
13418 gtk_mgr.register_toolkit (name);
13419
13420 return ovl ();
13421}
13422
13423DEFMETHOD (loaded_graphics_toolkits, interp, , ,
13424 doc: /* -*- texinfo -*-
13425@deftypefn {} {@var{toolkits} =} loaded_graphics_toolkits ()
13426Return a cell array of the currently loaded graphics toolkits.
13427@seealso{available_graphics_toolkits}
13428@end deftypefn */)
13429{
13430 gh_manager& gh_mgr = interp.get_gh_manager ();
13431
13432 octave::autolock guard (gh_mgr.graphics_lock ());
13433
13434 octave::gtk_manager& gtk_mgr = interp.get_gtk_manager ();
13435
13436 return ovl (gtk_mgr.loaded_toolkits_list ());
13437}
13438
13439DEFMETHOD (__show_figure__, interp, args, ,
13440 doc: /* -*- texinfo -*-
13441@deftypefn {} {} __show_figure__ (@var{n})
13442Undocumented internal function.
13443@end deftypefn */)
13444{
13445 if (args.length () != 1)
13446 print_usage ();
13447
13448 gh_manager& gh_mgr = interp.get_gh_manager ();
13449
13450 double h = args(0).xdouble_value ("__show_figure__: invalid handle H");
13451
13452 graphics_handle gh = gh_mgr.lookup (h);
13453
13454 if (! gh.ok ())
13455 error ("__show_figure__: invalid graphics object (= %g)", h);
13456
13457 graphics_object go = gh_mgr.get_object (gh);
13458
13459 figure::properties& fprops
13460 = dynamic_cast<figure::properties&> (go.get_properties ());
13461
13462 fprops.get_toolkit ().show_figure (go);
13463
13464 return ovl ();
13465}
13466
13467DEFMETHOD (drawnow, interp, args, ,
13468 doc: /* -*- texinfo -*-
13469@deftypefn {} {} drawnow ()
13470@deftypefnx {} {} drawnow ("expose")
13471@deftypefnx {} {} drawnow (@var{term}, @var{file}, @var{debug_file})
13472Update figure windows and their children.
13473
13474The event queue is flushed and any callbacks generated are executed.
13475
13476With the optional argument @qcode{"expose"}, only graphic objects are
13477updated and no other events or callbacks are processed.
13478
13479The third calling form of @code{drawnow} is for debugging and is
13480undocumented.
13481@seealso{refresh}
13482@end deftypefn */)
13483{
13484 if (args.length () > 3)
13485 print_usage ();
13486
13487 octave::unwind_protect_var<bool> restore_var (Vdrawnow_requested, false);
13488
13489 // Redraw unless we are in the middle of a deletion.
13490
13491 if (! delete_executing)
13492 {
13493 gh_manager& gh_mgr = interp.get_gh_manager ();
13494
13495 octave::autolock guard (gh_mgr.graphics_lock ());
13496
13497 if (args.length () <= 1)
13498 {
13499 // First process events so that the redraw happens when all
13500 // objects are in their definite state.
13501 bool do_events = true;
13502
13503 if (args.length () == 1)
13504 {
13505 caseless_str val (args(0).xstring_value ("drawnow: first argument must be a string"));
13506
13507 if (val.compare ("expose"))
13508 do_events = false;
13509 else
13510 error ("drawnow: invalid argument, 'expose' is only valid option");
13511 }
13512
13513 if (do_events)
13514 {
13515 gh_mgr.unlock ();
13516
13517 gh_mgr.process_events ();
13518
13519 gh_mgr.lock ();
13520 }
13521
13522 Matrix hlist = gh_mgr.figure_handle_list (true);
13523
13524 // Redraw modified figures
13525 for (int i = 0; i < hlist.numel (); i++)
13526 {
13527 graphics_handle h = gh_mgr.lookup (hlist(i));
13528
13529 if (h.ok () && h != 0)
13530 {
13531 graphics_object go = gh_mgr.get_object (h);
13532 figure::properties& fprops
13533 = dynamic_cast<figure::properties&> (go.get_properties ());
13534
13535 if (fprops.is_modified ())
13536 {
13537 if (fprops.is_visible ())
13538 {
13539 gh_mgr.unlock ();
13540
13541 fprops.get_toolkit ().redraw_figure (go);
13542
13543 gh_mgr.lock ();
13544 }
13545
13546 fprops.set_modified (false);
13547 }
13548 }
13549
13550 }
13551 }
13552 else if (args.length () >= 2 && args.length () <= 3)
13553 {
13554 std::string term, file, debug_file;
13555
13556 term = args(0).xstring_value ("drawnow: TERM must be a string");
13557
13558 file = args(1).xstring_value ("drawnow: FILE must be a string");
13559
13560 if (file.empty ())
13561 error ("drawnow: empty output ''");
13562 else if (file.length () == 1 && file[0] == '|')
13563 error ("drawnow: empty pipe '|'");
13564 else if (file[0] != '|')
13565 {
13566 std::size_t pos = file.find_last_of (octave::sys::file_ops::dir_sep_chars ());
13567
13568 if (pos != std::string::npos)
13569 {
13570 std::string dirname = file.substr (0, pos+1);
13571
13572 if (! octave::sys::dir_exists (dirname))
13573 error ("drawnow: nonexistent directory '%s'",
13574 dirname.c_str ());
13575
13576 }
13577 }
13578
13579 debug_file = (args.length () > 2 ? args(2).xstring_value ("drawnow: DEBUG_FILE must be a string") :
13580 "");
13581
13582 graphics_handle h = gcf ();
13583
13584 if (! h.ok ())
13585 error ("drawnow: nothing to draw");
13586
13587 graphics_object go = gh_mgr.get_object (h);
13588
13589 gh_mgr.unlock ();
13590
13591 go.get_toolkit ().print_figure (go, term, file, debug_file);
13592
13593 gh_mgr.lock ();
13594 }
13595 }
13596
13597 return ovl ();
13598}
13599
13600DEFMETHOD (addlistener, interp, args, ,
13601 doc: /* -*- texinfo -*-
13602@deftypefn {} {} addlistener (@var{h}, @var{prop}, @var{fcn})
13603Register @var{fcn} as listener for the property @var{prop} of the graphics
13604object @var{h}.
13605
13606Property listeners are executed (in order of registration) when the property
13607is set. The new value is already available when the listeners are executed.
13608
13609@var{prop} must be a string naming a valid property in @var{h}.
13610
13611@var{fcn} can be a function handle, a string or a cell array whose first
13612element is a function handle. If @var{fcn} is a function handle, the
13613corresponding function should accept at least 2 arguments, that will be
13614set to the object handle and the empty matrix respectively. If @var{fcn}
13615is a string, it must be any valid octave expression. If @var{fcn} is a cell
13616array, the first element must be a function handle with the same signature
13617as described above. The next elements of the cell array are passed
13618as additional arguments to the function.
13619
13620Example:
13621
13622@example
13623@group
13624function my_listener (h, dummy, p1)
13625 fprintf ("my_listener called with p1=%s\n", p1);
13626endfunction
13627
13628addlistener (gcf, "position", @{@@my_listener, "my string"@})
13629@end group
13630@end example
13631
13632@seealso{dellistener, addproperty, hggroup}
13633@end deftypefn */)
13634{
13635 gh_manager& gh_mgr = interp.get_gh_manager ();
13636
13637 octave::autolock guard (gh_mgr.graphics_lock ());
13638
13639 int nargin = args.length ();
13640
13641 if (nargin < 3 || nargin > 4)
13642 print_usage ();
13643
13644 double h = args(0).xdouble_value ("addlistener: invalid handle H");
13645
13646 std::string pname = args(1).xstring_value ("addlistener: PROP must be a string");
13647
13648 graphics_handle gh = gh_mgr.lookup (h);
13649
13650 if (! gh.ok ())
13651 error ("addlistener: invalid graphics object (= %g)", h);
13652
13653 graphics_object go = gh_mgr.get_object (gh);
13654
13655 go.add_property_listener (pname, args(2), GCB_POSTSET);
13656
13657 if (args.length () == 4)
13658 {
13659 caseless_str persistent = args(3).string_value ();
13660 if (persistent.compare ("persistent"))
13661 go.add_property_listener (pname, args(2), GCB_PERSISTENT);
13662 }
13663
13664 return ovl ();
13665}
13666
13667DEFMETHOD (dellistener, interp, args, ,
13668 doc: /* -*- texinfo -*-
13669@deftypefn {} {} dellistener (@var{h}, @var{prop}, @var{fcn})
13670Remove the registration of @var{fcn} as a listener for the property
13671@var{prop} of the graphics object @var{h}.
13672
13673The function @var{fcn} must be the same variable (not just the same value),
13674as was passed to the original call to @code{addlistener}.
13675
13676If @var{fcn} is not defined then all listener functions of @var{prop}
13677are removed.
13678
13679Example:
13680
13681@example
13682@group
13683function my_listener (h, dummy, p1)
13684 fprintf ("my_listener called with p1=%s\n", p1);
13685endfunction
13686
13687c = @{@@my_listener, "my string"@};
13688addlistener (gcf, "position", c);
13689dellistener (gcf, "position", c);
13690@end group
13691@end example
13692
13693@seealso{addlistener}
13694@end deftypefn */)
13695{
13696 gh_manager& gh_mgr = interp.get_gh_manager ();
13697
13698 octave::autolock guard (gh_mgr.graphics_lock ());
13699
13700 if (args.length () < 2 || args.length () > 3)
13701 print_usage ();
13702
13703 double h = args(0).xdouble_value ("dellistener: invalid handle");
13704
13705 std::string pname = args(1).xstring_value ("dellistener: PROP must be a string");
13706
13707 graphics_handle gh = gh_mgr.lookup (h);
13708
13709 if (! gh.ok ())
13710 error ("dellistener: invalid graphics object (= %g)", h);
13711
13712 graphics_object go = gh_mgr.get_object (gh);
13713
13714 if (args.length () == 2)
13715 go.delete_property_listener (pname, octave_value (), GCB_POSTSET);
13716 else
13717 {
13718 if (args(2).is_string ()
13719 && args(2).string_value () == "persistent")
13720 {
13721 go.delete_property_listener (pname, octave_value (),
13722 GCB_PERSISTENT);
13723 go.delete_property_listener (pname, octave_value (),
13724 GCB_POSTSET);
13725 }
13726 else
13727 go.delete_property_listener (pname, args(2), GCB_POSTSET);
13728 }
13729
13730 return ovl ();
13731}
13732
13733DEFMETHOD (addproperty, interp, args, ,
13734 doc: /* -*- texinfo -*-
13735@deftypefn {} {} addproperty (@var{name}, @var{h}, @var{type})
13736@deftypefnx {} {} addproperty (@var{name}, @var{h}, @var{type}, @var{arg}, @dots{})
13737Create a new property named @var{name} in graphics object @var{h}.
13738
13739@var{type} determines the type of the property to create. @var{args}
13740usually contains the default value of the property, but additional
13741arguments might be given, depending on the type of the property.
13742
13743The supported property types are:
13744
13745@table @code
13746@item string
13747A string property. @var{arg} contains the default string value.
13748
13749@item any
13750An @nospell{un-typed} property. This kind of property can hold any octave
13751value. @var{args} contains the default value.
13752
13753@item radio
13754A string property with a limited set of accepted values. The first
13755argument must be a string with all accepted values separated by
13756a vertical bar ('|'). The default value can be marked by enclosing
13757it with a '@{' '@}' pair. The default value may also be given as
13758an optional second string argument.
13759
13760@item boolean
13761A boolean property. This property type is equivalent to a radio
13762property with "on|off" as accepted values. @var{arg} contains
13763the default property value.
13764
13765@item double
13766A scalar double property. @var{arg} contains the default value.
13767
13768@item handle
13769A handle property. This kind of property holds the handle of a
13770graphics object. @var{arg} contains the default handle value.
13771When no default value is given, the property is initialized to
13772the empty matrix.
13773
13774@item data
13775A data (matrix) property. @var{arg} contains the default data
13776value. When no default value is given, the data is initialized to
13777the empty matrix.
13778
13779@item color
13780A color property. @var{arg} contains the default color value.
13781When no default color is given, the property is set to black.
13782An optional second string argument may be given to specify an
13783additional set of accepted string values (like a radio property).
13784@end table
13785
13786@var{type} may also be the concatenation of a core object type and
13787a valid property name for that object type. The property created
13788then has the same characteristics as the referenced property (type,
13789possible values, hidden state@dots{}). This allows one to clone an
13790existing property into the graphics object @var{h}.
13791
13792Examples:
13793
13794@example
13795@group
13796addproperty ("my_property", gcf, "string", "a string value");
13797addproperty ("my_radio", gcf, "radio", "val_1|val_2|@{val_3@}");
13798addproperty ("my_style", gcf, "linelinestyle", "--");
13799@end group
13800@end example
13801
13802@seealso{addlistener, hggroup}
13803@end deftypefn */)
13804{
13805 gh_manager& gh_mgr = interp.get_gh_manager ();
13806
13807 octave::autolock guard (gh_mgr.graphics_lock ());
13808
13809 if (args.length () < 3)
13810 print_usage ();
13811
13812 std::string name = args(0).xstring_value ("addproperty: NAME must be a string");
13813
13814 double h = args(1).xdouble_value ("addproperty: invalid handle H");
13815
13816 graphics_handle gh = gh_mgr.lookup (h);
13817
13818 if (! gh.ok ())
13819 error ("addproperty: invalid graphics object (= %g)", h);
13820
13821 graphics_object go = gh_mgr.get_object (gh);
13822
13823 std::string type = args(2).xstring_value ("addproperty: TYPE must be a string");
13824
13825 if (go.get_properties ().has_property (name))
13826 error ("addproperty: a '%s' property already exists in the graphics object",
13827 name.c_str ());
13828
13829 property p = property::create (name, gh, type, args.splice (0, 3));
13830
13831 go.get_properties ().insert_property (name, p);
13832
13833 return ovl ();
13834}
13835
13837get_property_from_handle (double handle, const std::string& property,
13838 const std::string& fcn)
13839{
13840 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
13841
13842 octave::autolock guard (gh_mgr.graphics_lock ());
13843
13844 graphics_object go = gh_mgr.get_object (handle);
13845
13846 if (! go)
13847 error ("%s: invalid handle (= %g)", fcn.c_str (), handle);
13848
13849 return go.get (caseless_str (property));
13850}
13851
13852bool
13853set_property_in_handle (double handle, const std::string& property,
13854 const octave_value& arg, const std::string& fcn)
13855{
13856 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
13857
13858 octave::autolock guard (gh_mgr.graphics_lock ());
13859
13860 graphics_object go = gh_mgr.get_object (handle);
13861
13862 if (! go)
13863 error ("%s: invalid handle (= %g)", fcn.c_str (), handle);
13864
13865 go.set (caseless_str (property), arg);
13866
13867 return true;
13868}
13869
13870static bool
13871compare_property_values (octave::interpreter& interp,
13872 const octave_value& ov1, const octave_value& ov2)
13873{
13874 octave_value_list args(2);
13875
13876 args(0) = ov1;
13877 args(1) = ov2;
13878
13879 octave_value_list result = interp.feval ("isequal", args, 1);
13880
13881 if (result.length () > 0)
13882 return result(0).bool_value ();
13883
13884 return false;
13885}
13886
13887static std::map<uint32_t, bool> waitfor_results;
13888
13889static void
13890cleanup_waitfor_id (uint32_t id)
13891{
13892 waitfor_results.erase (id);
13893}
13894
13895static void
13896do_cleanup_waitfor_listener (const octave_value& listener,
13897 listener_mode mode = GCB_POSTSET)
13898{
13899 Cell c = listener.cell_value ();
13900
13901 if (c.numel () >= 4)
13902 {
13903 double h = c(2).double_value ();
13904
13905 caseless_str pname = c(3).string_value ();
13906
13907 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
13908
13909 octave::autolock guard (gh_mgr.graphics_lock ());
13910
13911 graphics_handle gh = gh_mgr.lookup (h);
13912
13913 if (gh.ok ())
13914 {
13915 graphics_object go = gh_mgr.get_object (gh);
13916
13917 if (go.get_properties ().has_property (pname))
13918 {
13919 go.get_properties ().delete_listener (pname, listener, mode);
13920
13921 if (mode == GCB_POSTSET)
13922 go.get_properties ().delete_listener (pname, listener,
13923 GCB_PERSISTENT);
13924 }
13925 }
13926 }
13927}
13928
13929static void
13930cleanup_waitfor_postset_listener (const octave_value& listener)
13931{ do_cleanup_waitfor_listener (listener, GCB_POSTSET); }
13932
13933static void
13934cleanup_waitfor_predelete_listener (const octave_value& listener)
13935{ do_cleanup_waitfor_listener (listener, GCB_PREDELETE); }
13936
13937DECLARE_STATIC_METHODX (waitfor_listener, interp, args, )
13938{
13939 if (args.length () > 3)
13940 {
13941 uint32_t id = args(2).uint32_scalar_value ().value ();
13942
13943 if (args.length () > 5)
13944 {
13945 double h = args(0).double_value ();
13946
13947 caseless_str pname = args(4).string_value ();
13948
13949 gh_manager& gh_mgr = octave::__get_gh_manager__ ();
13950
13951 octave::autolock guard (gh_mgr.graphics_lock ());
13952
13953 graphics_handle gh = gh_mgr.lookup (h);
13954
13955 if (gh.ok ())
13956 {
13957 graphics_object go = gh_mgr.get_object (gh);
13958 octave_value pvalue = go.get (pname);
13959
13960 if (compare_property_values (interp, pvalue, args(5)))
13961 waitfor_results[id] = true;
13962 }
13963 }
13964 else
13965 waitfor_results[id] = true;
13966 }
13967
13968 return ovl ();
13969}
13970
13971DECLARE_STATIC_FUNX (waitfor_del_listener, args, )
13972{
13973 if (args.length () > 2)
13974 {
13975 uint32_t id = args(2).uint32_scalar_value ().value ();
13976
13977 waitfor_results[id] = true;
13978 }
13979
13980 return ovl ();
13981}
13982
13983DEFMETHOD (waitfor, interp, args, ,
13984 doc: /* -*- texinfo -*-
13985@deftypefn {} {} waitfor (@var{h})
13986@deftypefnx {} {} waitfor (@var{h}, @var{prop})
13987@deftypefnx {} {} waitfor (@var{h}, @var{prop}, @var{value})
13988@deftypefnx {} {} waitfor (@dots{}, "timeout", @var{timeout})
13989Suspend the execution of the current program until a condition is
13990satisfied on the graphics handle @var{h}.
13991
13992While the program is suspended graphics events are still processed normally,
13993allowing callbacks to modify the state of graphics objects. This function
13994is reentrant and can be called from a callback, while another @code{waitfor}
13995call is pending at the top-level.
13996
13997In the first form, program execution is suspended until the graphics object
13998@var{h} is destroyed. If the graphics handle is invalid or if @var{h} is
13999the root graphics handle and no property @var{prop} was provided, the function
14000returns immediately.
14001
14002In the second form, execution is suspended until the graphics object is
14003destroyed or the property named @var{prop} is modified. If the graphics
14004handle is invalid or the property does not exist, the function returns
14005immediately.
14006
14007In the third form, execution is suspended until the graphics object is
14008destroyed or the property named @var{prop} is set to @var{value}. The
14009function @code{isequal} is used to compare property values. If the graphics
14010handle is invalid, the property does not exist or the property is already
14011set to @var{value}, the function returns immediately.
14012
14013An optional timeout can be specified using the property @qcode{"timeout"}.
14014This timeout value is the number of seconds to wait for the condition to be
14015true. @var{timeout} must be at least 1. If a smaller value is specified, a
14016warning is issued and a value of 1 is used instead. If the timeout value is
14017not an integer, it is truncated towards 0.
14018
14019To define a condition on a property named @qcode{"timeout"}, use the string
14020@qcode{'@backslashchar{}timeout'} instead.
14021
14022In all cases, typing CTRL-C stops program execution immediately.
14023@seealso{waitforbuttonpress, isequal}
14024@end deftypefn */)
14025{
14026 if (args.length () == 0)
14027 print_usage ();
14028
14029 // return immediately if the graphics handle is invalid
14030 if (args(0).isempty ())
14031 return ovl ();
14032
14033 double h = args(0).xdouble_value ("waitfor: invalid handle value");
14034
14035 if (! ishghandle (h) || (h == 0 && args.length () == 1))
14036 return ovl ();
14037
14038 caseless_str pname;
14039
14040 octave::unwind_action cleanup_waitfor_id_action;
14041 octave::unwind_action cleanup_waitfor_postset_listener_action;
14042 octave::unwind_action cleanup_waitfor_predelete_listener_action;
14043
14044 static uint32_t id_counter = 0;
14045 uint32_t id = 0;
14046
14047 int max_arg_index = 0;
14048 int timeout_index = -1;
14049
14050 double timeout = 0;
14051
14052 gh_manager& gh_mgr = interp.get_gh_manager ();
14053
14054 if (args.length () > 1)
14055 {
14056 pname = args(1).xstring_value ("waitfor: PROP must be a string");
14057
14058 if (pname.empty ())
14059 error ("waitfor: PROP must be a non-empty string");
14060
14061 if (pname != "timeout")
14062 {
14063 if (pname.compare (R"(\timeout)"))
14064 pname = "timeout";
14065
14066 static octave_value wf_listener;
14067
14068 if (! wf_listener.is_defined ())
14069 wf_listener
14070 = octave_value (new octave_builtin (waitfor_listener,
14071 "waitfor_listener"));
14072
14073 max_arg_index++;
14074 if (args.length () > 2)
14075 {
14076 if (args(2).is_string ())
14077 {
14078 caseless_str s = args(2).string_value ();
14079
14080 if (s.compare ("timeout"))
14081 timeout_index = 2;
14082 else
14083 max_arg_index++;
14084 }
14085 else
14086 max_arg_index++;
14087 }
14088
14089 Cell listener (1, max_arg_index >= 2 ? 5 : 4);
14090
14091 id = id_counter++;
14092 cleanup_waitfor_id_action.set (cleanup_waitfor_id, id);
14093 waitfor_results[id] = false;
14094
14095 listener(0) = wf_listener;
14096 listener(1) = octave_uint32 (id);
14097 listener(2) = h;
14098 listener(3) = pname;
14099
14100 if (max_arg_index >= 2)
14101 listener(4) = args(2);
14102
14103 octave_value ov_listener (listener);
14104
14105 octave::autolock guard (gh_mgr.graphics_lock ());
14106
14107 graphics_handle gh = gh_mgr.lookup (h);
14108
14109 if (gh.ok ())
14110 {
14111 graphics_object go = gh_mgr.get_object (gh);
14112
14113 if (max_arg_index >= 2
14114 && compare_property_values (interp, go.get (pname), args(2)))
14115 waitfor_results[id] = true;
14116 else
14117 {
14118 cleanup_waitfor_postset_listener_action.set
14119 (cleanup_waitfor_postset_listener, ov_listener);
14120
14121 go.add_property_listener (pname, ov_listener, GCB_POSTSET);
14122 go.add_property_listener (pname, ov_listener, GCB_PERSISTENT);
14123
14124 if (go.get_properties ().has_dynamic_property (pname))
14125 {
14126 static octave_value wf_del_listener;
14127
14128 if (! wf_del_listener.is_defined ())
14129 wf_del_listener
14131 (waitfor_del_listener,
14132 "waitfor_del_listener"));
14133
14134 Cell del_listener (1, 4);
14135
14136 del_listener(0) = wf_del_listener;
14137 del_listener(1) = octave_uint32 (id);
14138 del_listener(2) = h;
14139 del_listener(3) = pname;
14140
14141 octave_value ov_del_listener (del_listener);
14142
14143 cleanup_waitfor_predelete_listener_action.set
14144 (cleanup_waitfor_predelete_listener, ov_del_listener);
14145
14146 go.add_property_listener (pname, ov_del_listener,
14147 GCB_PREDELETE);
14148 }
14149 }
14150 }
14151 }
14152 }
14153
14154 if (timeout_index < 0 && args.length () > (max_arg_index + 1))
14155 {
14156 caseless_str s = args(max_arg_index +
14157 1).xstring_value ("waitfor: invalid parameter, expected 'timeout'");
14158
14159 if (! s.compare ("timeout"))
14160 error ("waitfor: invalid parameter '%s'", s.c_str ());
14161
14162 timeout_index = max_arg_index + 1;
14163 }
14164
14165 if (timeout_index >= 0)
14166 {
14167 if (args.length () <= (timeout_index + 1))
14168 error ("waitfor: missing TIMEOUT value");
14169
14170 timeout = args(timeout_index + 1).xscalar_value ("waitfor: TIMEOUT must be a scalar >= 1");
14171
14172 if (timeout < 1)
14173 {
14174 warning ("waitfor: TIMEOUT value must be >= 1, using 1 instead");
14175 timeout = 1;
14176 }
14177 }
14178
14179 // FIXME: There is still a "hole" in the following loop. The code
14180 // assumes that an object handle is unique, which is a fair
14181 // assumption, except for figures. If a figure is destroyed
14182 // then recreated with the same figure ID, within the same
14183 // run of event hooks, then the figure destruction won't be
14184 // caught and the loop will not stop. This is an unlikely
14185 // possibility in practice, though.
14186 //
14187 // Using deletefcn callback is also unreliable as it could be
14188 // modified during a callback execution and the waitfor loop
14189 // would not stop.
14190 //
14191 // The only "good" implementation would require object
14192 // listeners, similar to property listeners.
14193
14194 octave::sys::time start;
14195
14196 if (timeout > 0)
14197 start.stamp ();
14198
14199 while (true)
14200 {
14201 if (true)
14202 {
14203 octave::autolock guard (gh_mgr.graphics_lock ());
14204
14205 graphics_handle gh = gh_mgr.lookup (h);
14206
14207 if (gh.ok ())
14208 {
14209 if (! pname.empty () && waitfor_results[id])
14210 break;
14211 }
14212 else
14213 break;
14214 }
14215
14216 octave::sleep (0.1); // FIXME: really needed?
14217
14218 octave_quit ();
14219
14220 octave::command_editor::run_event_hooks ();
14221
14222 if (timeout > 0)
14223 {
14224 octave::sys::time now;
14225
14226 if (start + timeout < now)
14227 break;
14228 }
14229 }
14230
14231 return ovl ();
14232}
14233
14234DEFMETHOD (__zoom__, interp, args, ,
14235 doc: /* -*- texinfo -*-
14236@deftypefn {} {} __zoom__ (@var{axes}, @var{mode}, @var{factor})
14237@deftypefnx {} {} __zoom__ (@var{axes}, "out")
14238@deftypefnx {} {} __zoom__ (@var{axes}, "reset")
14239Undocumented internal function.
14240@end deftypefn */)
14241{
14242 int nargin = args.length ();
14243
14244 if (nargin != 2 && nargin != 3)
14245 print_usage ();
14246
14247 double h = args(0).double_value ();
14248
14249 gh_manager& gh_mgr = interp.get_gh_manager ();
14250
14251 octave::autolock guard (gh_mgr.graphics_lock ());
14252
14253 graphics_handle handle = gh_mgr.lookup (h);
14254
14255 if (! handle.ok ())
14256 error ("__zoom__: invalid handle");
14257
14258 graphics_object ax = gh_mgr.get_object (handle);
14259
14260 axes::properties& ax_props
14261 = dynamic_cast<axes::properties&> (ax.get_properties ());
14262
14263 if (nargin == 2)
14264 {
14265 std::string opt = args(1).string_value ();
14266
14267 if (opt == "out" || opt == "reset")
14268 {
14269 if (opt == "out")
14270 {
14271 ax_props.clear_zoom_stack ();
14272 Vdrawnow_requested = true;
14273 }
14274 else
14275 ax_props.clear_zoom_stack (false);
14276 }
14277 }
14278 else
14279 {
14280 std::string mode = args(1).string_value ();
14281 double factor = args(2).scalar_value ();
14282
14283 ax_props.zoom (mode, factor);
14284 Vdrawnow_requested = true;
14285 }
14286
14287 return ovl ();
14288}
14289
14290DEFMETHOD (__get_frame__, interp, args, ,
14291 doc: /* -*- texinfo -*-
14292@deftypefn {} {@var{cdata} =} __get_frame__ (@var{hfig})
14293Internal function.
14294
14295Return the pixel cdata of figure hfig in the form of a height-by-width-by-3
14296uint8 array.
14297@end deftypefn */)
14298{
14299 if (args.length () != 1)
14300 print_usage ();
14301
14302 double h = args(0).xdouble_value ("__get_frame__: HFIG is not a handle");
14303
14304 gh_manager& gh_mgr = interp.get_gh_manager ();
14305
14306 graphics_object go = gh_mgr.get_object (h);
14307
14308 if (! go || ! go.isa ("figure"))
14309 error ("__get_frame__: HFIG is not a figure");
14310
14311 // For Matlab compatibility, getframe must flush the event queue.
14312 gh_mgr.process_events ();
14313
14314 return ovl (go.get_toolkit ().get_pixels (go));
14315}
14316
14317DEFMETHOD (__get_position__, interp, args, ,
14318 doc: /* -*- texinfo -*-
14319@deftypefn {} {@var{pos} =} __get_position__ (@var{h}, @var{units})
14320Internal function.
14321
14322Return the position of the graphics object @var{h} in the specified
14323@var{units}.
14324@end deftypefn */)
14325{
14326 if (args.length () != 2)
14327 print_usage ();
14328
14329 double h
14330 = args(0).xdouble_value ("__get_position__: H must be a graphics handle");
14331
14332 std::string units
14333 = args(1).xstring_value ("__get_position__: UNITS must be a string");
14334
14335 gh_manager& gh_mgr = interp.get_gh_manager ();
14336
14337 graphics_object go = gh_mgr.get_object (h);
14338
14339 if (h == 0 || ! go)
14340 error ("__get_position__: H must be a handle to a valid graphics object");
14341
14342 graphics_object parent_go = gh_mgr.get_object (go.get_parent ());
14343 Matrix bbox = parent_go.get_properties ().get_boundingbox (true)
14344 .extract_n (0, 2, 1, 2);
14345
14346 Matrix pos = convert_position (go.get ("position").matrix_value (),
14347 go.get ("units").string_value (),
14348 units, bbox);
14349
14350 return ovl (pos);
14351}
14352
14353DEFUN (__get_system_fonts__, args, ,
14354 doc: /* -*- texinfo -*-
14355@deftypefn {} {@var{font_struct} =} __get_system_fonts__ ()
14356Internal function.
14357@end deftypefn */)
14358{
14359 if (args.length () != 0)
14360 print_usage ();
14361
14362 octave::text_renderer txt_renderer;
14363
14364 return ovl (txt_renderer.get_system_fonts ());
14365}
14366
14367OCTAVE_END_NAMESPACE(octave)
N Dimensional Array with copy-on-write semantics.
Definition Array.h:130
const dim_vector & dims() const
Return a const-reference so that dims ()(i) works efficiently.
Definition Array.h:507
T & elem(octave_idx_type n)
Size of the specified dimension.
Definition Array.h:563
int ndims() const
Size of the specified dimension.
Definition Array.h:679
octave_idx_type rows() const
Definition Array.h:463
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.h:424
octave_idx_type columns() const
Definition Array.h:475
bool isempty() const
Size of the specified dimension.
Definition Array.h:652
const T * data() const
Size of the specified dimension.
Definition Array.h:665
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.h:418
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:85
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:2429
void resize(octave_idx_type nr, octave_idx_type nc, double rfv=0)
Definition dMatrix.h:156
ColumnVector row_max() const
Definition dMatrix.cc:2484
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:90
static dim_vector alloc(int n)
Definition dim-vector.h:198
octave_idx_type ndims() const
Number of dimensions.
Definition dim-vector.h:253
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:768
bool is_undefined() const
Definition ov.h:595
bool is_uint32_type() const
Definition ov.h:724
std::string class_name() const
Definition ov.h:1362
bool is_real_scalar() const
Definition ov.h:610
Cell cell_value() const
octave_idx_type rows() const
Definition ov.h:545
bool is_scalar_type() const
Definition ov.h:744
bool isreal() const
Definition ov.h:738
bool is_string() const
Definition ov.h:637
bool isnumeric() const
Definition ov.h:750
bool is_int8_type() const
Definition ov.h:706
bool is_single_type() const
Definition ov.h:698
bool is_defined() const
Definition ov.h:592
double xdouble_value(const char *fmt,...) const
bool isempty() const
Definition ov.h:601
bool is_uint8_type() const
Definition ov.h:718
bool is_uint64_type() const
Definition ov.h:727
bool is_uint16_type() const
Definition ov.h:721
bool is_int16_type() const
Definition ov.h:709
octave_value reshape(const dim_vector &dv) const
Definition ov.h:571
bool iscell() const
Definition ov.h:604
octave_idx_type numel() const
Definition ov.h:559
std::string string_value(bool force=false) const
Definition ov.h:983
bool is_int64_type() const
Definition ov.h:715
bool is_matrix_type() const
Definition ov.h:747
bool iscomplex() const
Definition ov.h:741
string_vector string_vector_value(bool pad=false) const
Definition ov.h:986
NDArray array_value(bool frc_str_conv=false) const
Definition ov.h:865
bool is_double_type() const
Definition ov.h:695
bool is_int32_type() const
Definition ov.h:712
bool is_bool_scalar() const
Definition ov.h:622
std::string type_name() const
Definition ov.h:1360
bool iscellstr() const
Definition ov.h:607
octave_idx_type columns() const
Definition ov.h:547
Matrix matrix_value(bool frc_str_conv=false) const
Definition ov.h:859
double double_value(bool frc_str_conv=false) const
Definition ov.h:847
bool islogical() const
Definition ov.h:735
dim_vector dims() const
Definition ov.h:541
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:5008
#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:1078
void warning_with_id(const char *id, const char *fmt,...)
Definition error.cc:1093
void error(const char *fmt,...)
Definition error.cc:1003
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:5677
Matrix xform_matrix()
Definition graphics.cc:5666
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:5772
void translate(Matrix &m, double x, double y, double z)
Definition graphics.cc:5737
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:5784
std::vector< octave_idx_type > coplanar_partition(const Matrix &vert, const Matrix &idx, octave_idx_type nc, octave_idx_type jj)
Definition graphics.cc:9791
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:8336
#define FIX_LIMITS
double norm(const ColumnVector &v)
Definition graphics.cc:5778
void xform(ColumnVector &v, const Matrix &m)
Definition graphics.cc:5743
void normalize(ColumnVector &v)
Definition graphics.cc:5765
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:5705
ColumnVector cam2xform(const Array< double > &m)
Definition graphics.cc:5817
int calc_dimensions(const graphics_object &go)
RowVector xform2cam(const ColumnVector &v)
Definition graphics.cc:5827
graphics_handle gcf()
Definition graphics.cc:2997
#define GO_BODY(TYPE)
void scale(Matrix &m, double x, double y, double z)
Definition graphics.cc:5731
Matrix xform_translate(double x, double y, double z)
Definition graphics.cc:5718
ColumnVector transform(const Matrix &m, double x, double y, double z)
Definition graphics.cc:5699
Matrix unit_cube()
Definition graphics.cc:5796
graphics_handle gca()
Definition graphics.cc:3007
#define CHECK_ARRAY_EQUAL(T, F, A)
bool is_coplanar(const Matrix &cov)
Definition graphics.cc:9779
double force_in_range(double x, double lower, double upper)
Definition graphics.cc:8855
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 lo-mappers.h:71
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
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 len
Definition xerbla.cc:61