mwxlib 1.0.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 +6 -4
- mwx/bookshelf.py +133 -57
- mwx/controls.py +441 -375
- mwx/framework.py +531 -513
- mwx/graphman.py +683 -677
- mwx/images.py +16 -0
- mwx/matplot2.py +225 -216
- mwx/matplot2g.py +735 -652
- mwx/matplot2lg.py +192 -183
- mwx/mgplt.py +20 -22
- mwx/nutshell.py +1063 -939
- mwx/plugins/ffmpeg_view.py +74 -75
- mwx/plugins/fft_view.py +13 -15
- mwx/plugins/frame_listview.py +55 -52
- mwx/plugins/line_profile.py +1 -1
- mwx/py/filling.py +9 -8
- mwx/testsuite.py +38 -0
- mwx/utilus.py +273 -210
- mwx/wxmon.py +39 -38
- mwx/wxpdb.py +81 -83
- mwx/wxwil.py +18 -18
- mwx/wxwit.py +32 -35
- {mwxlib-1.0.0.dist-info → mwxlib-1.7.13.dist-info}/METADATA +12 -5
- mwxlib-1.7.13.dist-info/RECORD +28 -0
- {mwxlib-1.0.0.dist-info → mwxlib-1.7.13.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.7.13.dist-info}/top_level.txt +0 -0
mwx/plugins/ffmpeg_view.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#! python3
|
|
2
2
|
"""FFmpeg wrapper.
|
|
3
3
|
"""
|
|
4
|
-
from functools import partial
|
|
5
4
|
from subprocess import Popen, PIPE
|
|
6
5
|
import numpy as np
|
|
7
6
|
import os
|
|
@@ -10,7 +9,7 @@ import wx.media
|
|
|
10
9
|
|
|
11
10
|
from mwx.framework import _F, hotkey
|
|
12
11
|
from mwx.graphman import Layer
|
|
13
|
-
from mwx.controls import LParam, Icon, Button,
|
|
12
|
+
from mwx.controls import LParam, Icon, Button, TextBox
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
def read_info(path):
|
|
@@ -35,8 +34,8 @@ def capture_video(path, ss=0):
|
|
|
35
34
|
'-pix_fmt', 'rgb24', # rgb24, gray, etc.
|
|
36
35
|
'pipe:' # pipe to stdout: '-'
|
|
37
36
|
]
|
|
38
|
-
bufsize = 4096
|
|
39
|
-
buf = b""
|
|
37
|
+
bufsize = 4096 # w * h * 3
|
|
38
|
+
buf = b""
|
|
40
39
|
with Popen(command, stdout=PIPE) as fp:
|
|
41
40
|
while 1:
|
|
42
41
|
s = fp.stdout.read(bufsize)
|
|
@@ -63,9 +62,9 @@ class MyFileDropLoader(wx.FileDropTarget):
|
|
|
63
62
|
def __init__(self, target):
|
|
64
63
|
wx.FileDropTarget.__init__(self)
|
|
65
64
|
self.target = target
|
|
66
|
-
|
|
65
|
+
|
|
67
66
|
def OnDropFiles(self, x, y, filenames):
|
|
68
|
-
path = filenames[-1]
|
|
67
|
+
path = filenames[-1] # Only the last one will be loaded.
|
|
69
68
|
if len(filenames) > 1:
|
|
70
69
|
print("- Drop only one file please."
|
|
71
70
|
"Loading {!r} ...".format(path))
|
|
@@ -78,13 +77,13 @@ class Plugin(Layer):
|
|
|
78
77
|
"""
|
|
79
78
|
menukey = "Plugins/Extensions/FFMpeg viewer"
|
|
80
79
|
dockable = False
|
|
81
|
-
|
|
80
|
+
|
|
82
81
|
def Init(self):
|
|
83
82
|
self.mc = wx.media.MediaCtrl()
|
|
84
83
|
self.mc.Create(self, size=(300,300),
|
|
85
84
|
style=wx.SIMPLE_BORDER,
|
|
86
85
|
szBackend=wx.media.MEDIABACKEND_WMP10
|
|
87
|
-
|
|
86
|
+
# szBackend=wx.media.MEDIABACKEND_DIRECTSHOW
|
|
88
87
|
)
|
|
89
88
|
self.mc.ShowPlayerControls()
|
|
90
89
|
self.mc.Bind(wx.media.EVT_MEDIA_LOADED, self.OnMediaLoaded)
|
|
@@ -93,15 +92,15 @@ class Plugin(Layer):
|
|
|
93
92
|
|
|
94
93
|
self._path = None
|
|
95
94
|
|
|
96
|
-
self.ss = LParam("ss:",
|
|
95
|
+
self.ss = LParam("ss:", # range/value will be set when loaded later.
|
|
97
96
|
handler=self.set_offset,
|
|
98
97
|
updater=self.get_offset,
|
|
99
98
|
)
|
|
100
|
-
self.to = LParam("to:",
|
|
99
|
+
self.to = LParam("to:", # range/value will be set when loaded later.
|
|
101
100
|
handler=self.set_offset,
|
|
102
101
|
updater=self.get_offset,
|
|
103
102
|
)
|
|
104
|
-
self.crop =
|
|
103
|
+
self.crop = TextBox(self, icon="cut", size=(130,-1),
|
|
105
104
|
handler=self.set_crop,
|
|
106
105
|
updater=self.get_crop,
|
|
107
106
|
)
|
|
@@ -109,13 +108,13 @@ class Plugin(Layer):
|
|
|
109
108
|
self.snp = Button(self, handler=self.snapshot, icon='clip')
|
|
110
109
|
self.exp = Button(self, handler=self.export, icon='save')
|
|
111
110
|
|
|
112
|
-
self.rw = Button(self, handler=lambda v: self.
|
|
113
|
-
self.fw = Button(self, handler=lambda v: self.
|
|
111
|
+
self.rw = Button(self, handler=lambda v: self.seekto(-100), icon='|<-')
|
|
112
|
+
self.fw = Button(self, handler=lambda v: self.seekto(+100), icon='->|')
|
|
114
113
|
|
|
115
114
|
self.layout((self.mc,), expand=2)
|
|
116
115
|
self.layout((self.ss, self.to, self.rw, self.fw,
|
|
117
116
|
self.snp, self.crop, self.exp),
|
|
118
|
-
expand=0, row=7, style='button', lw=
|
|
117
|
+
expand=0, row=7, style='button', lw=28, cw=0, tw=64)
|
|
119
118
|
|
|
120
119
|
self.menu[0:5] = [
|
|
121
120
|
(1, "&Load file", Icon('open'),
|
|
@@ -130,54 +129,52 @@ class Plugin(Layer):
|
|
|
130
129
|
self.parent.handler.bind("unknown_format", self.load_media)
|
|
131
130
|
|
|
132
131
|
self.handler.update({ # DNA<ffmpeg_viewer>
|
|
133
|
-
0 : {
|
|
134
|
-
'play' : (
|
|
135
|
-
'space pressed' : (
|
|
132
|
+
0 : { # MEDIASTATE_STOPPED
|
|
133
|
+
'play' : (2, ),
|
|
134
|
+
'space pressed' : (2, _F(self.mc.Play)),
|
|
135
|
+
'left pressed' : (0, _F(self.seekd, -1000)),
|
|
136
|
+
'right pressed' : (0, _F(self.seekd, 1000)),
|
|
137
|
+
},
|
|
138
|
+
1 : { # MEDIASTATE_PAUSED
|
|
139
|
+
'stop' : (0, ),
|
|
140
|
+
'space pressed' : (2, _F(self.mc.Play)),
|
|
141
|
+
'left pressed' : (1, _F(self.seekd, -1000)),
|
|
142
|
+
'right pressed' : (1, _F(self.seekd, 1000)),
|
|
136
143
|
},
|
|
137
|
-
|
|
144
|
+
2 : { # MEDIASTATE_PLAYING
|
|
138
145
|
'stop' : (0, ),
|
|
139
|
-
'pause' : (
|
|
146
|
+
'pause' : (1, ),
|
|
140
147
|
'space pressed' : (1, _F(self.mc.Pause)),
|
|
148
|
+
'left pressed' : (2, _F(self.seekd, -1000)),
|
|
149
|
+
'right pressed' : (2, _F(self.seekd, 1000)),
|
|
141
150
|
},
|
|
142
151
|
})
|
|
143
152
|
|
|
144
|
-
self.mc.Bind(wx.media.EVT_MEDIA_PAUSE,
|
|
145
|
-
self.mc.Bind(wx.media.EVT_MEDIA_PLAY,
|
|
146
|
-
self.mc.Bind(wx.media.EVT_MEDIA_STOP,
|
|
147
|
-
|
|
148
|
-
## self.mc.Bind(wx.EVT_KEY_DOWN, self.on_hotkey_down)
|
|
149
|
-
## self.mc.Bind(wx.EVT_KEY_UP, self.on_hotkey_up)
|
|
150
|
-
self.mc.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
|
|
151
|
-
self.mc.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
|
|
153
|
+
self.mc.Bind(wx.media.EVT_MEDIA_PAUSE, lambda v: self.handler('pause', v))
|
|
154
|
+
self.mc.Bind(wx.media.EVT_MEDIA_PLAY, lambda v: self.handler('play', v))
|
|
155
|
+
self.mc.Bind(wx.media.EVT_MEDIA_STOP, lambda v: self.handler('stop', v))
|
|
152
156
|
|
|
153
|
-
self.Bind(wx.
|
|
154
|
-
|
|
157
|
+
self.mc.Bind(wx.EVT_KEY_DOWN, self.on_hotkey_down)
|
|
158
|
+
self.mc.Bind(wx.EVT_KEY_UP, self.on_hotkey_up)
|
|
159
|
+
|
|
155
160
|
def Destroy(self):
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
def OnKeyDown(self, evt):
|
|
162
|
-
if self.handler('{} pressed'.format(hotkey(evt)), evt) is None:
|
|
163
|
-
evt.Skip()
|
|
164
|
-
|
|
165
|
-
def OnKeyUp(self, evt):
|
|
166
|
-
if self.handler('{} released'.format(hotkey(evt)), evt) is None:
|
|
167
|
-
evt.Skip()
|
|
168
|
-
|
|
161
|
+
self.parent.handler.unbind("unknown_format", self.load_media)
|
|
162
|
+
if self.mc:
|
|
163
|
+
self.mc.Destroy()
|
|
164
|
+
return Layer.Destroy(self)
|
|
165
|
+
|
|
169
166
|
def OnShow(self, evt):
|
|
170
167
|
if not evt.IsShown():
|
|
171
168
|
if self.mc:
|
|
172
169
|
self.mc.Stop()
|
|
173
|
-
|
|
174
|
-
|
|
170
|
+
Layer.OnShow(self, evt)
|
|
171
|
+
|
|
175
172
|
def OnMediaLoaded(self, evt):
|
|
176
173
|
self.ss.range = (0, self.video_dur, 0.01)
|
|
177
174
|
self.to.range = (0, self.video_dur, 0.01)
|
|
178
175
|
self.Show()
|
|
179
176
|
evt.Skip()
|
|
180
|
-
|
|
177
|
+
|
|
181
178
|
def load_media(self, path=None):
|
|
182
179
|
if path is None:
|
|
183
180
|
with wx.FileDialog(self, "Choose a media file",
|
|
@@ -185,11 +182,11 @@ class Plugin(Layer):
|
|
|
185
182
|
if dlg.ShowModal() != wx.ID_OK:
|
|
186
183
|
return None
|
|
187
184
|
path = dlg.Path
|
|
188
|
-
self.mc.Load(path)
|
|
185
|
+
self.mc.Load(path) # -> True (always)
|
|
189
186
|
self.info = read_info(path)
|
|
190
187
|
if self.info:
|
|
191
188
|
v = next(x for x in self.info['streams'] if x['codec_type'] == 'video')
|
|
192
|
-
|
|
189
|
+
# self.video_fps = eval(v['r_frame_rate']) # real base frame rate
|
|
193
190
|
self.video_fps = eval(v['avg_frame_rate']) # averaged frame rate
|
|
194
191
|
self.video_dur = eval(v['duration']) # duration [s]
|
|
195
192
|
w, h = v['width'], v['height']
|
|
@@ -205,25 +202,23 @@ class Plugin(Layer):
|
|
|
205
202
|
else:
|
|
206
203
|
self.message(f"Failed to load file {path!r}.")
|
|
207
204
|
return False
|
|
208
|
-
|
|
209
|
-
DELTA = 1000
|
|
210
|
-
|
|
205
|
+
|
|
206
|
+
DELTA = 1000 # correction ▲理由は不明 (WMP10 backend only?)
|
|
207
|
+
|
|
211
208
|
def set_offset(self, tc):
|
|
212
209
|
"""Set offset value by referring to ss/to value."""
|
|
213
|
-
|
|
210
|
+
if self._path:
|
|
214
211
|
self.mc.Seek(self.DELTA + int(tc.value * 1000))
|
|
215
|
-
|
|
216
|
-
pass
|
|
217
|
-
|
|
212
|
+
|
|
218
213
|
def get_offset(self, tc):
|
|
219
214
|
"""Get offset value and assigns it to ss/to value."""
|
|
220
|
-
|
|
215
|
+
if self._path:
|
|
221
216
|
tc.value = round(self.mc.Tell()) / 1000
|
|
222
|
-
|
|
223
|
-
pass
|
|
224
|
-
|
|
217
|
+
|
|
225
218
|
def set_crop(self):
|
|
226
|
-
"""Set crop area (W:H:Left:Top) to
|
|
219
|
+
"""Set crop area (W:H:Left:Top) to ROI."""
|
|
220
|
+
if not self._path:
|
|
221
|
+
return
|
|
227
222
|
frame = self.graph.frame
|
|
228
223
|
if frame:
|
|
229
224
|
try:
|
|
@@ -235,9 +230,11 @@ class Plugin(Layer):
|
|
|
235
230
|
frame.region = frame.xyfrompixel(nx, ny)
|
|
236
231
|
except Exception:
|
|
237
232
|
self.message("Failed to evaluate crop text.")
|
|
238
|
-
|
|
233
|
+
|
|
239
234
|
def get_crop(self):
|
|
240
|
-
"""Get crop area (W:H:Left:Top) from
|
|
235
|
+
"""Get crop area (W:H:Left:Top) from ROI."""
|
|
236
|
+
if not self._path:
|
|
237
|
+
return
|
|
241
238
|
crop = ''
|
|
242
239
|
frame = self.graph.frame
|
|
243
240
|
if frame:
|
|
@@ -246,23 +243,25 @@ class Plugin(Layer):
|
|
|
246
243
|
xo, yo = nx[0], ny[1]
|
|
247
244
|
xp, yp = nx[1], ny[0]
|
|
248
245
|
crop = "{}:{}:{}:{}".format(xp-xo, yp-yo, xo, yo)
|
|
249
|
-
if
|
|
246
|
+
if not crop:
|
|
250
247
|
crop = "{}:{}:0:0".format(*self.video_size)
|
|
251
248
|
self.crop.Value = crop
|
|
252
|
-
|
|
253
|
-
def
|
|
254
|
-
"""Seek
|
|
255
|
-
if
|
|
256
|
-
offset /= 10
|
|
257
|
-
try:
|
|
249
|
+
|
|
250
|
+
def seekto(self, offset):
|
|
251
|
+
"""Seek position with offset [ms] from the `to` position."""
|
|
252
|
+
if self._path:
|
|
258
253
|
t = self.to.value + offset/1000
|
|
259
|
-
|
|
260
|
-
print(e)
|
|
261
|
-
else:
|
|
262
|
-
if self._path and 0 <= t < self.video_dur:
|
|
254
|
+
if 0 <= t < self.video_dur:
|
|
263
255
|
self.to.value = round(t, 3)
|
|
264
|
-
self.set_offset(self.to)
|
|
265
|
-
|
|
256
|
+
self.set_offset(self.to)
|
|
257
|
+
|
|
258
|
+
def seekd(self, offset):
|
|
259
|
+
"""Seek position with offset [ms] from the current position."""
|
|
260
|
+
if self._path:
|
|
261
|
+
t = self.mc.Tell() + offset
|
|
262
|
+
if 0 <= t < self.video_dur * 1000:
|
|
263
|
+
self.mc.Seek(self.DELTA + t)
|
|
264
|
+
|
|
266
265
|
def snapshot(self):
|
|
267
266
|
"""Create a snapshot of the current frame.
|
|
268
267
|
Load the snapshot image into the graph window.
|
|
@@ -274,7 +273,7 @@ class Plugin(Layer):
|
|
|
274
273
|
buf = capture_video(self._path, t/1000).reshape((h,w,3))
|
|
275
274
|
name = "{}-ss{}".format(os.path.basename(self._path), int(t))
|
|
276
275
|
self.graph.load(buf, name)
|
|
277
|
-
|
|
276
|
+
|
|
278
277
|
def export(self):
|
|
279
278
|
"""Export the cropped / clipped data to a media file."""
|
|
280
279
|
if not self._path:
|
mwx/plugins/fft_view.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"""
|
|
4
4
|
import wx
|
|
5
5
|
import numpy as np
|
|
6
|
-
from numpy.fft import fft2,ifft2,fftshift,ifftshift
|
|
6
|
+
from numpy.fft import fft2, ifft2, fftshift, ifftshift
|
|
7
7
|
|
|
8
8
|
from mwx.graphman import Layer
|
|
9
9
|
from mwx.controls import Param
|
|
@@ -13,7 +13,7 @@ def fftcrop(src):
|
|
|
13
13
|
"""Crop src image in 2**N square ROI centered at (x, y)."""
|
|
14
14
|
h, w = src.shape
|
|
15
15
|
m = min(h, w)
|
|
16
|
-
n = 1 if m < 2 else 2 ** int(np.log2(m) - 1)
|
|
16
|
+
n = 1 if m < 2 else 2 ** int(np.log2(m) - 1) # +-m/2
|
|
17
17
|
x, y = w//2, h//2
|
|
18
18
|
return src[y-n:y+n, x-n:x+n]
|
|
19
19
|
|
|
@@ -28,30 +28,30 @@ class Plugin(Layer):
|
|
|
28
28
|
"""
|
|
29
29
|
menukey = "Plugins/Extensions/&FFT view\tAlt+f"
|
|
30
30
|
caption = "FFT view"
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
def Init(self):
|
|
33
33
|
self.pchk = wx.CheckBox(self, label="logical unit")
|
|
34
34
|
self.pchk.Value = True
|
|
35
35
|
|
|
36
|
-
self.ftor = Param("mask", (2,4,8,16,32,64))
|
|
36
|
+
self.ftor = Param("mask", (2,4,8,16,32,64)) # masking area factor of 1/2
|
|
37
37
|
|
|
38
38
|
self.layout((self.pchk,), title="normal FFT")
|
|
39
39
|
self.layout((self.ftor,), title="inverse FFT", style='chkbox', tw=32)
|
|
40
40
|
|
|
41
41
|
self.parent.define_key('C-f', self.newfft)
|
|
42
42
|
self.parent.define_key('C-S-f', self.newifft)
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
def Destroy(self):
|
|
45
45
|
self.parent.undefine_key('C-f')
|
|
46
46
|
self.parent.undefine_key('C-S-f')
|
|
47
47
|
return Layer.Destroy(self)
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
def newfft(self):
|
|
50
50
|
"""New FFT of graph to output."""
|
|
51
51
|
frame = self.graph.frame
|
|
52
52
|
if frame:
|
|
53
53
|
self.message("FFT execution...")
|
|
54
|
-
src = fftcrop(frame.
|
|
54
|
+
src = fftcrop(frame.roi_or_buffer)
|
|
55
55
|
h, w = src.shape
|
|
56
56
|
|
|
57
57
|
dst = fftshift(fft2(src))
|
|
@@ -60,27 +60,25 @@ class Plugin(Layer):
|
|
|
60
60
|
u = 1 / w
|
|
61
61
|
if self.pchk.Value:
|
|
62
62
|
u /= frame.unit
|
|
63
|
-
self.output.load(dst, "*fft of {}*"
|
|
64
|
-
localunit=u)
|
|
63
|
+
self.output.load(dst, f"*fft of {frame.name}*", localunit=u)
|
|
65
64
|
self.message("\b done")
|
|
66
|
-
|
|
65
|
+
|
|
67
66
|
def newifft(self):
|
|
68
67
|
"""New inverse FFT of output to graph."""
|
|
69
68
|
frame = self.output.frame
|
|
70
69
|
if frame:
|
|
71
70
|
self.message("iFFT execution...")
|
|
72
|
-
src = frame.
|
|
71
|
+
src = frame.buffer # Don't crop fft region
|
|
73
72
|
h, w = src.shape
|
|
74
73
|
|
|
75
74
|
if self.ftor.check:
|
|
76
75
|
y, x = np.ogrid[-h/2:h/2, -w/2:w/2]
|
|
77
|
-
mask = np.hypot(y,x) > w / self.ftor.value
|
|
78
|
-
src = src.copy()
|
|
76
|
+
mask = np.hypot(y, x) > w / self.ftor.value
|
|
77
|
+
src = src.copy() # apply mask to the copy
|
|
79
78
|
src[mask] = 0
|
|
80
79
|
|
|
81
80
|
dst = ifft2(ifftshift(src))
|
|
82
81
|
|
|
83
82
|
self.message("\b Loading image...")
|
|
84
|
-
self.graph.load(dst.real, "*ifft of {}*"
|
|
85
|
-
localunit=1/w/frame.unit)
|
|
83
|
+
self.graph.load(dst.real, f"*ifft of {frame.name}*", localunit=1/w/frame.unit)
|
|
86
84
|
self.message("\b done")
|
mwx/plugins/frame_listview.py
CHANGED
|
@@ -21,23 +21,23 @@ class CheckList(wx.ListCtrl, ListCtrlAutoWidthMixin, CtrlInterface):
|
|
|
21
21
|
@property
|
|
22
22
|
def selected_items(self):
|
|
23
23
|
return filter(self.IsSelected, range(self.ItemCount))
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
@property
|
|
26
26
|
def checked_items(self):
|
|
27
27
|
return filter(self.IsItemChecked, range(self.ItemCount))
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
@property
|
|
30
30
|
def focused_item(self):
|
|
31
31
|
return self.FocusedItem
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
@property
|
|
34
34
|
def all_items(self):
|
|
35
35
|
rows = range(self.ItemCount)
|
|
36
36
|
cols = range(self.ColumnCount)
|
|
37
|
-
|
|
37
|
+
# return [[self.GetItemText(j, k) for k in cols] for j in rows]
|
|
38
38
|
for j in rows:
|
|
39
39
|
yield [self.GetItemText(j, k) for k in cols]
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
def __init__(self, parent, target, **kwargs):
|
|
42
42
|
wx.ListCtrl.__init__(self, parent, size=(400,130),
|
|
43
43
|
style=wx.LC_REPORT|wx.LC_HRULES, **kwargs)
|
|
@@ -52,7 +52,7 @@ class CheckList(wx.ListCtrl, ListCtrlAutoWidthMixin, CtrlInterface):
|
|
|
52
52
|
self.__dir = True
|
|
53
53
|
|
|
54
54
|
_alist = ( # assoc-list of column names
|
|
55
|
-
("id",
|
|
55
|
+
("id", 45),
|
|
56
56
|
("name", 160),
|
|
57
57
|
("shape", 90),
|
|
58
58
|
("dtype", 60),
|
|
@@ -65,16 +65,20 @@ class CheckList(wx.ListCtrl, ListCtrlAutoWidthMixin, CtrlInterface):
|
|
|
65
65
|
|
|
66
66
|
for j, frame in enumerate(self.Target.all_frames):
|
|
67
67
|
self.InsertItem(j, str(j))
|
|
68
|
-
self.UpdateInfo(frame)
|
|
68
|
+
self.UpdateInfo(frame) # update all --> 計算が入ると時間がかかる
|
|
69
69
|
|
|
70
70
|
self.handler.update({ # DNA<frame_listview>
|
|
71
71
|
0 : {
|
|
72
|
-
'Lbutton dblclick' : (0, self.OnShowItems),
|
|
73
|
-
'enter pressed' : (0, self.OnShowItems),
|
|
74
|
-
'delete pressed' : (0, self.OnRemoveItems),
|
|
72
|
+
'Lbutton dblclick' : (0, self.OnShowItems), # -> frame_shown
|
|
73
|
+
'enter pressed' : (0, self.OnShowItems), # -> frame_shown
|
|
74
|
+
'delete pressed' : (0, self.OnRemoveItems), # -> frame_removed/shown
|
|
75
75
|
'C-a pressed' : (0, self.OnSelectAllItems),
|
|
76
76
|
'C-o pressed' : (0, self.OnLoadItems),
|
|
77
77
|
'C-s pressed' : (0, self.OnSaveItems),
|
|
78
|
+
'C-S-s pressed' : (0, self.OnSaveItems),
|
|
79
|
+
'C-c pressed' : (0, self.OnCopyInfo),
|
|
80
|
+
'C-l pressed' : (0, self.OnEditLocalUnit),
|
|
81
|
+
'f2 pressed' : (0, self.OnEditAnnotation),
|
|
78
82
|
'M-up pressed' : (0, self.Target.OnPageUp),
|
|
79
83
|
'M-down pressed' : (0, self.Target.OnPageDown),
|
|
80
84
|
},
|
|
@@ -98,7 +102,7 @@ class CheckList(wx.ListCtrl, ListCtrlAutoWidthMixin, CtrlInterface):
|
|
|
98
102
|
|
|
99
103
|
self.menu = [
|
|
100
104
|
(100, "Edit localunit", Icon('image'),
|
|
101
|
-
self.
|
|
105
|
+
self.OnEditLocalUnit,
|
|
102
106
|
lambda v: v.Enable(self.focused_item != -1)),
|
|
103
107
|
|
|
104
108
|
(101, "Edit annotation", Icon('pencil'),
|
|
@@ -111,13 +115,11 @@ class CheckList(wx.ListCtrl, ListCtrlAutoWidthMixin, CtrlInterface):
|
|
|
111
115
|
]
|
|
112
116
|
self.Bind(wx.EVT_CONTEXT_MENU,
|
|
113
117
|
lambda v: Menu.Popup(self, self.menu))
|
|
114
|
-
|
|
118
|
+
|
|
115
119
|
def Destroy(self):
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return wx.ListCtrl.Destroy(self)
|
|
120
|
-
|
|
120
|
+
self.Target.handler.remove(self.context)
|
|
121
|
+
return wx.ListCtrl.Destroy(self)
|
|
122
|
+
|
|
121
123
|
def UpdateInfo(self, frame):
|
|
122
124
|
ls = ("{}".format(frame.index),
|
|
123
125
|
"{}".format(frame.name),
|
|
@@ -132,46 +134,46 @@ class CheckList(wx.ListCtrl, ListCtrlAutoWidthMixin, CtrlInterface):
|
|
|
132
134
|
self.SetItem(j, k, v)
|
|
133
135
|
if frame.pathname:
|
|
134
136
|
self.CheckItem(j)
|
|
135
|
-
|
|
137
|
+
|
|
136
138
|
def OnShowItems(self, evt):
|
|
137
139
|
self.Target.select(self.focused_item)
|
|
138
|
-
|
|
140
|
+
|
|
139
141
|
def OnRemoveItems(self, evt):
|
|
140
142
|
del self.Target[self.selected_items]
|
|
141
|
-
|
|
142
|
-
def OnSortItems(self, evt):
|
|
143
|
+
|
|
144
|
+
def OnSortItems(self, evt): #<wx._controls.ListEvent>
|
|
143
145
|
col = evt.Column
|
|
144
|
-
if col == 0:
|
|
146
|
+
if col == 0: # reverse the first column
|
|
145
147
|
self.__dir = False
|
|
146
|
-
self.__dir = not self.__dir
|
|
148
|
+
self.__dir = not self.__dir # toggle 0:ascend/1:descend
|
|
147
149
|
|
|
148
150
|
frames = self.Target.all_frames
|
|
149
151
|
if frames:
|
|
150
152
|
def _eval(x):
|
|
151
153
|
try:
|
|
152
|
-
return eval(x[col].replace('*', ''))
|
|
154
|
+
return eval(x[col].replace('*', '')) # localunit* とか
|
|
153
155
|
except Exception:
|
|
154
156
|
return x[col]
|
|
155
157
|
frame = self.Target.frame
|
|
156
158
|
items = sorted(self.all_items, reverse=self.__dir, key=_eval)
|
|
157
|
-
frames[:] = [frames[int(c[0])] for c in items]
|
|
159
|
+
frames[:] = [frames[int(c[0])] for c in items] # sort by new Id of items
|
|
158
160
|
|
|
159
161
|
lc = list(self.checked_items)
|
|
160
162
|
|
|
161
163
|
for j, c in enumerate(items):
|
|
162
164
|
self.Select(j, False)
|
|
163
165
|
self.CheckItem(j, int(c[0]) in lc)
|
|
164
|
-
for k, v in enumerate(c[1:]):
|
|
166
|
+
for k, v in enumerate(c[1:]): # update data except for id(0)
|
|
165
167
|
self.SetItem(j, k+1, v)
|
|
166
|
-
self.Target.select(frame)
|
|
167
|
-
|
|
168
|
+
self.Target.select(frame) # invokes [frame_shown] to select the item
|
|
169
|
+
|
|
168
170
|
def OnSelectAllItems(self, evt):
|
|
169
171
|
for j in range(self.ItemCount):
|
|
170
172
|
self.Select(j)
|
|
171
|
-
|
|
173
|
+
|
|
172
174
|
def OnLoadItems(self, evt):
|
|
173
175
|
self.parent.parent.load_index(view=self.Target)
|
|
174
|
-
|
|
176
|
+
|
|
175
177
|
def OnSaveItems(self, evt):
|
|
176
178
|
selected_frames = [self.Target.all_frames[j] for j in self.selected_items]
|
|
177
179
|
if selected_frames:
|
|
@@ -179,64 +181,65 @@ class CheckList(wx.ListCtrl, ListCtrlAutoWidthMixin, CtrlInterface):
|
|
|
179
181
|
self.parent.parent.save_index(frames=selected_frames)
|
|
180
182
|
else:
|
|
181
183
|
self.parent.message("No frame selected.")
|
|
182
|
-
|
|
184
|
+
|
|
183
185
|
def OnCopyInfo(self, evt):
|
|
184
186
|
selected_frames = [self.Target.all_frames[j] for j in self.selected_items]
|
|
185
187
|
if selected_frames:
|
|
186
|
-
text =
|
|
188
|
+
text = []
|
|
187
189
|
for frame in selected_frames:
|
|
188
|
-
text += pformat(frame.attributes, sort_dicts=0)
|
|
189
|
-
|
|
190
|
-
Clipboard.write(text)
|
|
190
|
+
text += [pformat(frame.attributes, sort_dicts=0)] # ALL attributes
|
|
191
|
+
Clipboard.write('\n'.join(text))
|
|
191
192
|
else:
|
|
192
193
|
self.parent.message("No frame selected.")
|
|
193
|
-
|
|
194
|
-
def
|
|
194
|
+
|
|
195
|
+
def OnEditLocalUnit(self, evt):
|
|
195
196
|
frame = self.Target.all_frames[self.focused_item]
|
|
196
197
|
with wx.TextEntryDialog(self, frame.name,
|
|
197
198
|
'Enter localunit', repr(frame.localunit)) as dlg:
|
|
198
199
|
if dlg.ShowModal() == wx.ID_OK:
|
|
199
200
|
frame.unit = eval(dlg.Value or 'None')
|
|
200
|
-
|
|
201
|
+
self.SetFocus()
|
|
202
|
+
|
|
201
203
|
def OnEditAnnotation(self, evt):
|
|
202
204
|
frame = self.Target.all_frames[self.focused_item]
|
|
203
205
|
with wx.TextEntryDialog(self, frame.name,
|
|
204
206
|
'Enter an annotation', frame.annotation) as dlg:
|
|
205
207
|
if dlg.ShowModal() == wx.ID_OK:
|
|
206
208
|
frame.annotation = dlg.Value
|
|
207
|
-
|
|
209
|
+
self.SetFocus()
|
|
210
|
+
|
|
208
211
|
def OnItemSelected(self, evt):
|
|
209
212
|
frame = self.Target.all_frames[evt.Index]
|
|
210
213
|
self.parent.message(frame.pathname)
|
|
211
214
|
evt.Skip()
|
|
212
|
-
|
|
215
|
+
|
|
213
216
|
## --------------------------------
|
|
214
|
-
## Actions of frame-handler
|
|
217
|
+
## Actions of frame-handler.
|
|
215
218
|
## --------------------------------
|
|
216
|
-
|
|
219
|
+
|
|
217
220
|
def on_frame_loaded(self, frame):
|
|
218
221
|
j = frame.index
|
|
219
222
|
self.InsertItem(j, str(j))
|
|
220
|
-
for k in range(j+1, self.ItemCount):
|
|
223
|
+
for k in range(j+1, self.ItemCount): # id(0) を更新する
|
|
221
224
|
self.SetItem(k, 0, str(k))
|
|
222
225
|
self.UpdateInfo(frame)
|
|
223
|
-
|
|
226
|
+
|
|
224
227
|
def on_frame_shown(self, frame):
|
|
225
228
|
j = frame.index
|
|
226
229
|
self.SetItemFont(j, self.Font.Bold())
|
|
227
230
|
self.Select(j)
|
|
228
231
|
self.Focus(j)
|
|
229
|
-
|
|
232
|
+
|
|
230
233
|
def on_frame_hidden(self, frame):
|
|
231
234
|
j = frame.index
|
|
232
235
|
self.SetItemFont(j, self.Font)
|
|
233
236
|
self.Select(j, False)
|
|
234
|
-
|
|
237
|
+
|
|
235
238
|
def on_frames_removed(self, indices):
|
|
236
239
|
with wx.FrozenWindow(self):
|
|
237
240
|
for j in reversed(indices):
|
|
238
241
|
self.DeleteItem(j)
|
|
239
|
-
for k in range(self.ItemCount):
|
|
242
|
+
for k in range(self.ItemCount): # id(0) を更新する
|
|
240
243
|
self.SetItem(k, 0, str(k))
|
|
241
244
|
|
|
242
245
|
|
|
@@ -246,15 +249,15 @@ class Plugin(Layer):
|
|
|
246
249
|
menukey = "Plugins/Extensions/&Buffer listbox\tAlt+b"
|
|
247
250
|
caption = "Property list"
|
|
248
251
|
dockable = False
|
|
249
|
-
|
|
252
|
+
|
|
250
253
|
@property
|
|
251
254
|
def all_pages(self):
|
|
252
255
|
return [self.nb.GetPage(i) for i in range(self.nb.PageCount)]
|
|
253
|
-
|
|
256
|
+
|
|
254
257
|
@property
|
|
255
258
|
def message(self):
|
|
256
259
|
return self.statusline
|
|
257
|
-
|
|
260
|
+
|
|
258
261
|
def Init(self):
|
|
259
262
|
self.nb = aui.AuiNotebook(self, size=(400,150),
|
|
260
263
|
style = (aui.AUI_NB_DEFAULT_STYLE|aui.AUI_NB_RIGHT)
|
|
@@ -275,12 +278,12 @@ class Plugin(Layer):
|
|
|
275
278
|
self.parent.select_view(self.nb.CurrentPage.Target)
|
|
276
279
|
evt.Skip()
|
|
277
280
|
self.nb.Bind(wx.EVT_CHILD_FOCUS, on_focus_set)
|
|
278
|
-
|
|
281
|
+
|
|
279
282
|
def attach(self, target, caption):
|
|
280
283
|
if target not in [lc.Target for lc in self.all_pages]:
|
|
281
284
|
lc = CheckList(self, target)
|
|
282
285
|
self.nb.AddPage(lc, caption)
|
|
283
|
-
|
|
286
|
+
|
|
284
287
|
def detach(self, target):
|
|
285
288
|
for k, lc in enumerate(self.all_pages):
|
|
286
289
|
if target is lc.Target:
|