1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
|
# Authors: see git history
#
# Copyright (c) 2024 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
import os
from sys import platform
import wx
from wx.lib.intctrl import IntCtrl
from ...debug.debug import debug
from ...i18n import _
from ...utils import get_resource_dir
from ...utils.settings import global_settings
from . import SimulatorSlider
class ControlPanel(wx.Panel):
""""""
@debug.time
def __init__(self, parent, *args, **kwargs):
""""""
self.parent = parent
self.stitch_plan = kwargs.pop('stitch_plan', None)
self.detach_callback = kwargs.pop('detach_callback', None)
self.target_stitches_per_second = kwargs.pop('stitches_per_second')
self.target_duration = kwargs.pop('target_duration')
kwargs['style'] = wx.BORDER_SUNKEN
wx.Panel.__init__(self, parent, *args, **kwargs)
self.drawing_panel = None
self.num_stitches = 0
self.current_stitch = 0
self.speed = global_settings['simulator_speed']
self.direction = 1
self._last_color_block_end = 0
self.icons_dir = get_resource_dir("icons")
# Widgets
self.button_size = self.GetTextExtent("M").y * 2
self.button_style = wx.BU_EXACTFIT | wx.BU_NOTEXT
self.btnMinus = wx.Button(self, -1, style=self.button_style)
self.btnMinus.Bind(wx.EVT_BUTTON, self.animation_slow_down)
self.btnMinus.SetBitmap(self.load_icon('slower'))
self.btnMinus.SetToolTip(_('Slow down (arrow down)'))
self.btnPlus = wx.Button(self, -1, style=self.button_style)
self.btnPlus.Bind(wx.EVT_BUTTON, self.animation_speed_up)
self.btnPlus.SetBitmap(self.load_icon('faster'))
self.btnPlus.SetToolTip(_('Speed up (arrow up)'))
self.btnBackwardStitch = wx.Button(self, -1, style=self.button_style)
self.btnBackwardStitch.Bind(wx.EVT_BUTTON, self.animation_one_stitch_backward)
self.btnBackwardStitch.SetBitmap(self.load_icon('backward_stitch'))
self.btnBackwardStitch.SetToolTip(_('Go backward one stitch (-)'))
self.btnForwardStitch = wx.Button(self, -1, style=self.button_style)
self.btnForwardStitch.Bind(wx.EVT_BUTTON, self.animation_one_stitch_forward)
self.btnForwardStitch.SetBitmap(self.load_icon('forward_stitch'))
self.btnForwardStitch.SetToolTip(_('Go forward one stitch (+)'))
self.btnBackwardCommand = wx.Button(self, -1, style=self.button_style)
self.btnBackwardCommand.Bind(wx.EVT_BUTTON, self.animation_one_command_backward)
self.btnBackwardCommand.SetBitmap(self.load_icon('backward_command'))
self.btnBackwardCommand.SetToolTip(_('Go backward one command (page-down)'))
self.btnForwardCommand = wx.Button(self, -1, style=self.button_style)
self.btnForwardCommand.Bind(wx.EVT_BUTTON, self.animation_one_command_forward)
self.btnForwardCommand.SetBitmap(self.load_icon('forward_command'))
self.btnForwardCommand.SetToolTip(_('Go forward one command (page-up)'))
self.btnDirection = wx.Button(self, -1, style=self.button_style)
self.btnDirection.Bind(wx.EVT_BUTTON, self.on_direction_button)
self.btnDirection.SetBitmap(self.load_icon('direction'))
self.btnDirection.SetToolTip(_('Switch animation direction (arrow left, arrow right)'))
self.btnPlay = wx.BitmapToggleButton(self, -1, style=self.button_style)
self.btnPlay.Bind(wx.EVT_TOGGLEBUTTON, self.on_play_button)
self.btnPlay.SetBitmap(self.load_icon('play'))
self.btnPlay.SetToolTip(_('Play (P)'))
self.btnRestart = wx.Button(self, -1, style=self.button_style)
self.btnRestart.Bind(wx.EVT_BUTTON, self.animation_restart)
self.btnRestart.SetBitmap(self.load_icon('restart'))
self.btnRestart.SetToolTip(_('Restart (R)'))
self.slider = SimulatorSlider(self, -1, value=1, minValue=1, maxValue=2)
self.slider.Bind(wx.EVT_SLIDER, self.on_slider)
self.stitchBox = IntCtrl(self, -1, value=1, min=1, max=2, limited=True, allow_none=True,
size=((100, -1)), style=wx.TE_PROCESS_ENTER)
self.stitchBox.Clear()
self.stitchBox.Bind(wx.EVT_LEFT_DOWN, self.on_stitch_box_focus)
self.stitchBox.Bind(wx.EVT_SET_FOCUS, self.on_stitch_box_focus)
self.stitchBox.Bind(wx.EVT_TEXT_ENTER, self.on_stitch_box_focusout)
self.stitchBox.Bind(wx.EVT_KILL_FOCUS, self.on_stitch_box_focusout)
self.Bind(wx.EVT_LEFT_DOWN, self.on_stitch_box_focusout)
self.totalstitchText = wx.StaticText(self, -1, label="")
extent = self.totalstitchText.GetTextExtent("0000000")
self.totalstitchText.SetMinSize(extent)
# Layout
self.hbSizer1 = wx.BoxSizer(wx.HORIZONTAL)
self.hbSizer1.Add(self.slider, 1, wx.EXPAND | wx.RIGHT, 10)
self.hbSizer1.Add(self.stitchBox, 0, wx.ALIGN_TOP | wx.TOP, 25)
self.hbSizer1.Add((1, 1), 0, wx.RIGHT, 10)
self.hbSizer1.Add(self.totalstitchText, 0, wx.ALIGN_TOP | wx.TOP, 25)
self.hbSizer1.Add((1, 1), 0, wx.RIGHT, 10)
self.controls_sizer = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, _("Controls")), wx.HORIZONTAL)
self.controls_inner_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.controls_inner_sizer.Add(self.btnBackwardCommand, 0, wx.EXPAND | wx.ALL, 2)
self.controls_inner_sizer.Add(self.btnBackwardStitch, 0, wx.EXPAND | wx.ALL, 2)
self.controls_inner_sizer.Add(self.btnForwardStitch, 0, wx.EXPAND | wx.ALL, 2)
self.controls_inner_sizer.Add(self.btnForwardCommand, 0, wx.EXPAND | wx.ALL, 2)
self.controls_inner_sizer.Add(self.btnDirection, 0, wx.EXPAND | wx.ALL, 2)
self.controls_inner_sizer.Add(self.btnPlay, 0, wx.EXPAND | wx.ALL, 2)
self.controls_inner_sizer.Add(self.btnRestart, 0, wx.EXPAND | wx.ALL, 2)
self.controls_sizer.Add((1, 1), 1)
self.controls_sizer.Add(self.controls_inner_sizer, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 10)
self.controls_sizer.Add((1, 1), 1)
self.speed_sizer = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, _("Speed")), wx.VERTICAL)
self.speed_buttons_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.speed_buttons_sizer.Add((1, 1), 1)
self.speed_buttons_sizer.Add(self.btnMinus, 0, wx.ALL, 2)
self.speed_buttons_sizer.Add(self.btnPlus, 0, wx.ALL, 2)
self.speed_buttons_sizer.Add((1, 1), 1)
self.speed_sizer.Add(self.speed_buttons_sizer, 0, wx.EXPAND | wx.ALL)
self.speed_text = wx.StaticText(self, wx.ID_ANY, label="", style=wx.ALIGN_CENTRE_HORIZONTAL | wx.ST_NO_AUTORESIZE)
self.speed_text.SetFont(wx.Font(wx.FontInfo(10).Bold()))
extent = self.speed_text.GetTextExtent(self.format_speed_text(100000))
self.speed_text.SetMinSize(extent)
self.speed_sizer.Add(self.speed_text, 0, wx.EXPAND | wx.ALL, 5)
# A normal BoxSizer can only make child components the same or
# proportional size. A FlexGridSizer can split up the available extra
# space evenly among all growable columns.
self.control_row2_sizer = wx.FlexGridSizer(cols=3, vgap=0, hgap=5)
self.control_row2_sizer.AddGrowableCol(0)
self.control_row2_sizer.AddGrowableCol(1)
self.control_row2_sizer.AddGrowableCol(2)
self.control_row2_sizer.Add(self.controls_sizer, 0, wx.EXPAND)
self.control_row2_sizer.Add(self.speed_sizer, 0, wx.EXPAND)
self.vbSizer = vbSizer = wx.BoxSizer(wx.VERTICAL)
vbSizer.Add(self.hbSizer1, 1, wx.EXPAND | wx.ALL, 10)
vbSizer.Add(self.control_row2_sizer, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
self.SetSizerAndFit(vbSizer)
# wait for layouts so that panel size is set
if self.stitch_plan:
wx.CallLater(50, self.load, self.stitch_plan)
def set_drawing_panel(self, drawing_panel):
self.drawing_panel = drawing_panel
self.drawing_panel.set_speed(self.speed)
def _set_num_stitches(self, num_stitches):
if num_stitches < 2:
# otherwise the slider and intctrl get mad
num_stitches = 2
self.num_stitches = num_stitches
self.stitchBox.SetValue(1)
self.stitchBox.SetMax(num_stitches)
self.slider.SetMax(num_stitches)
self.totalstitchText.SetLabel(f"/ { num_stitches }")
self.choose_speed()
def clear(self):
self.stitches = []
self._set_num_stitches(0)
self.slider.clear()
self.stitchBox.Clear()
self.totalstitchText.SetLabel("")
def load(self, stitch_plan):
self.clear()
self.stitches = []
self._set_num_stitches(stitch_plan.num_stitches)
stitch_num = 0
last_block_end = 1
for color_block in stitch_plan.color_blocks:
self.stitches.extend(color_block.stitches)
start = stitch_num + 1
end = start + color_block.num_stitches - 1
self.slider.add_color_section(color_block.color.rgb, last_block_end, end)
last_block_end = end
for stitch_num, stitch in enumerate(color_block.stitches, start):
if stitch.trim:
self.slider.add_marker("trim", stitch_num)
elif stitch.stop:
self.slider.add_marker("stop", stitch_num)
elif stitch.jump:
self.slider.add_marker("jump", stitch_num)
elif stitch.color_change:
self.slider.add_marker("color_change", stitch_num)
def is_dark_theme(self):
return wx.SystemSettings().GetAppearance().IsDark()
def load_icon(self, icon_name):
if self.is_dark_theme() and platform != "win32":
icon = wx.Image(os.path.join(self.icons_dir, f"{icon_name}_dark.png"))
else:
icon = wx.Image(os.path.join(self.icons_dir, f"{icon_name}.png"))
icon.Rescale(self.button_size, self.button_size, wx.IMAGE_QUALITY_HIGH)
return icon.ConvertToBitmap()
def choose_speed(self):
if not global_settings['simulator_adaptive_speed']:
self.set_speed(global_settings['simulator_speed'])
return
if self.target_duration:
self.set_speed(int(self.num_stitches / float(self.target_duration)))
else:
self.set_speed(self.target_stitches_per_second)
def animation_forward(self, event=None):
self.drawing_panel.forward()
self.direction = 1
self.update_speed_text()
def animation_reverse(self, event=None):
self.drawing_panel.reverse()
self.direction = -1
self.update_speed_text()
def on_direction_button(self, event):
if self.direction == -1:
self.animation_forward()
else:
self.animation_reverse()
def set_speed(self, speed):
global_settings['simulator_speed'] = speed
self.speed = int(max(speed, 1))
self.update_speed_text()
if self.drawing_panel:
self.drawing_panel.set_speed(self.speed)
def format_speed_text(self, speed):
return _('%d stitches/sec') % speed
def update_speed_text(self):
self.speed_text.SetLabel(self.format_speed_text(self.speed * self.direction))
def on_slider(self, event):
self.animation_pause()
stitch = event.GetEventObject().GetValue()
self.stitchBox.SetValue(stitch)
if self.drawing_panel:
self.drawing_panel.set_current_stitch(stitch)
self.parent.SetFocus()
def on_current_stitch(self, stitch, command):
if self.current_stitch != stitch:
self.current_stitch = stitch
self.slider.SetValue(stitch)
stitch = min(self.stitchBox.GetMax(), stitch)
self.stitchBox.SetValue(stitch)
def on_stitch_box_focus(self, event):
self.animation_pause()
self.parent.SetAcceleratorTable(wx.AcceleratorTable([]))
event.Skip()
def on_stitch_box_focusout(self, event):
self.parent.SetAcceleratorTable(self.parent.accel_table)
stitch = self.stitchBox.GetValue()
# We now want to remove the focus from the stitchBox.
# In Windows it won't work if we set focus to self.parent, while setting the focus to the
# top level would work. This in turn would activate the trim button in Linux. So let's
# set the focus on the slider instead where it doesn't cause any harm in any of the operating systems
self.slider.SetFocus()
if stitch is None:
stitch = 1
self.stitchBox.SetValue(1)
self.slider.SetValue(stitch)
if self.drawing_panel:
self.drawing_panel.set_current_stitch(stitch)
event.Skip()
def animation_slow_down(self, event):
""""""
self.set_speed(self.speed / 2.0)
def animation_speed_up(self, event):
""""""
self.set_speed(self.speed * 2.0)
def animation_pause(self, event=None):
self.drawing_panel.stop()
def animation_start(self, event=None):
self.drawing_panel.go()
def on_start(self):
self.btnPlay.SetValue(True)
def on_stop(self):
self.btnPlay.SetValue(False)
def on_play_button(self, event):
play = self.btnPlay.GetValue()
if play:
self.animation_start()
else:
self.animation_pause()
def play_or_pause(self, event):
if self.drawing_panel.animating:
self.animation_pause()
else:
self.animation_start()
def animation_one_stitch_forward(self, event):
self.animation_pause()
self.drawing_panel.one_stitch_forward()
def animation_one_stitch_backward(self, event):
self.animation_pause()
self.drawing_panel.one_stitch_backward()
def animation_one_command_backward(self, event):
self.animation_pause()
stitch_number = self.current_stitch - 1
while stitch_number >= 1:
# stitch number shown to the user starts at 1
stitch = self.stitches[stitch_number - 1]
if stitch.jump or stitch.trim or stitch.stop or stitch.color_change:
break
stitch_number -= 1
self.drawing_panel.set_current_stitch(stitch_number)
def animation_one_command_forward(self, event):
self.animation_pause()
stitch_number = self.current_stitch + 1
while stitch_number <= self.num_stitches:
# stitch number shown to the user starts at 1
stitch = self.stitches[stitch_number - 1]
if stitch.jump or stitch.trim or stitch.stop or stitch.color_change:
break
stitch_number += 1
self.drawing_panel.set_current_stitch(stitch_number)
def animation_restart(self, event):
self.drawing_panel.restart()
|