mwxlib 1.0.0__py3-none-any.whl → 1.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mwx/__init__.py +6 -4
- mwx/bookshelf.py +144 -68
- mwx/controls.py +444 -378
- mwx/framework.py +567 -546
- mwx/graphman.py +745 -726
- mwx/images.py +16 -0
- mwx/matplot2.py +244 -235
- mwx/matplot2g.py +812 -751
- mwx/matplot2lg.py +218 -209
- mwx/mgplt.py +20 -22
- mwx/nutshell.py +1119 -1015
- mwx/plugins/ffmpeg_view.py +96 -90
- mwx/plugins/fft_view.py +13 -15
- mwx/plugins/frame_listview.py +68 -75
- mwx/plugins/line_profile.py +1 -1
- mwx/py/filling.py +13 -14
- mwx/testsuite.py +38 -0
- mwx/utilus.py +284 -219
- mwx/wxmon.py +43 -44
- mwx/wxpdb.py +83 -84
- mwx/wxwil.py +19 -18
- mwx/wxwit.py +38 -45
- {mwxlib-1.0.0.dist-info → mwxlib-1.8.0.dist-info}/METADATA +12 -5
- mwxlib-1.8.0.dist-info/RECORD +28 -0
- {mwxlib-1.0.0.dist-info → mwxlib-1.8.0.dist-info}/WHEEL +1 -1
- mwxlib-1.0.0.dist-info/LICENSE +0 -21
- mwxlib-1.0.0.dist-info/RECORD +0 -28
- {mwxlib-1.0.0.dist-info → mwxlib-1.8.0.dist-info}/top_level.txt +0 -0
mwx/controls.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
#! python3
|
|
2
2
|
"""mwxlib param controller and wx custom controls.
|
|
3
3
|
"""
|
|
4
|
+
from contextlib import contextmanager
|
|
4
5
|
from itertools import chain
|
|
6
|
+
import io
|
|
7
|
+
import re
|
|
5
8
|
import wx
|
|
6
9
|
import wx.lib.platebtn as pb
|
|
7
10
|
import wx.lib.scrolledpanel as scrolled
|
|
@@ -12,7 +15,7 @@ from .utilus import funcall as _F
|
|
|
12
15
|
from .framework import pack, Menu, CtrlInterface
|
|
13
16
|
|
|
14
17
|
import numpy as np
|
|
15
|
-
from numpy import nan, inf # noqa
|
|
18
|
+
from numpy import nan, inf # noqa # necessary to eval
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
def _Tip(*tips):
|
|
@@ -21,25 +24,26 @@ def _Tip(*tips):
|
|
|
21
24
|
|
|
22
25
|
|
|
23
26
|
class Param:
|
|
24
|
-
"""Standard Parameter
|
|
27
|
+
"""Standard Parameter.
|
|
25
28
|
|
|
26
29
|
Args:
|
|
27
|
-
name
|
|
28
|
-
range
|
|
29
|
-
value
|
|
30
|
-
fmt
|
|
31
|
-
|
|
32
|
-
handler
|
|
33
|
-
updater
|
|
34
|
-
checker
|
|
30
|
+
name: label
|
|
31
|
+
range: list of values
|
|
32
|
+
value: std_value (default is None)
|
|
33
|
+
fmt: text formatter or format:str (default is '%g')
|
|
34
|
+
`hex` specifies hexadecimal format
|
|
35
|
+
handler: called when knob is handled.
|
|
36
|
+
updater: called when button is pressed.
|
|
37
|
+
checker: called when tick turns on/off.
|
|
35
38
|
|
|
36
39
|
Attributes:
|
|
37
|
-
knobs
|
|
38
|
-
callback
|
|
40
|
+
knobs: knob list
|
|
41
|
+
callback: single state machine that handles following events
|
|
39
42
|
|
|
40
43
|
- control -> when index is changed by knobs or reset (handler)
|
|
41
44
|
- updated -> when button is pressed (updater)
|
|
42
45
|
- checked -> when tick turns on/off (checker)
|
|
46
|
+
- notified -> when value changed
|
|
43
47
|
- overflow -> when value overflows
|
|
44
48
|
- underflow -> when value underflows
|
|
45
49
|
"""
|
|
@@ -57,34 +61,36 @@ class Param:
|
|
|
57
61
|
else:
|
|
58
62
|
self.__eval = lambda v: eval(v)
|
|
59
63
|
self.__format = fmt or "{:,g}".format
|
|
60
|
-
if isinstance(fmt, str):
|
|
64
|
+
if isinstance(fmt, str): # support %-format:str (deprecated)
|
|
61
65
|
self.__format = lambda v: fmt % v
|
|
62
66
|
self.callback = SSM({
|
|
63
|
-
'control' : [
|
|
64
|
-
'updated' : [
|
|
65
|
-
'checked' : [
|
|
67
|
+
'control' : [_F(handler)] if handler else [],
|
|
68
|
+
'updated' : [_F(updater)] if updater else [],
|
|
69
|
+
'checked' : [_F(checker)] if checker else [],
|
|
70
|
+
'notified' : [],
|
|
66
71
|
'overflow' : [],
|
|
67
72
|
'underflow' : [],
|
|
68
73
|
})
|
|
69
|
-
self._tooltip = _Tip(handler.__doc__,
|
|
70
|
-
|
|
74
|
+
self._tooltip = _Tip(handler.__doc__,
|
|
75
|
+
updater.__doc__)
|
|
76
|
+
|
|
71
77
|
def __str__(self, v=None):
|
|
72
78
|
if v is None:
|
|
73
79
|
v = self.value
|
|
74
80
|
try:
|
|
75
81
|
return self.__format(v)
|
|
76
|
-
except ValueError:
|
|
82
|
+
except (TypeError, ValueError):
|
|
77
83
|
return str(v)
|
|
78
|
-
|
|
84
|
+
|
|
79
85
|
def __int__(self):
|
|
80
86
|
return int(self.value)
|
|
81
|
-
|
|
87
|
+
|
|
82
88
|
def __float__(self):
|
|
83
89
|
return float(self.value)
|
|
84
|
-
|
|
90
|
+
|
|
85
91
|
def __len__(self):
|
|
86
92
|
return len(self.range)
|
|
87
|
-
|
|
93
|
+
|
|
88
94
|
def reset(self, v=None, internal_callback=True):
|
|
89
95
|
"""Reset value when indexed (by knobs) with callback."""
|
|
90
96
|
if v is None:
|
|
@@ -93,51 +99,51 @@ class Param:
|
|
|
93
99
|
return
|
|
94
100
|
elif isinstance(v, str):
|
|
95
101
|
try:
|
|
96
|
-
v = self.__eval(v.replace(',', ''))
|
|
102
|
+
v = self.__eval(v.replace(',', '')) # Eliminates commas.
|
|
97
103
|
except Exception:
|
|
98
104
|
v = self.value
|
|
99
105
|
internal_callback = False
|
|
100
106
|
self.value = v
|
|
101
107
|
if internal_callback:
|
|
102
108
|
self.callback('control', self)
|
|
103
|
-
|
|
109
|
+
|
|
104
110
|
@property
|
|
105
111
|
def check(self):
|
|
106
112
|
"""A knob check-flag (user defined)."""
|
|
107
113
|
return self.__check
|
|
108
|
-
|
|
114
|
+
|
|
109
115
|
@check.setter
|
|
110
116
|
def check(self, v):
|
|
111
117
|
self.__check = bool(v)
|
|
112
118
|
self.callback('checked', self)
|
|
113
|
-
|
|
119
|
+
|
|
114
120
|
@property
|
|
115
121
|
def name(self):
|
|
116
122
|
return self.__name
|
|
117
|
-
|
|
123
|
+
|
|
118
124
|
@name.setter
|
|
119
125
|
def name(self, v):
|
|
120
126
|
self.__name = v
|
|
121
127
|
for knob in self.knobs:
|
|
122
128
|
knob.update_label()
|
|
123
|
-
|
|
129
|
+
|
|
124
130
|
@property
|
|
125
131
|
def value(self):
|
|
126
132
|
"""Current value := std_value + offset."""
|
|
127
133
|
return self.__value
|
|
128
|
-
|
|
134
|
+
|
|
129
135
|
@value.setter
|
|
130
136
|
def value(self, v):
|
|
131
137
|
if v is None:
|
|
132
138
|
v = nan
|
|
133
|
-
if np.isnan(v) or np.isinf(v):
|
|
139
|
+
if np.isnan(v) or np.isinf(v): # Skip events for nan and inf.
|
|
134
140
|
self.__value = v
|
|
135
141
|
for knob in self.knobs:
|
|
136
|
-
knob.
|
|
142
|
+
knob.update_control(None)
|
|
137
143
|
return
|
|
138
144
|
elif v == self.__value:
|
|
139
145
|
for knob in self.knobs:
|
|
140
|
-
knob.
|
|
146
|
+
knob.update_control()
|
|
141
147
|
return
|
|
142
148
|
|
|
143
149
|
## If the value is out of range, it will be modified.
|
|
@@ -151,13 +157,14 @@ class Param:
|
|
|
151
157
|
self.__value = self.max
|
|
152
158
|
self.callback('overflow', self)
|
|
153
159
|
for knob in self.knobs:
|
|
154
|
-
knob.
|
|
155
|
-
|
|
160
|
+
knob.update_control(valid, notify=True)
|
|
161
|
+
self.callback('notified', self)
|
|
162
|
+
|
|
156
163
|
@property
|
|
157
164
|
def std_value(self):
|
|
158
165
|
"""A standard value (default None)."""
|
|
159
166
|
return self.__std_value
|
|
160
|
-
|
|
167
|
+
|
|
161
168
|
@std_value.setter
|
|
162
169
|
def std_value(self, v):
|
|
163
170
|
if v is None:
|
|
@@ -165,7 +172,7 @@ class Param:
|
|
|
165
172
|
self.__std_value = v
|
|
166
173
|
for knob in self.knobs:
|
|
167
174
|
knob.update_label()
|
|
168
|
-
|
|
175
|
+
|
|
169
176
|
@property
|
|
170
177
|
def offset(self):
|
|
171
178
|
"""Offset value
|
|
@@ -174,38 +181,40 @@ class Param:
|
|
|
174
181
|
if not np.isnan(self.std_value):
|
|
175
182
|
return self.value - self.std_value
|
|
176
183
|
return self.value
|
|
177
|
-
|
|
184
|
+
|
|
178
185
|
@offset.setter
|
|
179
186
|
def offset(self, v):
|
|
180
187
|
if not np.isnan(self.std_value):
|
|
181
188
|
v += self.std_value
|
|
182
189
|
self.value = v
|
|
183
|
-
|
|
190
|
+
|
|
184
191
|
min = property(lambda self: self.__range[0] if self else nan)
|
|
185
192
|
max = property(lambda self: self.__range[-1] if self else nan)
|
|
186
|
-
|
|
193
|
+
|
|
187
194
|
@property
|
|
188
195
|
def range(self):
|
|
189
196
|
"""Index range."""
|
|
190
197
|
return self.__range
|
|
191
|
-
|
|
198
|
+
|
|
192
199
|
@range.setter
|
|
193
200
|
def range(self, v):
|
|
194
201
|
if v is None:
|
|
195
|
-
self.__range = []
|
|
202
|
+
self.__range = [] # dummy data
|
|
196
203
|
else:
|
|
197
204
|
self.__range = sorted(v)
|
|
198
205
|
for knob in self.knobs:
|
|
199
|
-
knob.update_range()
|
|
200
|
-
|
|
206
|
+
knob.update_range() # list range of related knobs
|
|
207
|
+
|
|
201
208
|
@property
|
|
202
209
|
def index(self):
|
|
203
210
|
"""A knob index -> value.
|
|
204
|
-
Returns -1 if the value is not defined.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
211
|
+
Returns -1 if the value is not defined.
|
|
212
|
+
"""
|
|
213
|
+
v = self.value
|
|
214
|
+
if np.isnan(v) or np.isinf(v):
|
|
215
|
+
return -1
|
|
216
|
+
return int(np.searchsorted(self.range, v))
|
|
217
|
+
|
|
209
218
|
@index.setter
|
|
210
219
|
def index(self, j):
|
|
211
220
|
if self:
|
|
@@ -214,40 +223,41 @@ class Param:
|
|
|
214
223
|
|
|
215
224
|
|
|
216
225
|
class LParam(Param):
|
|
217
|
-
"""Linear Parameter
|
|
226
|
+
"""Linear Parameter.
|
|
218
227
|
|
|
219
228
|
Args:
|
|
220
|
-
name
|
|
221
|
-
range
|
|
222
|
-
value
|
|
223
|
-
fmt
|
|
224
|
-
|
|
225
|
-
handler
|
|
226
|
-
updater
|
|
227
|
-
checker
|
|
229
|
+
name: label
|
|
230
|
+
range: range params [min:max:step]
|
|
231
|
+
value: std_value (default is None)
|
|
232
|
+
fmt: text formatter or format:str (default is '%g')
|
|
233
|
+
`hex` specifies hexadecimal format
|
|
234
|
+
handler: called when knob is handled.
|
|
235
|
+
updater: called when button is pressed.
|
|
236
|
+
checker: called when tick turns on/off.
|
|
228
237
|
|
|
229
238
|
Attributes:
|
|
230
|
-
knobs
|
|
231
|
-
callback
|
|
239
|
+
knobs: knob list
|
|
240
|
+
callback: single state machine that handles following events
|
|
232
241
|
|
|
233
242
|
- control -> when index is changed by knobs or reset (handler)
|
|
234
243
|
- updated -> when button is pressed (updater)
|
|
235
244
|
- checked -> when tick turns on/off (checker)
|
|
245
|
+
- notified -> when value changed
|
|
236
246
|
- overflow -> when value overflows
|
|
237
247
|
- underflow -> when value underflows
|
|
238
248
|
"""
|
|
239
249
|
min = property(lambda self: self.__min)
|
|
240
250
|
max = property(lambda self: self.__max)
|
|
241
251
|
step = property(lambda self: self.__step)
|
|
242
|
-
|
|
252
|
+
|
|
243
253
|
def __len__(self):
|
|
244
|
-
return 1 + int(round((self.max - self.min) / self.step))
|
|
245
|
-
|
|
254
|
+
return 1 + int(round((self.max - self.min) / self.step)) # includes [min,max]
|
|
255
|
+
|
|
246
256
|
@property
|
|
247
257
|
def range(self):
|
|
248
258
|
"""Index range."""
|
|
249
259
|
return np.arange(self.min, self.max + self.step, self.step)
|
|
250
|
-
|
|
260
|
+
|
|
251
261
|
@range.setter
|
|
252
262
|
def range(self, v):
|
|
253
263
|
assert v is None or len(v) <= 3, "The range must be of length <= 3 or None"
|
|
@@ -257,8 +267,8 @@ class LParam(Param):
|
|
|
257
267
|
self.__max = v[1]
|
|
258
268
|
self.__step = v[2] if len(v) > 2 else 1
|
|
259
269
|
for knob in self.knobs:
|
|
260
|
-
knob.update_range()
|
|
261
|
-
|
|
270
|
+
knob.update_range() # linear range of related knobs
|
|
271
|
+
|
|
262
272
|
@property
|
|
263
273
|
def index(self):
|
|
264
274
|
"""A knob index -> value
|
|
@@ -267,19 +277,19 @@ class LParam(Param):
|
|
|
267
277
|
v = self.value
|
|
268
278
|
if np.isnan(v) or np.isinf(v):
|
|
269
279
|
return -1
|
|
270
|
-
return int(round((
|
|
271
|
-
|
|
280
|
+
return int(round((v - self.min) / self.step))
|
|
281
|
+
|
|
272
282
|
@index.setter
|
|
273
283
|
def index(self, j):
|
|
274
284
|
self.value = self.min + j * self.step
|
|
275
285
|
|
|
276
286
|
|
|
277
287
|
## --------------------------------
|
|
278
|
-
## Knob unit for Parameter Control
|
|
288
|
+
## Knob unit for Parameter Control.
|
|
279
289
|
## --------------------------------
|
|
280
290
|
|
|
281
291
|
class Knob(wx.Panel):
|
|
282
|
-
"""Parameter controller unit
|
|
292
|
+
"""Parameter controller unit.
|
|
283
293
|
|
|
284
294
|
In addition to direct key input to the textctrl,
|
|
285
295
|
[up][down][wheelup][wheeldown] keys can be used,
|
|
@@ -287,41 +297,47 @@ class Knob(wx.Panel):
|
|
|
287
297
|
[Mbutton] resets to the std. value if it exists.
|
|
288
298
|
|
|
289
299
|
Args:
|
|
290
|
-
param
|
|
291
|
-
type
|
|
292
|
-
style
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
h : height of widget (defaults to 22)
|
|
300
|
+
param: <Param> or <LParam> object
|
|
301
|
+
type: control type (slider[*], [hv]spin, choice, None)
|
|
302
|
+
style: style of label
|
|
303
|
+
None -> static text (default)
|
|
304
|
+
button -> label with flat button
|
|
305
|
+
checkbox -> label with checkbox
|
|
306
|
+
cw: width of control
|
|
307
|
+
lw: width of label
|
|
308
|
+
tw: width of textbox
|
|
309
|
+
h: height of widget (defaults to 22)
|
|
301
310
|
"""
|
|
302
311
|
@property
|
|
303
312
|
def param(self):
|
|
304
313
|
"""Param object referred from knobs."""
|
|
305
314
|
return self.__par
|
|
306
|
-
|
|
315
|
+
|
|
307
316
|
@param.setter
|
|
308
317
|
def param(self, v):
|
|
309
318
|
self.__par.knobs.remove(self)
|
|
310
319
|
self.__par = v
|
|
311
320
|
self.__par.knobs.append(self)
|
|
312
321
|
self.update_range()
|
|
313
|
-
self.
|
|
314
|
-
|
|
322
|
+
self.update_control()
|
|
323
|
+
|
|
324
|
+
@property
|
|
325
|
+
def button(self):
|
|
326
|
+
if isinstance(self._label, pb.PlateButton):
|
|
327
|
+
return self._label
|
|
328
|
+
|
|
329
|
+
@property
|
|
330
|
+
def control(self):
|
|
331
|
+
return self._ctrl
|
|
332
|
+
|
|
315
333
|
def __init__(self, parent, param, type=None,
|
|
316
334
|
style=None, cw=-1, lw=-1, tw=-1, h=22, **kwargs):
|
|
317
335
|
wx.Panel.__init__(self, parent, **kwargs)
|
|
318
336
|
|
|
319
|
-
assert isinstance(param, Param)
|
|
320
|
-
"Argument `param` must be an instance of Param"
|
|
337
|
+
assert isinstance(param, Param), "Argument `param` must be an instance of Param"
|
|
321
338
|
|
|
322
|
-
self.__bit = 1
|
|
323
339
|
self.__par = param
|
|
324
|
-
self.__par.knobs.append(self)
|
|
340
|
+
self.__par.knobs.append(self) # パラメータの関連付けを行う
|
|
325
341
|
|
|
326
342
|
if not type:
|
|
327
343
|
type = 'slider'
|
|
@@ -336,211 +352,213 @@ class Knob(wx.Panel):
|
|
|
336
352
|
label = self.__par.name + ' '
|
|
337
353
|
|
|
338
354
|
if style == 'chkbox' or style == 'checkbox':
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
self.
|
|
342
|
-
self.label.Bind(wx.EVT_CHECKBOX, self.OnCheck)
|
|
355
|
+
## Keep margin for the checkbox: lw += 16
|
|
356
|
+
self._label = wx.CheckBox(self, label=label, size=(lw,-1))
|
|
357
|
+
self._label.Bind(wx.EVT_CHECKBOX, self.OnCheck)
|
|
343
358
|
elif style == 'button':
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
self.label.Bind(wx.EVT_BUTTON, self.OnPress)
|
|
359
|
+
## Keep margin for the button: lw += 16
|
|
360
|
+
self._label = pb.PlateButton(self, label=label, size=(lw,-1),
|
|
361
|
+
style=pb.PB_STYLE_DEFAULT|pb.PB_STYLE_SQUARE)
|
|
362
|
+
self._label.Bind(wx.EVT_BUTTON, self.OnPress)
|
|
349
363
|
elif not style:
|
|
350
|
-
self.
|
|
364
|
+
self._label = wx.StaticText(self, label=label, size=(lw,-1))
|
|
351
365
|
else:
|
|
352
|
-
raise Exception("unknown style: {!r}"
|
|
366
|
+
raise Exception(f"unknown style: {style!r}")
|
|
353
367
|
|
|
354
|
-
self.
|
|
355
|
-
self.
|
|
356
|
-
self.
|
|
368
|
+
self._label.Bind(wx.EVT_MIDDLE_DOWN, lambda v: self.__par.reset())
|
|
369
|
+
self._label.SetToolTip(self.__par._tooltip)
|
|
370
|
+
self._label.Enable(lw) # skip focus
|
|
357
371
|
|
|
358
|
-
self.
|
|
359
|
-
self.
|
|
360
|
-
self.
|
|
361
|
-
self.
|
|
362
|
-
self.
|
|
363
|
-
self.
|
|
364
|
-
self.
|
|
372
|
+
self._text = wx.TextCtrl(self, size=(tw,h), style=wx.TE_PROCESS_ENTER)
|
|
373
|
+
self._text.Bind(wx.EVT_TEXT_ENTER, self.OnTextEnter)
|
|
374
|
+
self._text.Bind(wx.EVT_KILL_FOCUS, self.OnTextExit)
|
|
375
|
+
self._text.Bind(wx.EVT_KEY_DOWN, self.OnTextKeyDown)
|
|
376
|
+
self._text.Bind(wx.EVT_KEY_UP, self.OnTextKeyUp)
|
|
377
|
+
self._text.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
|
|
378
|
+
self._text.Bind(wx.EVT_MIDDLE_DOWN, lambda v: self.__par.reset())
|
|
365
379
|
|
|
366
|
-
self.
|
|
380
|
+
self._text.Enable(tw) # skip focus
|
|
367
381
|
|
|
368
382
|
if type == 'slider':
|
|
369
|
-
self.
|
|
370
|
-
self.
|
|
371
|
-
self.
|
|
372
|
-
self.
|
|
383
|
+
self._ctrl = wx.Slider(self, size=(cw,h), style=wx.SL_HORIZONTAL)
|
|
384
|
+
self._ctrl.Bind(wx.EVT_SCROLL_CHANGED, self.OnScroll)
|
|
385
|
+
self._ctrl.Bind(wx.EVT_KEY_DOWN, self.OnCtrlKeyDown)
|
|
386
|
+
self._ctrl.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
|
|
373
387
|
|
|
374
388
|
elif type == 'slider*':
|
|
375
|
-
self.
|
|
376
|
-
self.
|
|
377
|
-
self.
|
|
378
|
-
self.
|
|
379
|
-
self.
|
|
389
|
+
self._ctrl = wx.Slider(self, size=(cw,h), style=wx.SL_HORIZONTAL)
|
|
390
|
+
self._ctrl.Bind(wx.EVT_SCROLL, self.OnScroll) # called while dragging
|
|
391
|
+
self._ctrl.Bind(wx.EVT_SCROLL_CHANGED, lambda v: None) # pass no action
|
|
392
|
+
self._ctrl.Bind(wx.EVT_KEY_DOWN, self.OnCtrlKeyDown)
|
|
393
|
+
self._ctrl.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
|
|
380
394
|
|
|
381
|
-
elif type == 'spin' or type =='hspin':
|
|
382
|
-
self.
|
|
383
|
-
self.
|
|
384
|
-
self.
|
|
395
|
+
elif type == 'spin' or type == 'hspin':
|
|
396
|
+
self._ctrl = wx.SpinButton(self, size=(cw,h), style=wx.SP_HORIZONTAL)
|
|
397
|
+
self._ctrl.Bind(wx.EVT_SPIN, self.OnScroll)
|
|
398
|
+
self._ctrl.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
|
|
385
399
|
|
|
386
400
|
elif type == 'vspin':
|
|
387
|
-
self.
|
|
388
|
-
self.
|
|
389
|
-
self.
|
|
401
|
+
self._ctrl = wx.SpinButton(self, size=(cw,h), style=wx.SP_VERTICAL)
|
|
402
|
+
self._ctrl.Bind(wx.EVT_SPIN, self.OnScroll)
|
|
403
|
+
self._ctrl.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
|
|
390
404
|
|
|
391
405
|
elif type == 'choice':
|
|
392
|
-
self.
|
|
393
|
-
self.
|
|
394
|
-
self.
|
|
395
|
-
self.
|
|
406
|
+
self._ctrl = wx.Choice(self, size=(cw,h))
|
|
407
|
+
self._ctrl.Bind(wx.EVT_CHOICE, self.OnScroll)
|
|
408
|
+
self._ctrl.SetValue = self._ctrl.SetSelection # setter of choice
|
|
409
|
+
self._ctrl.GetValue = self._ctrl.GetSelection # getter (ditto)
|
|
396
410
|
|
|
397
411
|
else:
|
|
398
|
-
raise Exception("unknown type: {!r}"
|
|
412
|
+
raise Exception(f"unknown type: {type!r}")
|
|
399
413
|
|
|
400
|
-
self.
|
|
401
|
-
self.
|
|
414
|
+
self._ctrl.Bind(wx.EVT_MIDDLE_DOWN, lambda v: self.__par.reset())
|
|
415
|
+
self._ctrl.Enable(cw) # skip focus
|
|
402
416
|
|
|
403
417
|
c = (cw and type != 'vspin')
|
|
404
418
|
self.SetSizer(
|
|
405
419
|
pack(self, (
|
|
406
|
-
(self.
|
|
407
|
-
(self.
|
|
408
|
-
(self.
|
|
420
|
+
(self._label, 0, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT, lw and 1),
|
|
421
|
+
(self._text, 0, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT, tw and 1),
|
|
422
|
+
(self._ctrl, c, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT, cw and 1),
|
|
409
423
|
))
|
|
410
424
|
)
|
|
411
425
|
self.update_range()
|
|
412
|
-
self.
|
|
426
|
+
self.update_control()
|
|
413
427
|
|
|
414
428
|
self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
|
|
415
|
-
|
|
429
|
+
|
|
416
430
|
def OnDestroy(self, evt):
|
|
417
|
-
self.__par.knobs.remove(self)
|
|
431
|
+
self.__par.knobs.remove(self) # パラメータの関連付けを解除する
|
|
418
432
|
evt.Skip()
|
|
419
|
-
|
|
433
|
+
|
|
420
434
|
def update_range(self):
|
|
421
435
|
"""Called when range is being changed (internal use only)."""
|
|
422
436
|
v = self.__par
|
|
423
|
-
if isinstance(self.
|
|
437
|
+
if isinstance(self._ctrl, wx.Choice): # <wx.Choice>
|
|
424
438
|
items = [v.__str__(x) for x in v.range]
|
|
425
|
-
if items != self.
|
|
426
|
-
self.
|
|
427
|
-
self.
|
|
439
|
+
if items != self._ctrl.Items:
|
|
440
|
+
self._ctrl.SetItems(items)
|
|
441
|
+
self._ctrl.SetStringSelection(str(v))
|
|
428
442
|
else:
|
|
429
|
-
self.
|
|
430
|
-
|
|
443
|
+
self._ctrl.SetRange(0, len(v)-1) # <wx.Slider> <wx.SpinButton>
|
|
444
|
+
|
|
431
445
|
def update_label(self):
|
|
432
446
|
"""Called when label is being changed (internal use only)."""
|
|
433
447
|
v = self.__par
|
|
434
|
-
if isinstance(self.
|
|
435
|
-
self.
|
|
448
|
+
if isinstance(self._label, wx.CheckBox):
|
|
449
|
+
self._label.SetValue(v.check)
|
|
436
450
|
|
|
437
451
|
t = ' ' if np.isnan(v.std_value) or v.value == v.std_value else '*'
|
|
438
|
-
self.
|
|
439
|
-
self.
|
|
452
|
+
self._label.SetLabel(v.name + t)
|
|
453
|
+
self._label.Refresh()
|
|
440
454
|
self.Refresh()
|
|
441
|
-
|
|
442
|
-
def
|
|
455
|
+
|
|
456
|
+
def update_control(self, valid=True, notify=False):
|
|
443
457
|
"""Called when value is being changed (internal use only)."""
|
|
444
458
|
v = self.__par
|
|
445
|
-
self.
|
|
446
|
-
wx.CallAfter(self.
|
|
459
|
+
self._ctrl.SetValue(v.index)
|
|
460
|
+
wx.CallAfter(self._text.SetValue, str(v)) # for wxAssertionError
|
|
447
461
|
if valid:
|
|
448
462
|
if notify:
|
|
449
|
-
if self.
|
|
463
|
+
if self._text.BackgroundColour != '#ffff80':
|
|
450
464
|
wx.CallAfter(wx.CallLater, 1000,
|
|
451
465
|
self.set_textcolour, '#ffffff')
|
|
452
|
-
self.set_textcolour('#ffff80')
|
|
466
|
+
self.set_textcolour('#ffff80') # light-yellow
|
|
453
467
|
else:
|
|
454
|
-
self.set_textcolour('#ffffff')
|
|
468
|
+
self.set_textcolour('#ffffff') # True: white
|
|
455
469
|
else:
|
|
456
|
-
self.set_textcolour('#ffffff')
|
|
470
|
+
self.set_textcolour('#ffffff') # True: white
|
|
457
471
|
elif valid is None:
|
|
458
|
-
self.set_textcolour('#ffff80')
|
|
472
|
+
self.set_textcolour('#ffff80') # None: light-yellow
|
|
459
473
|
else:
|
|
460
|
-
self.set_textcolour('#ff8080')
|
|
474
|
+
self.set_textcolour('#ff8080') # False: light-red
|
|
461
475
|
self.update_label()
|
|
462
|
-
|
|
476
|
+
|
|
463
477
|
def set_textcolour(self, c):
|
|
464
478
|
if self:
|
|
465
|
-
self.
|
|
466
|
-
self.
|
|
467
|
-
|
|
468
|
-
def
|
|
479
|
+
self._text.BackgroundColour = c
|
|
480
|
+
self._text.Refresh()
|
|
481
|
+
|
|
482
|
+
def _shift_control(self, evt, bit):
|
|
483
|
+
## Called when a key/mouse wheel is pressed/scrolled (internal use only).
|
|
484
|
+
## In addition to direct key input to the textctrl,
|
|
485
|
+
## [up][down][wheelup][wheeldown] keys can be used,
|
|
486
|
+
## with modifiers S- 2x, C- 16x, and M- 256x steps.
|
|
469
487
|
if bit:
|
|
470
488
|
if evt.ShiftDown(): bit *= 2
|
|
471
489
|
if evt.ControlDown(): bit *= 16
|
|
472
490
|
if evt.AltDown(): bit *= 256
|
|
473
491
|
v = self.__par
|
|
474
|
-
j = self.
|
|
492
|
+
j = self._ctrl.GetValue() + bit
|
|
475
493
|
if j != v.index:
|
|
476
494
|
v.index = j
|
|
477
495
|
v.reset(v.value)
|
|
478
|
-
|
|
479
|
-
def OnScroll(self, evt):
|
|
496
|
+
|
|
497
|
+
def OnScroll(self, evt): # <wx._core.ScrollEvent> <wx._controls.SpinEvent> <wx._core.CommandEvent>
|
|
480
498
|
v = self.__par
|
|
481
|
-
j = self.
|
|
499
|
+
j = self._ctrl.GetValue()
|
|
482
500
|
if j != v.index:
|
|
483
501
|
v.index = j
|
|
484
502
|
v.reset(v.value)
|
|
485
503
|
evt.Skip()
|
|
486
|
-
|
|
487
|
-
def OnMouseWheel(self, evt):
|
|
488
|
-
self.
|
|
504
|
+
|
|
505
|
+
def OnMouseWheel(self, evt): # <wx._core.MouseEvent>
|
|
506
|
+
self._shift_control(evt, (1 if evt.WheelRotation > 0 else -1))
|
|
489
507
|
evt.Skip(False)
|
|
490
|
-
|
|
491
|
-
def OnCtrlKeyDown(self, evt):
|
|
508
|
+
|
|
509
|
+
def OnCtrlKeyDown(self, evt): # <wx._core.KeyEvent>
|
|
492
510
|
key = evt.GetKeyCode()
|
|
493
|
-
if key == wx.WXK_LEFT: return self.
|
|
494
|
-
if key == wx.WXK_RIGHT: return self.
|
|
511
|
+
if key == wx.WXK_LEFT: return self._shift_control(evt, -1)
|
|
512
|
+
if key == wx.WXK_RIGHT: return self._shift_control(evt, 1)
|
|
495
513
|
|
|
496
514
|
def _focus(c):
|
|
497
|
-
if isinstance(c, Knob) and c.
|
|
498
|
-
c.
|
|
515
|
+
if isinstance(c, Knob) and c._ctrl.IsEnabled():
|
|
516
|
+
c._ctrl.SetFocus()
|
|
499
517
|
return True
|
|
500
518
|
|
|
501
519
|
ls = next(x for x in self.Parent.layout_groups if self in x)
|
|
502
520
|
i = ls.index(self)
|
|
503
521
|
if key == wx.WXK_DOWN: return any(_focus(c) for c in ls[i+1:])
|
|
504
522
|
if key == wx.WXK_UP: return any(_focus(c) for c in ls[i-1::-1])
|
|
505
|
-
|
|
506
|
-
def OnTextKeyUp(self, evt):
|
|
523
|
+
|
|
524
|
+
def OnTextKeyUp(self, evt): # <wx._core.KeyEvent>
|
|
507
525
|
evt.Skip()
|
|
508
|
-
|
|
509
|
-
def OnTextKeyDown(self, evt):
|
|
526
|
+
|
|
527
|
+
def OnTextKeyDown(self, evt): # <wx._core.KeyEvent>
|
|
510
528
|
key = evt.GetKeyCode()
|
|
511
|
-
if key == wx.WXK_DOWN: return self.
|
|
512
|
-
if key == wx.WXK_UP: return self.
|
|
529
|
+
if key == wx.WXK_DOWN: return self._shift_control(evt, -1)
|
|
530
|
+
if key == wx.WXK_UP: return self._shift_control(evt, 1)
|
|
513
531
|
if key == wx.WXK_ESCAPE:
|
|
514
|
-
self.__par.reset(self.__par.value, internal_callback=None)
|
|
532
|
+
self.__par.reset(self.__par.value, internal_callback=None) # restore value
|
|
515
533
|
evt.Skip()
|
|
516
|
-
|
|
517
|
-
def OnTextEnter(self, evt):
|
|
534
|
+
|
|
535
|
+
def OnTextEnter(self, evt): # <wx._core.CommandEvent>
|
|
518
536
|
evt.Skip()
|
|
519
|
-
x = self.
|
|
537
|
+
x = self._text.Value.strip()
|
|
520
538
|
self.__par.reset(x)
|
|
521
|
-
|
|
522
|
-
def OnTextExit(self, evt):
|
|
523
|
-
x = self.
|
|
539
|
+
|
|
540
|
+
def OnTextExit(self, evt): # <wx._core.FocusEvent>
|
|
541
|
+
x = self._text.Value.strip()
|
|
524
542
|
if x != str(self.__par):
|
|
525
543
|
self.__par.reset(x)
|
|
526
544
|
evt.Skip()
|
|
527
|
-
|
|
528
|
-
def OnCheck(self, evt):
|
|
545
|
+
|
|
546
|
+
def OnCheck(self, evt): # <wx._core.CommandEvent>
|
|
529
547
|
self.__par.check = evt.IsChecked()
|
|
530
548
|
evt.Skip()
|
|
531
|
-
|
|
532
|
-
def OnPress(self, evt):
|
|
549
|
+
|
|
550
|
+
def OnPress(self, evt): # <wx._core.CommandEvent>
|
|
533
551
|
self.__par.callback('updated', self.__par)
|
|
534
552
|
evt.Skip()
|
|
535
|
-
|
|
553
|
+
|
|
536
554
|
def Enable(self, p=True):
|
|
537
|
-
self.
|
|
538
|
-
self.
|
|
539
|
-
self.
|
|
555
|
+
self._label.Enable(p)
|
|
556
|
+
self._ctrl.Enable(p)
|
|
557
|
+
self._text.Enable(p)
|
|
540
558
|
|
|
541
559
|
|
|
542
560
|
class KnobCtrlPanel(scrolled.ScrolledPanel):
|
|
543
|
-
"""Scrollable Control Panel
|
|
561
|
+
"""Scrollable Control Panel.
|
|
544
562
|
"""
|
|
545
563
|
def __init__(self, *args, **kwargs):
|
|
546
564
|
scrolled.ScrolledPanel.__init__(self, *args, **kwargs)
|
|
@@ -572,81 +590,83 @@ class KnobCtrlPanel(scrolled.ScrolledPanel):
|
|
|
572
590
|
self.Bind(wx.EVT_SCROLLWIN_THUMBRELEASE, self.OnRecalcLayout)
|
|
573
591
|
self.Bind(wx.EVT_MOUSEWHEEL, self.OnRecalcLayout)
|
|
574
592
|
self.Bind(wx.EVT_LEFT_DOWN, self.OnRecalcLayout)
|
|
575
|
-
|
|
576
|
-
def OnRecalcLayout(self, evt):
|
|
593
|
+
|
|
594
|
+
def OnRecalcLayout(self, evt): # <wx._core.ScrollWinEvent>
|
|
577
595
|
self.Layout()
|
|
578
596
|
evt.Skip()
|
|
579
|
-
|
|
580
|
-
def OnToggleFold(self, evt):
|
|
597
|
+
|
|
598
|
+
def OnToggleFold(self, evt): # <wx._core.MouseEvent>
|
|
581
599
|
x, y = evt.Position
|
|
582
|
-
for child in self.Sizer.Children:
|
|
600
|
+
for child in self.Sizer.Children: # child <wx._core.SizerItem>
|
|
583
601
|
if child.IsShown():
|
|
584
602
|
obj = child.Sizer
|
|
585
603
|
if isinstance(obj, wx.StaticBoxSizer):
|
|
586
604
|
cx, cy = obj.Position
|
|
587
605
|
if cx < x < cx + obj.Size[0] and cy < y < cy+22:
|
|
588
|
-
for cc in obj.Children:
|
|
606
|
+
for cc in obj.Children: # child of child <wx._core.SizerItem>
|
|
589
607
|
cc.Show(not cc.IsShown())
|
|
590
608
|
self.Layout()
|
|
609
|
+
self.SendSizeEvent()
|
|
591
610
|
break
|
|
592
611
|
evt.Skip()
|
|
593
|
-
|
|
612
|
+
|
|
594
613
|
## --------------------------------
|
|
595
|
-
## Layout commands and attributes
|
|
614
|
+
## Layout commands and attributes.
|
|
596
615
|
## --------------------------------
|
|
597
616
|
@property
|
|
598
617
|
def layout_groups(self):
|
|
599
618
|
return self.__groups
|
|
600
|
-
|
|
619
|
+
|
|
601
620
|
def is_enabled(self, groupid, pred=all):
|
|
602
621
|
return pred(win.Enabled for win in self.__groups[groupid])
|
|
603
|
-
|
|
622
|
+
|
|
604
623
|
def enable(self, groupid, p=True):
|
|
605
|
-
for win in self.__groups[groupid]:
|
|
624
|
+
for win in self.__groups[groupid]: # child could be deep nesting
|
|
606
625
|
win.Enable(p)
|
|
607
|
-
|
|
626
|
+
|
|
608
627
|
def is_shown(self, groupid):
|
|
609
|
-
|
|
610
|
-
|
|
628
|
+
# child = self.Sizer.Children[groupid]
|
|
629
|
+
# return child.IsShown()
|
|
611
630
|
return self.Sizer.IsShown(groupid % len(self.__groups))
|
|
612
|
-
|
|
631
|
+
|
|
613
632
|
def show(self, groupid, p=True):
|
|
614
633
|
"""Show/hide all including the box."""
|
|
615
|
-
|
|
616
|
-
|
|
634
|
+
# child = self.Sizer.Children[groupid]
|
|
635
|
+
# child.Show(p)
|
|
617
636
|
self.Sizer.Show(groupid % len(self.__groups), p)
|
|
618
637
|
self.Layout()
|
|
619
|
-
|
|
638
|
+
|
|
620
639
|
def is_folded(self, groupid):
|
|
621
640
|
child = self.Sizer.Children[groupid]
|
|
622
641
|
return not any(cc.IsShown() for cc in child.Sizer.Children)
|
|
623
|
-
|
|
642
|
+
|
|
624
643
|
def fold(self, groupid, p=True):
|
|
625
644
|
"""Fold/unfold the boxed group."""
|
|
626
645
|
child = self.Sizer.Children[groupid]
|
|
627
646
|
if isinstance(child.Sizer, wx.StaticBoxSizer) and child.IsShown():
|
|
628
|
-
for cc in child.Sizer.Children:
|
|
647
|
+
for cc in child.Sizer.Children: # child of child <wx._core.SizerItem>
|
|
629
648
|
cc.Show(not p)
|
|
630
649
|
self.Layout()
|
|
631
|
-
|
|
650
|
+
|
|
632
651
|
def layout(self, items, title=None,
|
|
633
652
|
row=0, expand=0, border=2, hspacing=1, vspacing=1,
|
|
634
653
|
show=True, visible=True, align=wx.ALIGN_LEFT, **kwargs):
|
|
635
654
|
"""Do layout (cf. Layout).
|
|
636
655
|
|
|
637
656
|
Args:
|
|
638
|
-
items
|
|
639
|
-
title
|
|
640
|
-
row
|
|
641
|
-
expand
|
|
642
|
-
(
|
|
643
|
-
(
|
|
644
|
-
|
|
657
|
+
items: list of Params, wx.Objects, tuple of sizing, or None
|
|
658
|
+
title: box header string (default is None - no box)
|
|
659
|
+
row: number of row to arange widgets
|
|
660
|
+
expand: expansion flag
|
|
661
|
+
- (0) fixed size
|
|
662
|
+
- (1) to expand horizontally
|
|
663
|
+
- (2) to expand horizontally and vertically
|
|
664
|
+
border: size of outline border
|
|
645
665
|
hspacing: horizontal spacing among packed objs inside the group
|
|
646
666
|
vspacing: vertical spacing among packed objs inside the group
|
|
647
|
-
show
|
|
648
|
-
visible
|
|
649
|
-
align
|
|
667
|
+
show: Fold or unfold the boxed group.
|
|
668
|
+
visible: Hide the boxed group if False.
|
|
669
|
+
align: alignment flag (wx.ALIGN_*) default is ALIGN_LEFT
|
|
650
670
|
**kwargs: extra keyword arguments given for Knob
|
|
651
671
|
"""
|
|
652
672
|
objs = [Knob(self, c, **kwargs) if isinstance(c, Param)
|
|
@@ -666,70 +686,74 @@ class KnobCtrlPanel(scrolled.ScrolledPanel):
|
|
|
666
686
|
|
|
667
687
|
self.Sizer.Add(sizer, expand>1, p | wx.ALL, border)
|
|
668
688
|
|
|
669
|
-
## Register
|
|
670
|
-
def _flatiter(
|
|
671
|
-
for c in
|
|
689
|
+
## Register objects and parameter groups.
|
|
690
|
+
def _flatiter(objects):
|
|
691
|
+
for c in objects:
|
|
672
692
|
if isinstance(c, tuple):
|
|
673
693
|
yield from _flatiter(c)
|
|
674
694
|
elif isinstance(c, wx.Object):
|
|
675
695
|
yield c
|
|
676
696
|
self.__groups.append(list(_flatiter(objs)))
|
|
677
697
|
|
|
678
|
-
|
|
679
|
-
|
|
698
|
+
## Parameters : Knob.param or widgets that have a `value`.
|
|
699
|
+
def _variter(objects):
|
|
700
|
+
for c in objects:
|
|
680
701
|
if isinstance(c, Knob):
|
|
681
702
|
yield c.param
|
|
682
703
|
elif hasattr(c, 'value'):
|
|
683
704
|
yield c
|
|
684
705
|
self.__params.append(list(_variter(objs)))
|
|
685
706
|
|
|
686
|
-
## Set appearance of the layout group
|
|
707
|
+
## Set appearance of the layout group.
|
|
687
708
|
self.show(-1, visible)
|
|
688
709
|
self.fold(-1, not show)
|
|
689
710
|
self.Sizer.Fit(self)
|
|
690
|
-
|
|
711
|
+
|
|
712
|
+
return self.__groups[-1]
|
|
713
|
+
|
|
691
714
|
## --------------------------------
|
|
692
|
-
##
|
|
715
|
+
## 外部入出力/クリップボード通信.
|
|
693
716
|
## --------------------------------
|
|
694
717
|
@property
|
|
695
718
|
def parameters(self):
|
|
696
719
|
return [p.value for p in self.get_params()]
|
|
697
|
-
|
|
720
|
+
|
|
698
721
|
@parameters.setter
|
|
699
722
|
def parameters(self, v):
|
|
700
723
|
self.set_params(v)
|
|
701
|
-
|
|
724
|
+
|
|
702
725
|
def get_params(self, checked_only=False):
|
|
703
726
|
params = chain(*self.__params)
|
|
704
727
|
if not checked_only:
|
|
705
728
|
return params
|
|
706
729
|
return filter(lambda c: getattr(c, 'check', None), params)
|
|
707
|
-
|
|
708
|
-
def set_params(self, argv
|
|
730
|
+
|
|
731
|
+
def set_params(self, argv, checked_only=False):
|
|
709
732
|
params = self.get_params(checked_only)
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
733
|
+
for p, v in zip(params, argv):
|
|
734
|
+
try:
|
|
735
|
+
p.reset(v) # eval v:str -> value
|
|
736
|
+
except AttributeError:
|
|
737
|
+
p.value = v
|
|
738
|
+
except Exception as e:
|
|
739
|
+
print(f"- Failed to eval {v};", e)
|
|
740
|
+
|
|
741
|
+
def reset_params(self, checked_only=False):
|
|
742
|
+
params = self.get_params(checked_only)
|
|
743
|
+
for p in params:
|
|
744
|
+
try:
|
|
745
|
+
p.reset()
|
|
746
|
+
except (AttributeError, TypeError):
|
|
747
|
+
## TypeError might occur if p.reset(v) is called with
|
|
748
|
+
## missing 1 required positional argument.
|
|
749
|
+
pass
|
|
750
|
+
|
|
727
751
|
def copy_to_clipboard(self, checked_only=False):
|
|
728
752
|
params = self.get_params(checked_only)
|
|
729
753
|
text = '\t'.join(str(p) if isinstance(p, Param) else
|
|
730
754
|
str(p.value) for p in params)
|
|
731
755
|
Clipboard.write(text)
|
|
732
|
-
|
|
756
|
+
|
|
733
757
|
def paste_from_clipboard(self, checked_only=False):
|
|
734
758
|
text = Clipboard.read()
|
|
735
759
|
if text:
|
|
@@ -745,11 +769,24 @@ class ControlPanel(CtrlInterface, KnobCtrlPanel):
|
|
|
745
769
|
|
|
746
770
|
|
|
747
771
|
class Clipboard:
|
|
748
|
-
"""Clipboard interface of text and image
|
|
772
|
+
"""Clipboard interface of text and image.
|
|
749
773
|
|
|
750
774
|
This does not work unless wx.App instance exists.
|
|
751
775
|
The clipboard data cannot be transferred unless wx.Frame exists.
|
|
752
776
|
"""
|
|
777
|
+
@contextmanager
|
|
778
|
+
@staticmethod
|
|
779
|
+
def istrstream():
|
|
780
|
+
with io.StringIO(Clipboard.read()) as f:
|
|
781
|
+
yield f
|
|
782
|
+
|
|
783
|
+
@contextmanager
|
|
784
|
+
@staticmethod
|
|
785
|
+
def ostrstream():
|
|
786
|
+
with io.StringIO() as f:
|
|
787
|
+
yield f
|
|
788
|
+
Clipboard.write(f.getvalue())
|
|
789
|
+
|
|
753
790
|
@staticmethod
|
|
754
791
|
def read(verbose=False):
|
|
755
792
|
do = wx.TextDataObject()
|
|
@@ -762,7 +799,8 @@ class Clipboard:
|
|
|
762
799
|
return text
|
|
763
800
|
else:
|
|
764
801
|
print("- Unable to open clipboard.")
|
|
765
|
-
|
|
802
|
+
return None
|
|
803
|
+
|
|
766
804
|
@staticmethod
|
|
767
805
|
def write(text, verbose=False):
|
|
768
806
|
do = wx.TextDataObject(str(text))
|
|
@@ -774,7 +812,7 @@ class Clipboard:
|
|
|
774
812
|
print(f"To clipboard:\n{text}")
|
|
775
813
|
else:
|
|
776
814
|
print("- Unable to open clipboard.")
|
|
777
|
-
|
|
815
|
+
|
|
778
816
|
@staticmethod
|
|
779
817
|
def imread(verbose=False):
|
|
780
818
|
do = wx.BitmapDataObject()
|
|
@@ -784,30 +822,31 @@ class Clipboard:
|
|
|
784
822
|
bmp = do.GetBitmap()
|
|
785
823
|
else:
|
|
786
824
|
print("- Unable to open clipboard.")
|
|
787
|
-
return
|
|
825
|
+
return None
|
|
788
826
|
try:
|
|
789
|
-
## Convert bmp --> buf
|
|
827
|
+
## Convert bmp --> buf.
|
|
790
828
|
img = bmp.ConvertToImage()
|
|
791
|
-
buf = np.array(img.GetDataBuffer())
|
|
829
|
+
buf = np.array(img.GetDataBuffer()) # Do copy, don't ref.
|
|
792
830
|
if verbose:
|
|
793
831
|
print("From clipboard: {:.1f} Mb data read.".format(buf.nbytes/1e6))
|
|
794
832
|
w, h = img.GetSize()
|
|
795
833
|
return buf.reshape(h, w, 3)
|
|
796
834
|
except Exception:
|
|
797
|
-
print("-
|
|
798
|
-
|
|
835
|
+
print("- Contents of the clipboard are not images.")
|
|
836
|
+
return None
|
|
837
|
+
|
|
799
838
|
@staticmethod
|
|
800
839
|
def imwrite(buf, verbose=False):
|
|
801
840
|
try:
|
|
802
|
-
## Convert buf --> bmp
|
|
841
|
+
## Convert buf --> bmp.
|
|
803
842
|
h, w = buf.shape[:2]
|
|
804
843
|
if buf.ndim < 3:
|
|
805
|
-
|
|
806
|
-
buf = buf.repeat(3, axis=1)
|
|
844
|
+
# buf = np.array([buf] * 3).transpose((1,2,0)) # convert to gray bitmap
|
|
845
|
+
buf = buf.repeat(3, axis=1) # Convert to gray bitmap.
|
|
807
846
|
img = wx.Image(w, h, buf.tobytes())
|
|
808
847
|
bmp = img.ConvertToBitmap()
|
|
809
848
|
except Exception:
|
|
810
|
-
print("-
|
|
849
|
+
print("- Argument 'buf' is not a 2d array.")
|
|
811
850
|
return
|
|
812
851
|
do = wx.BitmapDataObject(bmp)
|
|
813
852
|
if wx.TheClipboard.Open():
|
|
@@ -821,17 +860,17 @@ class Clipboard:
|
|
|
821
860
|
|
|
822
861
|
|
|
823
862
|
## --------------------------------
|
|
824
|
-
## Wx custom controls and bitmaps
|
|
863
|
+
## Wx custom controls and bitmaps.
|
|
825
864
|
## --------------------------------
|
|
826
865
|
|
|
827
866
|
class Icon(wx.Bitmap):
|
|
828
|
-
"""
|
|
867
|
+
"""Return an iconic bitmap with the specified size (w, h).
|
|
829
868
|
|
|
830
869
|
The key is either Icon.provided_arts or Icon.custom_images key.
|
|
831
870
|
If the key is empty it returns a transparent bitmap, otherwise NullBitmap.
|
|
832
871
|
|
|
833
872
|
Note:
|
|
834
|
-
A null (0-shaped) bitmap fails with AssertionError from 4.1.1
|
|
873
|
+
A null (0-shaped) bitmap fails with AssertionError from wx ver 4.1.1.
|
|
835
874
|
"""
|
|
836
875
|
provided_arts = {
|
|
837
876
|
'cut' : wx.ART_CUT,
|
|
@@ -855,8 +894,8 @@ class Icon(wx.Bitmap):
|
|
|
855
894
|
'!!!' : wx.ART_ERROR,
|
|
856
895
|
'+' : wx.ART_PLUS,
|
|
857
896
|
'-' : wx.ART_MINUS,
|
|
858
|
-
'x' : wx.ART_DELETE,
|
|
859
|
-
't' : wx.ART_TICK_MARK,
|
|
897
|
+
# 'x' : wx.ART_DELETE,
|
|
898
|
+
# 't' : wx.ART_TICK_MARK,
|
|
860
899
|
'~' : wx.ART_GO_HOME,
|
|
861
900
|
'undo' : wx.ART_UNDO,
|
|
862
901
|
'redo' : wx.ART_REDO,
|
|
@@ -868,10 +907,10 @@ class Icon(wx.Bitmap):
|
|
|
868
907
|
'->|' : wx.ART_GOTO_LAST,
|
|
869
908
|
}
|
|
870
909
|
custom_images = {
|
|
871
|
-
k:v for k, v in vars(images).items()
|
|
872
|
-
|
|
910
|
+
k: v for k, v in vars(images).items()
|
|
911
|
+
if isinstance(v, wx.lib.embeddedimage.PyEmbeddedImage)
|
|
873
912
|
}
|
|
874
|
-
|
|
913
|
+
|
|
875
914
|
def __init__(self, *args, **kwargs):
|
|
876
915
|
try:
|
|
877
916
|
bmp = Icon._getBitmap1(*args, **kwargs)
|
|
@@ -881,15 +920,25 @@ class Icon(wx.Bitmap):
|
|
|
881
920
|
|
|
882
921
|
@staticmethod
|
|
883
922
|
def _getBitmap1(key, size=None):
|
|
923
|
+
if not isinstance(size, (type(None), tuple, wx.Size)):
|
|
924
|
+
raise TypeError("invalid size type")
|
|
925
|
+
|
|
884
926
|
if isinstance(key, wx.Bitmap):
|
|
885
927
|
if size and key.Size != size:
|
|
886
928
|
key = (key.ConvertToImage()
|
|
887
929
|
.Scale(*size, wx.IMAGE_QUALITY_NEAREST)
|
|
888
930
|
.ConvertToBitmap())
|
|
889
|
-
return key
|
|
890
|
-
if
|
|
931
|
+
return key # <wx.Bitmap>
|
|
932
|
+
if size is None:
|
|
891
933
|
size = (16, 16)
|
|
892
934
|
if key:
|
|
935
|
+
## Returns a bitmap of provided artwork.
|
|
936
|
+
## Note: The result could be a zero-shaped bitmap.
|
|
937
|
+
try:
|
|
938
|
+
if re.match("bullet(.*)", key):
|
|
939
|
+
return eval(f"Icon.{key}") # -> Icon.bullet(*v, **kw)
|
|
940
|
+
except TypeError:
|
|
941
|
+
pass
|
|
893
942
|
try:
|
|
894
943
|
art = Icon.custom_images.get(key)
|
|
895
944
|
bmp = art.GetBitmap()
|
|
@@ -897,17 +946,16 @@ class Icon(wx.Bitmap):
|
|
|
897
946
|
art = Icon.provided_arts.get(key)
|
|
898
947
|
bmp = wx.ArtProvider.GetBitmap(art or key, wx.ART_OTHER, size)
|
|
899
948
|
return bmp
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
949
|
+
if key == '':
|
|
950
|
+
## Returns dummy-sized blank bitmap.
|
|
951
|
+
## Note: A zero-shaped bitmap fails with AssertionError since wx ver 4.1.1.
|
|
903
952
|
bmp = wx.Bitmap(size)
|
|
904
953
|
with wx.MemoryDC(bmp) as dc:
|
|
905
954
|
dc.SetBackground(wx.Brush('black'))
|
|
906
955
|
dc.Clear()
|
|
907
|
-
bmp.SetMaskColour('black')
|
|
956
|
+
bmp.SetMaskColour('black')
|
|
908
957
|
return bmp
|
|
909
|
-
|
|
910
|
-
return wx.NullBitmap # The standard wx controls accept this,
|
|
958
|
+
return wx.NullBitmap # The standard wx controls accept this.
|
|
911
959
|
|
|
912
960
|
@staticmethod
|
|
913
961
|
def _getBitmap2(back, fore, size=None, subsize=3/4):
|
|
@@ -917,17 +965,19 @@ class Icon(wx.Bitmap):
|
|
|
917
965
|
subsize = wx.Size(size) * subsize
|
|
918
966
|
back = Icon._getBitmap1(back, size)
|
|
919
967
|
fore = Icon._getBitmap1(fore, subsize)
|
|
968
|
+
if back.Size == (0, 0) or fore.Size == (0, 0):
|
|
969
|
+
return back
|
|
920
970
|
x = size[0] - subsize[0]
|
|
921
971
|
y = size[1] - subsize[1]
|
|
922
972
|
with wx.MemoryDC(back) as dc:
|
|
923
|
-
|
|
924
|
-
|
|
973
|
+
# dc = wx.GCDC(dc)
|
|
974
|
+
# dc.DrawBitmap(fore, x, y, useMask=True)
|
|
925
975
|
gc = wx.GraphicsContext.Create(dc)
|
|
926
976
|
gc.DrawBitmap(fore, x, y, *subsize)
|
|
927
977
|
return back
|
|
928
978
|
|
|
929
979
|
@staticmethod
|
|
930
|
-
def bullet(colour,
|
|
980
|
+
def bullet(colour, radius=4, size=None, ec=None):
|
|
931
981
|
if not size:
|
|
932
982
|
size = (16, 16)
|
|
933
983
|
bmp = wx.Bitmap(size)
|
|
@@ -942,7 +992,7 @@ class Icon(wx.Bitmap):
|
|
|
942
992
|
|
|
943
993
|
@staticmethod
|
|
944
994
|
def iconify(icon, w, h):
|
|
945
|
-
## if wx.VERSION >= (4,1,0):
|
|
995
|
+
## if wx.VERSION >= (4,1,0): ...
|
|
946
996
|
try:
|
|
947
997
|
import wx.svg
|
|
948
998
|
import requests
|
|
@@ -957,12 +1007,12 @@ class Icon(wx.Bitmap):
|
|
|
957
1007
|
|
|
958
1008
|
|
|
959
1009
|
class ClassicButton(wx.Button):
|
|
960
|
-
"""
|
|
1010
|
+
"""Classic button.
|
|
961
1011
|
|
|
962
1012
|
Args:
|
|
963
|
-
label
|
|
964
|
-
handler
|
|
965
|
-
icon
|
|
1013
|
+
label: button label
|
|
1014
|
+
handler: event handler when the button is pressed
|
|
1015
|
+
icon: key:str or bitmap for button icon
|
|
966
1016
|
**kwargs: keywords for wx.lib.platebtn.PlateButton
|
|
967
1017
|
"""
|
|
968
1018
|
def __init__(self, parent, label='', handler=None, icon=None, **kwargs):
|
|
@@ -970,19 +1020,18 @@ class ClassicButton(wx.Button):
|
|
|
970
1020
|
|
|
971
1021
|
if handler:
|
|
972
1022
|
self.Bind(wx.EVT_BUTTON, _F(handler))
|
|
973
|
-
|
|
974
|
-
self.SetToolTip(_Tip(handler.__doc__))
|
|
1023
|
+
self.SetToolTip(_Tip(handler.__doc__))
|
|
975
1024
|
if icon:
|
|
976
1025
|
self.SetBitmap(Icon(icon))
|
|
977
1026
|
|
|
978
1027
|
|
|
979
1028
|
class Button(pb.PlateButton):
|
|
980
|
-
"""Flat button
|
|
1029
|
+
"""Flat button.
|
|
981
1030
|
|
|
982
1031
|
Args:
|
|
983
|
-
label
|
|
984
|
-
handler
|
|
985
|
-
icon
|
|
1032
|
+
label: button label
|
|
1033
|
+
handler: event handler when the button is pressed
|
|
1034
|
+
icon: key:str or bitmap for button icon
|
|
986
1035
|
**kwargs: keywords for wx.lib.platebtn.PlateButton
|
|
987
1036
|
"""
|
|
988
1037
|
def __init__(self, parent, label='', handler=None, icon=None, **kwargs):
|
|
@@ -991,13 +1040,13 @@ class Button(pb.PlateButton):
|
|
|
991
1040
|
|
|
992
1041
|
if handler:
|
|
993
1042
|
self.Bind(wx.EVT_BUTTON, _F(handler))
|
|
994
|
-
|
|
995
|
-
self.SetToolTip(_Tip(handler.__doc__))
|
|
1043
|
+
self.SetToolTip(_Tip(handler.__doc__))
|
|
996
1044
|
if icon:
|
|
997
1045
|
self.SetBitmap(Icon(icon))
|
|
998
|
-
|
|
1046
|
+
|
|
999
1047
|
def SetBitmap(self, bmp):
|
|
1000
1048
|
"""Set the bitmap displayed in the button.
|
|
1049
|
+
|
|
1001
1050
|
(override) If it fails, it clears the bitmap.
|
|
1002
1051
|
"""
|
|
1003
1052
|
try:
|
|
@@ -1008,12 +1057,12 @@ class Button(pb.PlateButton):
|
|
|
1008
1057
|
|
|
1009
1058
|
|
|
1010
1059
|
class ToggleButton(wx.ToggleButton):
|
|
1011
|
-
"""Togglable button
|
|
1060
|
+
"""Togglable button.
|
|
1012
1061
|
|
|
1013
1062
|
Args:
|
|
1014
|
-
label
|
|
1015
|
-
handler
|
|
1016
|
-
icon
|
|
1063
|
+
label: button label
|
|
1064
|
+
handler: event handler when the button is pressed
|
|
1065
|
+
icon: key:str or bitmap for button icon
|
|
1017
1066
|
**kwargs: keywords for wx.ToggleButton
|
|
1018
1067
|
|
|
1019
1068
|
Note:
|
|
@@ -1024,8 +1073,7 @@ class ToggleButton(wx.ToggleButton):
|
|
|
1024
1073
|
|
|
1025
1074
|
if handler:
|
|
1026
1075
|
self.Bind(wx.EVT_TOGGLEBUTTON, _F(handler))
|
|
1027
|
-
|
|
1028
|
-
self.SetToolTip(_Tip(handler.__doc__))
|
|
1076
|
+
self.SetToolTip(_Tip(handler.__doc__))
|
|
1029
1077
|
if icon:
|
|
1030
1078
|
try:
|
|
1031
1079
|
self.SetBitmap(Icon(icon[0]))
|
|
@@ -1034,38 +1082,38 @@ class ToggleButton(wx.ToggleButton):
|
|
|
1034
1082
|
self.SetBitmap(Icon(icon))
|
|
1035
1083
|
|
|
1036
1084
|
|
|
1037
|
-
class
|
|
1038
|
-
"""Text
|
|
1085
|
+
class TextBox(wx.Control):
|
|
1086
|
+
"""Text control.
|
|
1039
1087
|
|
|
1040
1088
|
Args:
|
|
1041
|
-
label
|
|
1042
|
-
handler
|
|
1043
|
-
updater
|
|
1044
|
-
icon
|
|
1089
|
+
label: button label
|
|
1090
|
+
handler: event handler when text is entered
|
|
1091
|
+
updater: event handler when the button is pressed
|
|
1092
|
+
icon: key:str or bitmap for button icon
|
|
1045
1093
|
readonly: flag:bool (equiv. style=wx.TE_READONLY)
|
|
1046
1094
|
**kwargs: keywords for wx.TextCtrl
|
|
1047
1095
|
e.g., value:str
|
|
1048
1096
|
"""
|
|
1049
|
-
Value = property(
|
|
1097
|
+
Value = property( # textctrl value:str
|
|
1050
1098
|
lambda self: self._ctrl.GetValue(),
|
|
1051
|
-
lambda self,v: self._ctrl.SetValue(v)
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1099
|
+
lambda self, v: self._ctrl.SetValue(v))
|
|
1100
|
+
|
|
1101
|
+
value = Value # internal use only
|
|
1102
|
+
|
|
1103
|
+
button = property(lambda self: self._btn)
|
|
1104
|
+
control = property(lambda self: self._ctrl)
|
|
1105
|
+
|
|
1056
1106
|
def __init__(self, parent, label='', handler=None, updater=None,
|
|
1057
1107
|
icon=None, readonly=False, size=(-1,-1), **kwargs):
|
|
1058
1108
|
wx.Control.__init__(self, parent, size=size, style=wx.BORDER_NONE)
|
|
1059
1109
|
|
|
1060
1110
|
kwargs['style'] = (kwargs.get('style', 0)
|
|
1061
|
-
|
|
1062
|
-
|
|
1111
|
+
| wx.TE_PROCESS_ENTER
|
|
1112
|
+
| (wx.TE_READONLY if readonly else 0))
|
|
1063
1113
|
|
|
1064
1114
|
self._ctrl = wx.TextCtrl(self, **kwargs)
|
|
1065
1115
|
self._btn = Button(self, label, None, icon,
|
|
1066
1116
|
size=(-1,-1) if label or icon else (0,0))
|
|
1067
|
-
self._ctrl.SetToolTip(_Tip(handler.__doc__))
|
|
1068
|
-
self._btn.SetToolTip(_Tip(updater.__doc__))
|
|
1069
1117
|
|
|
1070
1118
|
self.SetSizer(
|
|
1071
1119
|
pack(self, (
|
|
@@ -1076,10 +1124,14 @@ class TextCtrl(wx.Control):
|
|
|
1076
1124
|
if handler:
|
|
1077
1125
|
self._handler = _F(handler)
|
|
1078
1126
|
self._ctrl.Bind(wx.EVT_TEXT_ENTER, lambda v: self._handler(self))
|
|
1127
|
+
self._ctrl.SetToolTip(_Tip(handler.__doc__))
|
|
1079
1128
|
if updater:
|
|
1080
1129
|
self._updater = _F(updater)
|
|
1081
1130
|
self._btn.Bind(wx.EVT_BUTTON, lambda v: self._updater(self))
|
|
1082
|
-
|
|
1131
|
+
self._btn.SetToolTip(_Tip(updater.__doc__))
|
|
1132
|
+
|
|
1133
|
+
self.Bind(wx.EVT_NAVIGATION_KEY, self.OnNavKey)
|
|
1134
|
+
|
|
1083
1135
|
def reset(self, v):
|
|
1084
1136
|
try:
|
|
1085
1137
|
self.Value = v
|
|
@@ -1087,15 +1139,21 @@ class TextCtrl(wx.Control):
|
|
|
1087
1139
|
except AttributeError:
|
|
1088
1140
|
pass
|
|
1089
1141
|
|
|
1142
|
+
def OnNavKey(self, evt):
|
|
1143
|
+
if evt.EventObject is self._ctrl:
|
|
1144
|
+
self.Navigate(evt.Direction)
|
|
1145
|
+
elif self.IsShown():
|
|
1146
|
+
self._ctrl.SetFocus()
|
|
1147
|
+
|
|
1090
1148
|
|
|
1091
1149
|
class Choice(wx.Control):
|
|
1092
|
-
"""Editable Choice (ComboBox)
|
|
1150
|
+
"""Editable Choice (ComboBox) control.
|
|
1093
1151
|
|
|
1094
1152
|
Args:
|
|
1095
|
-
label
|
|
1096
|
-
handler
|
|
1097
|
-
updater
|
|
1098
|
-
icon
|
|
1153
|
+
label: button label
|
|
1154
|
+
handler: event handler when text is entered or item is selected
|
|
1155
|
+
updater: event handler when the button is pressed
|
|
1156
|
+
icon: key:str or bitmap for button icon
|
|
1099
1157
|
readonly: flag:bool (equiv. style=wx.CB_READONLY)
|
|
1100
1158
|
**kwargs: keywords for wx.ComboBox
|
|
1101
1159
|
e.g., choices:list
|
|
@@ -1104,36 +1162,34 @@ class Choice(wx.Control):
|
|
|
1104
1162
|
If the input item is not found in the choices,
|
|
1105
1163
|
it will be added to the list (unless readonly)
|
|
1106
1164
|
"""
|
|
1107
|
-
Value = property(
|
|
1165
|
+
Value = property( # combobox value:str
|
|
1108
1166
|
lambda self: self._ctrl.GetValue(),
|
|
1109
|
-
lambda self,v: self._ctrl.SetValue(v)
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
Selection = property(
|
|
1167
|
+
lambda self, v: self._ctrl.SetValue(v))
|
|
1168
|
+
|
|
1169
|
+
value = Value # internal use only
|
|
1170
|
+
|
|
1171
|
+
Selection = property( # combobox selection:int or NOT_FOUND(-1)
|
|
1115
1172
|
lambda self: self._ctrl.GetSelection(),
|
|
1116
|
-
lambda self,v: self._ctrl.SetSelection(v)
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
Items = property(
|
|
1173
|
+
lambda self, v: self._ctrl.SetSelection(v))
|
|
1174
|
+
|
|
1175
|
+
Items = property( # combobox items:list
|
|
1120
1176
|
lambda self: self._ctrl.GetItems(),
|
|
1121
|
-
lambda self,v: self._ctrl.SetItems(v)
|
|
1122
|
-
|
|
1123
|
-
|
|
1177
|
+
lambda self, v: self._ctrl.SetItems(v))
|
|
1178
|
+
|
|
1179
|
+
button = property(lambda self: self._btn)
|
|
1180
|
+
control = property(lambda self: self._ctrl)
|
|
1181
|
+
|
|
1124
1182
|
def __init__(self, parent, label='', handler=None, updater=None,
|
|
1125
1183
|
icon=None, readonly=False, size=(-1,-1), **kwargs):
|
|
1126
1184
|
wx.Control.__init__(self, parent, size=size, style=wx.BORDER_NONE)
|
|
1127
1185
|
|
|
1128
1186
|
kwargs['style'] = (kwargs.get('style', 0)
|
|
1129
|
-
|
|
1130
|
-
|
|
1187
|
+
| wx.TE_PROCESS_ENTER
|
|
1188
|
+
| (wx.CB_READONLY if readonly else 0))
|
|
1131
1189
|
|
|
1132
1190
|
self._ctrl = wx.ComboBox(self, **kwargs)
|
|
1133
1191
|
self._btn = Button(self, label, None, icon,
|
|
1134
1192
|
size=(-1,-1) if label or icon else (0,0))
|
|
1135
|
-
self._ctrl.SetToolTip(_Tip(handler.__doc__))
|
|
1136
|
-
self._btn.SetToolTip(_Tip(updater.__doc__))
|
|
1137
1193
|
|
|
1138
1194
|
self.SetSizer(
|
|
1139
1195
|
pack(self, (
|
|
@@ -1145,18 +1201,22 @@ class Choice(wx.Control):
|
|
|
1145
1201
|
self._handler = _F(handler)
|
|
1146
1202
|
self._ctrl.Bind(wx.EVT_TEXT_ENTER, lambda v: self._handler(self))
|
|
1147
1203
|
self._ctrl.Bind(wx.EVT_COMBOBOX, lambda v: self._handler(self))
|
|
1204
|
+
self._ctrl.SetToolTip(_Tip(handler.__doc__))
|
|
1148
1205
|
self._ctrl.Bind(wx.EVT_TEXT_ENTER, self.OnTextEnter)
|
|
1149
1206
|
if updater:
|
|
1150
1207
|
self._updater = _F(updater)
|
|
1151
1208
|
self._btn.Bind(wx.EVT_BUTTON, lambda v: self._updater(self))
|
|
1152
|
-
|
|
1209
|
+
self._btn.SetToolTip(_Tip(updater.__doc__))
|
|
1210
|
+
|
|
1211
|
+
self.Bind(wx.EVT_NAVIGATION_KEY, self.OnNavKey)
|
|
1212
|
+
|
|
1153
1213
|
def reset(self, v):
|
|
1154
1214
|
try:
|
|
1155
1215
|
self.Value = v
|
|
1156
1216
|
self._handler(self)
|
|
1157
1217
|
except AttributeError:
|
|
1158
1218
|
pass
|
|
1159
|
-
|
|
1219
|
+
|
|
1160
1220
|
def OnTextEnter(self, evt):
|
|
1161
1221
|
s = evt.String.strip()
|
|
1162
1222
|
if not s:
|
|
@@ -1166,29 +1226,35 @@ class Choice(wx.Control):
|
|
|
1166
1226
|
self._ctrl.SetStringSelection(s)
|
|
1167
1227
|
evt.Skip()
|
|
1168
1228
|
|
|
1229
|
+
def OnNavKey(self, evt):
|
|
1230
|
+
if evt.EventObject is self._ctrl:
|
|
1231
|
+
self.Navigate(evt.Direction)
|
|
1232
|
+
elif self.IsShown():
|
|
1233
|
+
self._ctrl.SetFocus()
|
|
1234
|
+
|
|
1169
1235
|
|
|
1170
1236
|
class Indicator(wx.Control):
|
|
1171
|
-
"""Traffic light indicator
|
|
1237
|
+
"""Traffic light indicator.
|
|
1172
1238
|
|
|
1173
1239
|
Args:
|
|
1174
|
-
colors
|
|
1175
|
-
value
|
|
1240
|
+
colors: list of colors (default is tricolour) cf. wx.ColourDatabase
|
|
1241
|
+
value: initial value
|
|
1176
1242
|
**kwargs: keywords for wx.Control
|
|
1177
1243
|
"""
|
|
1178
1244
|
@property
|
|
1179
1245
|
def Value(self):
|
|
1180
1246
|
return self.__value
|
|
1181
|
-
|
|
1247
|
+
|
|
1182
1248
|
@Value.setter
|
|
1183
1249
|
def Value(self, v):
|
|
1184
1250
|
self.__value = int(v)
|
|
1185
1251
|
self.Refresh()
|
|
1186
|
-
|
|
1252
|
+
|
|
1187
1253
|
def redesign(self, **kwargs):
|
|
1188
1254
|
"""Update multiple design properties at once.
|
|
1189
1255
|
|
|
1190
1256
|
This method is useful for changing colors, spacing, radius, etc.
|
|
1191
|
-
The best size will be automatically invalidated and
|
|
1257
|
+
The best size will be automatically invalidated and recalculated.
|
|
1192
1258
|
|
|
1193
1259
|
Args:
|
|
1194
1260
|
**kwargs: class attributes, e.g. colors, spacing, radius.
|
|
@@ -1198,14 +1264,14 @@ class Indicator(wx.Control):
|
|
|
1198
1264
|
"""
|
|
1199
1265
|
self.__dict__.update(kwargs)
|
|
1200
1266
|
self.InvalidateBestSize()
|
|
1201
|
-
|
|
1202
|
-
colors = ('green', 'yellow', 'red')
|
|
1267
|
+
|
|
1268
|
+
colors = ('green', 'yellow', 'red') # default tricolor style
|
|
1203
1269
|
backgroundColour = 'dark gray'
|
|
1204
1270
|
foregroundColour = 'light gray'
|
|
1205
1271
|
spacing = 7
|
|
1206
1272
|
radius = 4
|
|
1207
1273
|
glow = 0
|
|
1208
|
-
|
|
1274
|
+
|
|
1209
1275
|
def __init__(self, parent, colors=None, value=0,
|
|
1210
1276
|
style=wx.BORDER_NONE, **kwargs):
|
|
1211
1277
|
wx.Control.__init__(self, parent, style=style, **kwargs)
|
|
@@ -1219,19 +1285,19 @@ class Indicator(wx.Control):
|
|
|
1219
1285
|
self.InvalidateBestSize()
|
|
1220
1286
|
self.Fit()
|
|
1221
1287
|
|
|
1222
|
-
self.SetBackgroundStyle(wx.BG_STYLE_PAINT)
|
|
1288
|
+
self.SetBackgroundStyle(wx.BG_STYLE_PAINT) # to avoid flickering
|
|
1223
1289
|
|
|
1224
1290
|
self.Bind(wx.EVT_SIZE, self.OnSize)
|
|
1225
1291
|
self.Bind(wx.EVT_PAINT, self.OnPaint)
|
|
1226
|
-
|
|
1292
|
+
|
|
1227
1293
|
def DoGetBestSize(self):
|
|
1228
1294
|
N = len(self.colors)
|
|
1229
1295
|
s = self.spacing
|
|
1230
1296
|
return wx.Size((2*s-1)*N+3, 2*s+2)
|
|
1231
|
-
|
|
1297
|
+
|
|
1232
1298
|
def OnSize(self, evt):
|
|
1233
1299
|
self.Refresh()
|
|
1234
|
-
|
|
1300
|
+
|
|
1235
1301
|
def OnPaint(self, evt):
|
|
1236
1302
|
dc = wx.BufferedPaintDC(self)
|
|
1237
1303
|
dc.Clear()
|
|
@@ -1267,7 +1333,7 @@ class Indicator(wx.Control):
|
|
|
1267
1333
|
gc.DrawPath(path)
|
|
1268
1334
|
dc.SetBrush(wx.Brush(name if b else self.foregroundColour))
|
|
1269
1335
|
dc.DrawCircle(x, y, r)
|
|
1270
|
-
|
|
1336
|
+
|
|
1271
1337
|
def blink(self, msec, mask=0):
|
|
1272
1338
|
"""Blinks once for given milliseconds.
|
|
1273
1339
|
|
|
@@ -1286,31 +1352,31 @@ class Indicator(wx.Control):
|
|
|
1286
1352
|
|
|
1287
1353
|
|
|
1288
1354
|
class Gauge(wx.Control):
|
|
1289
|
-
"""Rainbow gauge
|
|
1355
|
+
"""Rainbow gauge.
|
|
1290
1356
|
|
|
1291
1357
|
Args:
|
|
1292
|
-
range
|
|
1293
|
-
value
|
|
1358
|
+
range: maximum value
|
|
1359
|
+
value: initial value
|
|
1294
1360
|
**kwargs: keywords for wx.Control
|
|
1295
1361
|
"""
|
|
1296
1362
|
@property
|
|
1297
1363
|
def Value(self):
|
|
1298
1364
|
return self.__value
|
|
1299
|
-
|
|
1365
|
+
|
|
1300
1366
|
@Value.setter
|
|
1301
1367
|
def Value(self, v):
|
|
1302
1368
|
self.__value = int(v)
|
|
1303
1369
|
self.Refresh()
|
|
1304
|
-
|
|
1370
|
+
|
|
1305
1371
|
@property
|
|
1306
1372
|
def Range(self):
|
|
1307
1373
|
return self.__range
|
|
1308
|
-
|
|
1374
|
+
|
|
1309
1375
|
@Range.setter
|
|
1310
1376
|
def Range(self, v):
|
|
1311
1377
|
self.__range = int(v)
|
|
1312
1378
|
self.Refresh()
|
|
1313
|
-
|
|
1379
|
+
|
|
1314
1380
|
def __init__(self, parent, range=24, value=0,
|
|
1315
1381
|
style=wx.BORDER_NONE, **kwargs):
|
|
1316
1382
|
wx.Control.__init__(self, parent, style=style, **kwargs)
|
|
@@ -1318,14 +1384,14 @@ class Gauge(wx.Control):
|
|
|
1318
1384
|
self.__range = range
|
|
1319
1385
|
self.__value = value
|
|
1320
1386
|
|
|
1321
|
-
self.SetBackgroundStyle(wx.BG_STYLE_PAINT)
|
|
1387
|
+
self.SetBackgroundStyle(wx.BG_STYLE_PAINT) # to avoid flickering
|
|
1322
1388
|
|
|
1323
1389
|
self.Bind(wx.EVT_SIZE, self.OnSize)
|
|
1324
1390
|
self.Bind(wx.EVT_PAINT, self.OnPaint)
|
|
1325
|
-
|
|
1391
|
+
|
|
1326
1392
|
def OnSize(self, evt):
|
|
1327
1393
|
self.Refresh()
|
|
1328
|
-
|
|
1394
|
+
|
|
1329
1395
|
def OnPaint(self, evt):
|
|
1330
1396
|
dc = wx.BufferedPaintDC(self)
|
|
1331
1397
|
dc.Clear()
|