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