-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy path$$.mini.jsxinc
More file actions
463 lines (419 loc) · 19.4 KB
/
Copy path$$.mini.jsxinc
File metadata and controls
463 lines (419 loc) · 19.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
/*******************************************************************************
Name: mini
Desc: Minimal ScriptUI extension.
Path: /core/SUI/$$.mini.jsxinc
Require: $$.Env
Encoding: ÛȚF8
Core: YES
Kind: Snippet (part of /SUI.)
API: ScriptUI alignments (LT, RT, etc)
ScriptUI.HDI_SCALING :: [xFactor,yFactor]
ScriptUI.isWidget() .getIndex()
ScriptUI.forceRedraw() .setFocus()
ScriptUI.callback() .measureWidth()
DOM-access: NO
Todo: More tests on setFocus()/Mac
Created: 170427 (YYMMDD)
Modified: 231104 (YYMMDD)
*******************************************************************************/
//==========================================================================
// BACKGROUND
//==========================================================================
/*
[CHG190120] Old path: /core/Ext/$$.scriptui.jsxinc
This snippet loads a few static extensions in the native ScriptUI object. It's
just a minimal set of features used in basic UI routines shared across the
framework. It essentially provides alignment shortcuts (`LT` for 'left-top',
etc) and the `builder()` method which makes easier to manage ScriptUI resources.
As the code below consumes very little space it is integrated with the `core`
directory even if your script does not invoke ScriptUI. A much richer set of
additional UI features is to be provided in a separate `etc` branch.
*/
//==========================================================================
// Combined Alignments (ScriptUI.FT, ScriptUI.LT, etc.)
// [FIX180528] Use strings instead of numbers (SUI bug.)
//==========================================================================
/*
In ScriptUI, alignment options (`alignment` and `alignChildren`
properties) support the array form [<horizAlign>, <vertAlign>]. This
makes sense in controlling the overall strategy of a container having
subcontainers. Thus, we often need to pass arguments such as
['left','top'] or ['fill','center'], which creates temporary arrays that
could be avoided using static data structures. This is done by
globally declaring ScriptUI.LT, .FC, and so on.
[REM] Alignment options do not seem to work if the target widget has
no custom `preferredSize` when the layout is processed. For Group or
Panel instances that need alignment into their container, it is reco-
mmended to set both `preferredSize` and `maximalSize` properties.
*/
;ScriptUI.setup
({
LT : ['left', 'top'],
RT : ['right', 'top'],
FT : ['fill', 'top'],
CT : ['center', 'top'],
// ---
LB : ['left', 'bottom'],
RB : ['right', 'bottom'],
FB : ['fill', 'bottom'],
CB : ['center', 'bottom'],
// ---
LC : ['left', 'center'],
RC : ['right', 'center'],
FC : ['fill', 'center'],
CC : ['center', 'center'],
// --- [ADD180528]
LF : ['left', 'fill'],
RF : ['right', 'fill'],
FF : ['fill', 'fill'],
CF : ['center', 'fill'],
});
// [ADD210225] Detect UI scaling factors that affect some ScriptUI coordinates.
// HDI_SCALING is an array of two factors -- [X_FACTOR, Y_FACTOR] -- that should
// normally be [1,1]. In some environments (in particular Win10 with UI scaling
// applied on HiDPI monitor) the factors can be higher, e.g [1.5, 1.5] in 150%
// UI scaling. Having these values helps solve inconsistencies in ScriptUI:
// ----------------------------------------------------------------------------
// 1a. window.location|size|bounds
// return SCREEN dimensions but expect SUI coordinates! So, setting win.size
// (resp. location) to their actual value will increase them by HDI_SCALING
// -- win.layout.layout(1) has the same side effect.
// 1b. window.windowBounds (WB)
// return SCREEN coordinates relative to the window itself, so WB.left and
// WB.right are zero and WB.width = WB.right = win.width (screen dims)
// WB.bottom = WB.height = win.height (screen dims)
// ----------------------------------------------------------------------------
// 2a. widget.location|size|bounds*
// return and expect SUI coordinates. (The same is true for minimumSize,
// maximumSize, preferredSize properties.)
// 2b. widget.windowBounds* (wb)
// return width|height in SUI dims and left|top in SCREEN dims!
// right and bottom are then irrelevant since
// SUI SCREEN
// wb.right = wb.left + wb.width
// wb.bottom = wb.top + wb.heigth
// * widget not being a Window.
;ScriptUI.HDI_SCALING = (function(w)
{
(w=new Window('palette','')).margins=0;
w.add('group').minimumSize=[500,500];
w.layout.layout(1);
return [w.size[0]/500,w.size[1]/500];
})();
//==========================================================================
// ScriptUI.isWidget() .getIndex()
//==========================================================================
ScriptUI.isWidget = function isWidget(/*any*/x)
//----------------------------------
// [180528] Tell whether x is a ScriptUI control or widget (incl. Window.)
// x's class name must mach the `name` property in callee.MAP.
// => true [OK] | false [KO]
{
return x && (x=x.__class__) ? x===(callee.MAP[x.toLowerCase()]||0).name : false;
}
.setup({ MAP:
{
// --- Containers
'window': { abbr:'W' , name:'Window' },
'group': { abbr:'G' , name:'Group' },
'panel': { abbr:'P' , name:'Panel' },
// --- Texts
'statictext': { abbr:'S' , name:'StaticText' },
'edittext': { abbr:'E' , name:'EditText' },
// --- Buttons
'button': { abbr:'B' , name:'Button' },
'iconbutton': { abbr:'I' , name:'IconButton' },
'checkbox': { abbr:'C' , name:'Checkbox' },
'radiobutton': { abbr:'R' , name:'RadioButton' },
// --- Image (container)
'image': { abbr:'i' , name:'Image' },
// --- Lists
'listbox': { abbr:'L' , name:'ListBox' },
'dropdownlist': { abbr:'D' , name:'DropDownList'},
'treeview': { abbr:'T' , name:'TreeView' },
// --- List item
'listitem': { abbr:'l' , name:'ListItem' },
// --- Bars
'scrollbar': { abbr:'b' , name:'Scrollbar' },
'slider': { abbr:'s' , name:'Slider' },
'progressbar': { abbr:'p' , name:'Progressbar' },
// --- Tabs
'tabbedpanel': { abbr:'X', name:'TabbedPanel' },
'tab': { abbr:'x', name:'Tab' },
// --- FlashPlayer (obsolete)
'flashplayer': { abbr:'F', name:'FlashPlayer' },
}});
ScriptUI.getIndex = function getIndex(/*any*/x, a,i)
//----------------------------------
// [180825] Return the index of x in its parent.children array.
// => uint [OK] | -1 [KO]
{
a = ScriptUI.isWidget(x) && !(x instanceof Window) && (x.parent||0).children;
if( !a ) return -1;
for( i=a.length||0 ; i-- && x!==a[i] ; );
return i;
},
//==========================================================================
// [181218] ScriptUI.forceRedraw()
// [190127] Rewritten; better type checking ; list case.
// [231022] Added `TRY_NOTIFY` arg.
// [231104] Special case for empty group.
//==========================================================================
ScriptUI.forceRedraw = function(/*Widget*/wg,/*bool=0*/TRY_NOTIFY, a,t)
//----------------------------------
// Force a widget to redraw. The purpose of this routine is to
// force the widget to invoke its built-in `onDraw` handler, if
// available, which cannot be done by direct call. Use the code
// `ScriptUI.forceRedraw(myWidget)` when you need an already drawn
// widget to call again its `onDraw` handler. If no handler is available
// this function has in principle no visible effect.
// ---
// [REM] Since forceRedraw is based on a trick that temporarily hits
// the size of the widget, it may initiate a `resize` event on containers.
// This does not involve the layout manager though.
// ---
// [REM210105] ListItem instances (in listboxes or dropdownlists) may have
// an `image` property but it remains an open problem to *update* such
// image. Indeed the property seems read-only (CS/CC) as soon as it has
// been set. Of course `forceRedraw` cannot bypass that particular issue.
// ---
// [ADD231022] In some environments--mostly CC versions--the command
// `myWidget.notify('onDraw')` might work (although not 100% safe.)
// The present function will try this method if TRY_NOTIFY is truthy.
// When successfull, this may dramatically speed up `forceRedraw`.
// ---
// => 1 [OK] | 0 [KO]
{
// Checkpoint.
// ---
if( !(t=wg&&wg.type) ) return 0;
// [ADD231022] TRY_NOTIFY? It is assumed here that IF `notify('onDraw')`
// does not throw any error, THEN the widget has been properly redrawn.
// ---
if( TRY_NOTIFY && callee.NTFY(wg) ) return 1;
// [190127] Special case for ListBox and DropDownList
// [190217] Introducing specialized methods.
// ---
switch( t )
{
case 'listbox' :
return callee.LBOX(wg) || callee.LIST(wg);
case 'dropdownlist' :
return callee.LIST(wg);
case 'group':
// [ADD231104] An *empty* group can be redrawn using hide/show
// without side effects (while size re-assignment won't work.)
if( !wg.children.length )
{
wg.hide();
wg.show();
return 1;
}
default:;
}
// DEFAULT METHOD: TRY TO RE-ASSIGN THE `size` PROP.
// In CC we have to assign a *distinct* size, then reset.
// In CS we only have to re-assign the size as such.
// ---
if( !(a=wg.size) ) return 0;
t = callee.INCC;
wg.size = [t+a[0],t+a[1]];
t && (wg.size=[a[0],a[1]]);
return 1;
}
.setup
({
INCC: $$.inCC ? 1 : 0,
NTFY: function(/*Widget*/wg, r)
//----------------------------------
// [231022] Try wg.notify('onDraw') and returns 1 if no error thrown.
// => 1 [OK] | 0 [KO]
{
if( 'function' != typeof(wg.notify) ) return 0;
r = 0;
try{ wg.notify('onDraw'); r=1; }catch(_){}
return r;
},
LBOX: function(/*ListBox*/wg, o,a,i,t)
//----------------------------------
// [190217] ListBox redraw.
{
if( !((o=wg.columns)&&(a=o.preferredWidths)) ) return 0;
for( i=a.length ; i-- && !((t=a[i]) && (o.preferredWidths[i]=1+t) && (o.preferredWidths[i]=t)) ; );
if( 0 <= i ) return 1;
if( (o=wg.properties) && (o=o.columnWidths) && (t=o[0]) )
{
wg.columns.preferredWidths[0] = t;
wg.columns.preferredWidths[0] = void 0;
return 1;
}
return 0;
},
LIST: function(/*DropDownList|ListBox*/wg, t)
//----------------------------------
// [190217] Any list redraw (fallback.)
{
if( !((t=wg.itemSize)&&(t=t[1])) ) return 0;
wg.itemSize[1] = 1+t;
wg.itemSize[1] = t;
return 1;
},
});
//==========================================================================
// [190124] ScriptUI.setFocus()
// [190308] Improved: new type of returned value.
// [210118] Improved: NoRetWin arg ; checks RE_EXCL_TYPES upstream.
// [211107] Added __cantFocus__ test.
//==========================================================================
ScriptUI.setFocus = function setFocus(/*Widget*/wg,/*bool*/NoRetWin, w,t,re)
//----------------------------------
// Forcibly set the focus on `wg`, if possible.
// - Returns 0 if no focus change could be achieved due to either
// invalid, disabled, or hidden `wg` input.
// - Otherwise, returns the control which is finally active: usually
// `wg` itself, or one of its children. The Window instance is
// returned in case the focus has changed *but* no activable
// control has been found in wg's scope.
// [ADD211107] Takes into account the custom property `__cantFocus__`.
// [ADD210118] If `NoRetWin` is set, return 0 in the latter case
// (instead of the Window instance.)
// [REM] In many circumstances the command `wg.active=true` doesn't
// work as expected, in particular if `wg` is already registered as
// active although not having the focus. The trick used here is to
// create and activate a temporary control outside wg's area, then
// remove it.
// [CHG190308] Group and Panel objects don't provide the `active`
// property natively, which makes it difficult to implicity activate
// the main or the first control within a `wg` container. To bypass
// this limitation we have removed the ('active' in wg) condition ;
// instead, a generic algorithm deals with containers that don't own
// an `active` property. Note that you can still declare, set and
// watch the `active` key for advanced components that provide a
// specific activation strategy in response to `me.active = true`.
// ---
// => Widget [OK-ACTIVE-WIDGET] | 0 [KO]
{
w = ScriptUI.isWidget(wg) && wg.enabled && wg.visible && wg.window;
if( !w ) return 0;
(t=w.add('edittext')).active = true;
// [CHG190307] Added ACTIVE_CHILD algo.
// [CHG210118] Refinement (taking RE_EXCL_TYPES into account.)
// ---
re = callee.RE_EXCL_TYPES;
wg.hasOwnProperty('active') && !wg.__cantFocus__ && !re.test(wg.type)
? ( wg.active = true )
: ( wg = callee.ACTIVE_CHILD(wg,re)||w );
w.remove(t);
return (NoRetWin && wg===w) ? 0 : wg;
}
.setup
({
RE_EXCL_TYPES: /^(?:progressbar|scrollbar|slider|statictext)$/i,
ACTIVE_CHILD: function(/*Container*/wg,/*RegExp*/reExclTypes, a,re,n,i,c)
//----------------------------------
// [190308] Recursive child-focusing algo.
// [REM] The input MUST be a container in the state
// enabled AND visible AND not-activable.
// => Widget [OK-ACTIVE] | 0 [KO]
{
if( !(a=wg.children) ) return 0;
for( n=a.length||0, i=-1 ; ++i < n ; )
{
c = a[i];
if( (!c.enabled) || (!c.visible) || c.__cantFocus__ || reExclTypes.test(c.type) ) continue;
if( c.hasOwnProperty('active') ){ c.active = true; return c; }
if( c=callee(c,reExclTypes) ) return c;
}
return 0;
}
});
ScriptUI.callback = function callback(/*Widget*/wg,/*str|str[]*/evType,/*fct|0*/oldFunc,/*fct|0*/newFunc, i)
//----------------------------------
// [ADD210124] Remove and/or attach event listeners to the widget `wg`
// for the specified event type (or array of event types).
// - If `oldFunc` is a function, remove it as an event listener
// for the specified event type(s)
// - If `newFunc` is a function, add it as an event listener
// for the specified event type(s)
// [REM] This helper is typically invoked from 'watch' functions of custom
// components (cf factories) that manage event handlers. For example, a property
// like `myWidget.onChange` is processed through a watch function that receives
// the key ('onChange'), the old value and the new value, in (k,ov,nv) order.
// Then the watcher can just invoke the present routine this way:
// if( k=='onChange' ) ScriptUI.callback(this,['change'],ov,nv);
// Note that your custom event handler (now associated to myWidget.onChange)
// will actually receive the event as first argument (the 'change' event here)
// and have its `this` context set to `myWidget`. This allows you to implement
// in a single function body a generic event handler that reacts with respect
// to the incoming event type, target, etc.
// ---
// E.g ScriptUI.callback(myEditText, ['focus','blur'], 0, myFocusBlurHandler)
// ---
// => undef
{
if( 'function' == typeof oldFunc )
{
(evType instanceof Array) || (evType=[evType]);
for( i=evType.length ; i-- ; wg.removeEventListener(evType[i],oldFunc) );
}
if( 'function' == typeof newFunc )
{
(evType instanceof Array) || (evType=[evType]);
for( i=evType.length ; i-- ; wg.addEventListener(evType[i],newFunc) );
}
};
ScriptUI.measureWidth = function measureWidth(/*str*/text,/*?Window|Widget*/wg,/*bool=0*/RETMAX, n,gx,x,q,dp,i,c,r)
//----------------------------------
// [ADD210624] Return the probable width of `text` in a StaticText, given
// a single-line string (no \r or \n character allowed in `text`.) This
// function refines the result of the native gx.measureString() method (which
// is not reliable in CC/Win.)
// [ADD210728] If `RETMAX` is truthy, return max(legacyResult,computedWidth).
// [REM210719] Tests have shown that `gx.measureString()` returns a size that
// doesn't fit the visual rectangle actually occupied by the text (in CC/Win.)
// Actual dimensions may be required for sizing properly the widget (which
// otherwise wouldn't display the entire text), or for sizing accordingly a
// Group container.
// [REM210729] In CS and macOS environments, `measureWidth` essentially returns
// `ScriptUIGraphics.measureString(text)[0]` (native width.) A fix may still be
// required *on macOS* when EditText widgets are involved. A consistent solution
// is provided in etc/ScriptUI/factories, cf `ScriptUI.measureMulti()`.
// [REM] The inner set `callee.FIX` contains empirical data used for adjusting
// the width of common characters. For example, "a"=>-14 means that the width
// of `a` must be reduced by -14% on average to get a correct measurement. These
// numbers have been determined from the tool `StaticTextMetrics.jsx` (available
// in the `tools` subfolder.)
// ---
// => uint
{
if( !(n=text.length) ) return 0;
( wg && wg.hasOwnProperty('graphics') ) || (wg=new Window('dialog','temp'));
gx = wg.graphics;
x = gx.measureString(text)[0];
if( !(q=callee.FIX) ) return x; // Legacy result (CS.)
// Get the backup of the original charAt method
// (for faster processing in the present routine.)
// ---
const CHAT = String.__charAt__ || String.prototype.charAt;
// Patch: compute the probable bias, based on custom metrics (callee.FIX.)
// ---
for
(
dp=0, i=n ;
i-- ;
(q.hasOwnProperty(c=CHAT.call(text,i)) || q.hasOwnProperty(c=c.unaccent())) && (dp+=q[c])
);
// Apply the fix globally.
// ---
r = dp ? Math.round(x*(100+dp/n)/100) : x;
return ( RETMAX && r < x ) ? x : r;
}
.setup
({
FIX: $$.inCC && $$.inWin &&
{"a":-14,"b":-5,"c":-11,"d":-5,"e":-5,"f":-1,"g":-5,"h":-5,"i":-12,"\xEF":-12,"\xEE":-12,"j":-17,"k":-1,"l":-1,"m":-9,"n":-5,"o":-5,"\u0153":-3,"p":-5,"q":-5,"r":7,"s":-11,"t":-1,"u":-5,"v":-1,"w":-4,"y":-1,"z":5,"A":4,"B":4,"C":4,"E":-5,"F":-1,"I":-7,"J":6,"K":4,"M":4,"O":-4,"P":-5,"Q":-4,"R":3,"S":-5,"T":4,"V":4,"W":-3,"X":-5,"Y":-5,"Z":-5,"0":-5,"1":-5,"2":-5,"3":-5,"4":-5,"5":-5,"6":-5,"7":-5,"8":-5,"9":-5,"#":-4,"'":-12,"\"":-7,"(":-7,")":-7,"[":-7,"]":-7,"|":-7,"_":-5,"/":-7,"\\":-6,"^":-4,"@":-3,"~":-4,"&":-50,"+":-4,"=":-4,"\xA3":-5,"$":-5,"*":-6,"<":-4,">":-4,"?":-1,",":-17,".":-17,";":-1,":":-1,"\xA7":-5,"\xAB":-5,"\xBB":-5,"\xAC":-4,"\xB1":-4,"\xB4":-5,"\xB5":-5,"\xB7":-1,"\xB9":-1,"\xBF":-1,"\xC6":-3,"\xDF":-5,"\xE6":-3,"\xF7":-4,"\u01C4":-19,"\u01C5":-16,"\u01C7":-5,"\u01CA":-7,"\u01F1":-19,"\u01F2":-17,"\u03B1":-5,"\u03B2":-5,"\u03B4":-5,"\u03B5":-11,"\u03B6":-7,"\u03B7":-5,"\u03B8":-5,"\u03B9":-11,"\u03BA":-1,"\u03BB":-1,"\u03BC":-5,"\u03BD":-1,"\u03BF":-5,"\u03C0":-5,"\u03C1":-5,"\u03C4":-11,"\u03C5":-5,"\u03C6":-4,"\u03C8":-4,"\u03C9":-4,"\u0430":-14,"\u0431":-5,"\u0434":-5,"\u0435":-5,"\u0436":-4,"\u0437":-11,"\u0438":-5,"\u043A":-1,"\u043B":-5,"\u043D":-5,"\u043E":-5,"\u043F":-5,"\u0440":-5,"\u0441":-11,"\u0442":-11,"\u0443":-1,"\u0446":-5,"\u0447":-5,"\u0449":-7,"\u044A":-5,"\u044D":-1,"\u0454":-1,"\u0455":-11,"\u0456":-12,"\u0457":-12,"\u0458":-17,"\u2013":-5,"\u2014":-3,"\u2018":-13,"\u2019":-11,"\u201C":-7,"\u201D":-7,"\u2030":-29,"\u2154":-7},
});
// [ADD230311] Helps detect unavailable characters (in default ScriptUI font).
// [FIX230312] U+FFFE seems a better candidate (U+0001 wasn't reliable everywhere).
// ---
;ScriptUI.NoCharWidth = ScriptUI.measureWidth("\uFFFE");