|
1 #!/usr/bin/env python |
|
2 # -*- coding: utf-8 -*- |
|
3 |
|
4 #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor |
|
5 #based on the plcopen standard. |
|
6 # |
|
7 #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD |
|
8 # |
|
9 #See COPYING file for copyrights details. |
|
10 # |
|
11 #This library is free software; you can redistribute it and/or |
|
12 #modify it under the terms of the GNU General Public |
|
13 #License as published by the Free Software Foundation; either |
|
14 #version 2.1 of the License, or (at your option) any later version. |
|
15 # |
|
16 #This library is distributed in the hope that it will be useful, |
|
17 #but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
18 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
19 #General Public License for more details. |
|
20 # |
|
21 #You should have received a copy of the GNU General Public |
|
22 #License along with this library; if not, write to the Free Software |
|
23 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|
24 |
|
25 import wx |
|
26 |
|
27 from graphics import * |
|
28 |
|
29 #------------------------------------------------------------------------------- |
|
30 # Create New Block Dialog |
|
31 #------------------------------------------------------------------------------- |
|
32 |
|
33 [ID_FBDBLOCKDIALOG, ID_FBDBLOCKDIALOGNAME, |
|
34 ID_FBDBLOCKDIALOGTYPETREE, ID_FBDBLOCKDIALOGTYPEDESC, |
|
35 ID_FBDBLOCKDIALOGINPUTS, ID_FBDBLOCKDIALOGPREVIEW, |
|
36 ID_FBDBLOCKDIALOGEXECUTIONORDER, ID_FBDBLOCKDIALOGEXECUTIONCONTROL, |
|
37 ID_FBDBLOCKDIALOGSTATICTEXT1, ID_FBDBLOCKDIALOGSTATICTEXT2, |
|
38 ID_FBDBLOCKDIALOGSTATICTEXT3, ID_FBDBLOCKDIALOGSTATICTEXT4, |
|
39 ID_FBDBLOCKDIALOGSTATICTEXT5, ID_FBDBLOCKDIALOGSTATICTEXT6, |
|
40 ] = [wx.NewId() for _init_ctrls in range(14)] |
|
41 |
|
42 [CATEGORY, BLOCK] = range(2) |
|
43 |
|
44 class FBDBlockDialog(wx.Dialog): |
|
45 |
|
46 if wx.VERSION < (2, 6, 0): |
|
47 def Bind(self, event, function, id = None): |
|
48 if id is not None: |
|
49 event(self, id, function) |
|
50 else: |
|
51 event(self, function) |
|
52 |
|
53 def _init_coll_flexGridSizer1_Items(self, parent): |
|
54 parent.AddSizer(self.MainSizer, 0, border=20, flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT) |
|
55 parent.AddSizer(self.ButtonSizer, 0, border=20, flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT) |
|
56 |
|
57 def _init_coll_flexGridSizer1_Growables(self, parent): |
|
58 parent.AddGrowableCol(0) |
|
59 parent.AddGrowableRow(0) |
|
60 |
|
61 def _init_coll_MainSizer_Items(self, parent): |
|
62 parent.AddSizer(self.LeftBoxSizer, 1, border=5, flag=wx.GROW|wx.RIGHT) |
|
63 parent.AddSizer(self.RightGridSizer, 1, border=5, flag=wx.GROW|wx.LEFT) |
|
64 |
|
65 def _init_coll_LeftBoxSizer_Items(self, parent): |
|
66 parent.AddWindow(self.TypeTree, 3, border=5, flag=wx.GROW|wx.BOTTOM) |
|
67 parent.AddWindow(self.TypeDesc, 1, border=0, flag=wx.GROW) |
|
68 |
|
69 def _init_coll_RightGridSizer_Items(self, parent): |
|
70 parent.AddSizer(self.RightUpGridSizer, 0, border=0, flag=wx.GROW) |
|
71 parent.AddWindow(self.staticText6, 0, border=0, flag=wx.GROW) |
|
72 parent.AddWindow(self.Preview, 0, border=0, flag=wx.GROW) |
|
73 |
|
74 def _init_coll_RightGridSizer_Growables(self, parent): |
|
75 parent.AddGrowableCol(0) |
|
76 parent.AddGrowableRow(2) |
|
77 |
|
78 def _init_coll_RightUpGridSizer_Items(self, parent): |
|
79 parent.AddWindow(self.staticText2, 0, border=4, flag=wx.ALIGN_CENTER_VERTICAL|wx.TOP) |
|
80 parent.AddWindow(self.BlockName, 0, border=0, flag=wx.GROW) |
|
81 parent.AddWindow(self.staticText3, 0, border=4, flag=wx.ALIGN_CENTER_VERTICAL|wx.TOP) |
|
82 parent.AddWindow(self.Inputs, 0, border=0, flag=wx.GROW) |
|
83 parent.AddWindow(self.staticText4, 0, border=4, flag=wx.ALIGN_CENTER_VERTICAL|wx.TOP) |
|
84 parent.AddWindow(self.ExecutionOrder, 0, border=0, flag=wx.GROW) |
|
85 parent.AddWindow(self.staticText5, 0, border=4, flag=wx.ALIGN_CENTER_VERTICAL|wx.TOP) |
|
86 parent.AddWindow(self.ExecutionControl, 0, border=0, flag=wx.GROW) |
|
87 |
|
88 def _init_coll_RightUpGridSizer_Growables(self, parent): |
|
89 parent.AddGrowableCol(1) |
|
90 |
|
91 def _init_sizers(self): |
|
92 self.flexGridSizer1 = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10) |
|
93 self.MainSizer = wx.BoxSizer(wx.HORIZONTAL) |
|
94 self.LeftBoxSizer = wx.StaticBoxSizer(self.staticbox1, wx.VERTICAL) |
|
95 self.RightGridSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=5) |
|
96 self.RightUpGridSizer = wx.FlexGridSizer(cols=2, hgap=5, rows=4, vgap=5) |
|
97 |
|
98 self._init_coll_flexGridSizer1_Items(self.flexGridSizer1) |
|
99 self._init_coll_flexGridSizer1_Growables(self.flexGridSizer1) |
|
100 self._init_coll_MainSizer_Items(self.MainSizer) |
|
101 self._init_coll_LeftBoxSizer_Items(self.LeftBoxSizer) |
|
102 self._init_coll_RightGridSizer_Items(self.RightGridSizer) |
|
103 self._init_coll_RightGridSizer_Growables(self.RightGridSizer) |
|
104 self._init_coll_RightUpGridSizer_Items(self.RightUpGridSizer) |
|
105 self._init_coll_RightUpGridSizer_Growables(self.RightUpGridSizer) |
|
106 |
|
107 self.SetSizer(self.flexGridSizer1) |
|
108 |
|
109 def _init_ctrls(self, prnt): |
|
110 wx.Dialog.__init__(self, id=ID_FBDBLOCKDIALOG, |
|
111 name='FBDBlockDialog', parent=prnt, pos=wx.Point(376, 223), |
|
112 size=wx.Size(600, 400), style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER, |
|
113 title=_('Block Properties')) |
|
114 self.SetClientSize(wx.Size(600, 400)) |
|
115 |
|
116 self.staticbox1 = wx.StaticBox(id=ID_FBDBLOCKDIALOGSTATICTEXT1, |
|
117 label=_('Type:'), name='staticBox1', parent=self, |
|
118 pos=wx.Point(0, 0), size=wx.Size(0, 0), style=0) |
|
119 |
|
120 self.staticText2 = wx.StaticText(id=ID_FBDBLOCKDIALOGSTATICTEXT2, |
|
121 label=_('Name:'), name='staticText2', parent=self, |
|
122 pos=wx.Point(0, 0), size=wx.DefaultSize, style=0) |
|
123 |
|
124 self.staticText3 = wx.StaticText(id=ID_FBDBLOCKDIALOGSTATICTEXT2, |
|
125 label=_('Inputs:'), name='staticText4', parent=self, |
|
126 pos=wx.Point(0, 0), size=wx.DefaultSize, style=0) |
|
127 |
|
128 self.staticText4 = wx.StaticText(id=ID_FBDBLOCKDIALOGSTATICTEXT4, |
|
129 label=_('Execution Order:'), name='staticText4', parent=self, |
|
130 pos=wx.Point(0, 0), size=wx.DefaultSize, style=0) |
|
131 |
|
132 self.staticText5 = wx.StaticText(id=ID_FBDBLOCKDIALOGSTATICTEXT5, |
|
133 label=_('Execution Control:'), name='staticText5', parent=self, |
|
134 pos=wx.Point(0, 0), size=wx.DefaultSize, style=0) |
|
135 |
|
136 self.staticText6 = wx.StaticText(id=ID_FBDBLOCKDIALOGSTATICTEXT6, |
|
137 label=_('Preview:'), name='staticText6', parent=self, |
|
138 pos=wx.Point(0, 0), size=wx.DefaultSize, style=0) |
|
139 |
|
140 if wx.Platform == '__WXMSW__': |
|
141 treestyle = wx.TR_HAS_BUTTONS|wx.TR_SINGLE|wx.SUNKEN_BORDER |
|
142 else: |
|
143 treestyle = wx.TR_HAS_BUTTONS|wx.TR_HIDE_ROOT|wx.TR_SINGLE|wx.SUNKEN_BORDER |
|
144 self.TypeTree = wx.TreeCtrl(id=ID_FBDBLOCKDIALOGTYPETREE, |
|
145 name='TypeTree', parent=self, pos=wx.Point(0, 0), |
|
146 size=wx.Size(0, 0), style=treestyle) |
|
147 self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTypeTreeItemSelected, |
|
148 id=ID_FBDBLOCKDIALOGTYPETREE) |
|
149 |
|
150 self.TypeDesc = wx.TextCtrl(id=ID_FBDBLOCKDIALOGTYPEDESC, |
|
151 name='TypeDesc', parent=self, pos=wx.Point(0, 0), |
|
152 size=wx.Size(0, 0), style=wx.TE_READONLY|wx.TE_MULTILINE) |
|
153 |
|
154 self.BlockName = wx.TextCtrl(id=ID_FBDBLOCKDIALOGNAME, value='', |
|
155 name='BlockName', parent=self, pos=wx.Point(0, 0), |
|
156 size=wx.Size(0, 24), style=0) |
|
157 self.Bind(wx.EVT_TEXT, self.OnNameChanged, id=ID_FBDBLOCKDIALOGNAME) |
|
158 |
|
159 self.Inputs = wx.SpinCtrl(id=ID_FBDBLOCKDIALOGINPUTS, |
|
160 name='Inputs', parent=self, pos=wx.Point(0, 0), |
|
161 size=wx.Size(0, 24), style=wx.SP_ARROW_KEYS, min=2, max=20) |
|
162 self.Bind(wx.EVT_SPINCTRL, self.OnInputsChanged, id=ID_FBDBLOCKDIALOGINPUTS) |
|
163 |
|
164 self.ExecutionOrder = wx.SpinCtrl(id=ID_FBDBLOCKDIALOGEXECUTIONORDER, |
|
165 name='ExecutionOrder', parent=self, pos=wx.Point(0, 0), |
|
166 size=wx.Size(0, 24), style=wx.SP_ARROW_KEYS, min=0) |
|
167 self.Bind(wx.EVT_SPINCTRL, self.OnExecutionOrderChanged, id=ID_FBDBLOCKDIALOGEXECUTIONORDER) |
|
168 |
|
169 self.ExecutionControl = wx.CheckBox(id=ID_FBDBLOCKDIALOGEXECUTIONCONTROL, |
|
170 name='ExecutionControl', parent=self, pos=wx.Point(0, 0), |
|
171 size=wx.Size(0, 24), style=0) |
|
172 self.Bind(wx.EVT_CHECKBOX, self.OnExecutionOrderChanged, id=ID_FBDBLOCKDIALOGEXECUTIONCONTROL) |
|
173 |
|
174 self.Preview = wx.Panel(id=ID_FBDBLOCKDIALOGPREVIEW, |
|
175 name='Preview', parent=self, pos=wx.Point(0, 0), |
|
176 size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER) |
|
177 self.Preview.SetBackgroundColour(wx.Colour(255,255,255)) |
|
178 setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE) |
|
179 setattr(self.Preview, "GetScaling", lambda:None) |
|
180 setattr(self.Preview, "GetBlockType", self.Controler.GetBlockType) |
|
181 setattr(self.Preview, "IsOfType", self.Controler.IsOfType) |
|
182 |
|
183 self.ButtonSizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE) |
|
184 if wx.VERSION >= (2, 5, 0): |
|
185 self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.ButtonSizer.GetAffirmativeButton().GetId()) |
|
186 self.Preview.Bind(wx.EVT_PAINT, self.OnPaint) |
|
187 else: |
|
188 self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.ButtonSizer.GetChildren()[0].GetSizer().GetChildren()[0].GetWindow().GetId()) |
|
189 wx.EVT_PAINT(self.Preview, self.OnPaint) |
|
190 |
|
191 self._init_sizers() |
|
192 |
|
193 def __init__(self, parent, controler): |
|
194 self.Controler = controler |
|
195 self._init_ctrls(parent) |
|
196 self.BlockName.SetValue("") |
|
197 self.BlockName.Enable(False) |
|
198 self.Inputs.Enable(False) |
|
199 self.Block = None |
|
200 self.MinBlockSize = None |
|
201 self.First = True |
|
202 |
|
203 self.PouNames = [] |
|
204 self.PouElementNames = [] |
|
205 |
|
206 def SetPreviewFont(self, font): |
|
207 self.Preview.SetFont(font) |
|
208 |
|
209 def FindTreeItem(self, root, name, inputs = None): |
|
210 if root.IsOk(): |
|
211 pydata = self.TypeTree.GetPyData(root) |
|
212 type_inputs = pydata.get("inputs", None) |
|
213 type_extension = pydata.get("extension", None) |
|
214 if inputs is not None and type_inputs is not None: |
|
215 if type_extension is not None: |
|
216 same_inputs = type_inputs == inputs[:type_extension] |
|
217 else: |
|
218 same_inputs = type_inputs == inputs |
|
219 else: |
|
220 same_inputs = True |
|
221 if self.TypeTree.GetItemText(root) == name and same_inputs: |
|
222 return root |
|
223 else: |
|
224 if wx.VERSION < (2, 6, 0): |
|
225 item, root_cookie = self.TypeTree.GetFirstChild(root, 0) |
|
226 else: |
|
227 item, root_cookie = self.TypeTree.GetFirstChild(root) |
|
228 while item.IsOk(): |
|
229 result = self.FindTreeItem(item, name, inputs) |
|
230 if result: |
|
231 return result |
|
232 item, root_cookie = self.TypeTree.GetNextChild(root, root_cookie) |
|
233 return None |
|
234 |
|
235 def OnOK(self, event): |
|
236 selected = self.TypeTree.GetSelection() |
|
237 block_name = self.BlockName.GetValue() |
|
238 name_enabled = self.BlockName.IsEnabled() |
|
239 if not selected.IsOk() or self.TypeTree.GetItemParent(selected) == self.TypeTree.GetRootItem() or selected == self.TypeTree.GetRootItem(): |
|
240 message = wx.MessageDialog(self, _("Form isn't complete. Valid block type must be selected!"), _("Error"), wx.OK|wx.ICON_ERROR) |
|
241 message.ShowModal() |
|
242 message.Destroy() |
|
243 elif name_enabled and block_name == "": |
|
244 message = wx.MessageDialog(self, _("Form isn't complete. Name must be filled!"), _("Error"), wx.OK|wx.ICON_ERROR) |
|
245 message.ShowModal() |
|
246 message.Destroy() |
|
247 elif name_enabled and not TestIdentifier(block_name): |
|
248 message = wx.MessageDialog(self, _("\"%s\" is not a valid identifier!")%block_name, _("Error"), wx.OK|wx.ICON_ERROR) |
|
249 message.ShowModal() |
|
250 message.Destroy() |
|
251 elif name_enabled and block_name.upper() in IEC_KEYWORDS: |
|
252 message = wx.MessageDialog(self, _("\"%s\" is a keyword. It can't be used!")%block_name, _("Error"), wx.OK|wx.ICON_ERROR) |
|
253 message.ShowModal() |
|
254 message.Destroy() |
|
255 elif name_enabled and block_name.upper() in self.PouNames: |
|
256 message = wx.MessageDialog(self, _("\"%s\" pou already exists!")%block_name, _("Error"), wx.OK|wx.ICON_ERROR) |
|
257 message.ShowModal() |
|
258 message.Destroy() |
|
259 elif name_enabled and block_name.upper() in self.PouElementNames: |
|
260 message = wx.MessageDialog(self, _("\"%s\" element for this pou already exists!")%block_name, _("Error"), wx.OK|wx.ICON_ERROR) |
|
261 message.ShowModal() |
|
262 message.Destroy() |
|
263 else: |
|
264 self.EndModal(wx.ID_OK) |
|
265 |
|
266 def SetBlockList(self, blocktypes): |
|
267 if wx.Platform == '__WXMSW__': |
|
268 root = self.TypeTree.AddRoot(_("Block Types")) |
|
269 else: |
|
270 root = self.TypeTree.AddRoot("") |
|
271 self.TypeTree.SetPyData(root, {"type" : CATEGORY}) |
|
272 for category in blocktypes: |
|
273 category_name = category["name"] |
|
274 category_item = self.TypeTree.AppendItem(root, _(category_name)) |
|
275 self.TypeTree.SetPyData(category_item, {"type" : CATEGORY}) |
|
276 for blocktype in category["list"]: |
|
277 blocktype_item = self.TypeTree.AppendItem(category_item, blocktype["name"]) |
|
278 block_data = {"type" : BLOCK, |
|
279 "inputs" : tuple([type for name, type, modifier in blocktype["inputs"]]), |
|
280 "extension" : None} |
|
281 if blocktype["extensible"]: |
|
282 block_data["extension"] = len(blocktype["inputs"]) |
|
283 self.TypeTree.SetPyData(blocktype_item, block_data) |
|
284 if wx.Platform == '__WXMSW__': |
|
285 self.TypeTree.Expand(root) |
|
286 |
|
287 def SetMinBlockSize(self, size): |
|
288 self.MinBlockSize = size |
|
289 |
|
290 def SetPouNames(self, pou_names): |
|
291 self.PouNames = [pou_name.upper() for pou_name in pou_names] |
|
292 |
|
293 def SetPouElementNames(self, element_names): |
|
294 self.PouElementNames = [element_name.upper() for element_name in element_names] |
|
295 |
|
296 def SetValues(self, values): |
|
297 blocktype = values.get("type", None) |
|
298 if blocktype is not None: |
|
299 inputs = values.get("inputs", None) |
|
300 item = self.FindTreeItem(self.TypeTree.GetRootItem(), blocktype, inputs) |
|
301 if item: |
|
302 self.TypeTree.SelectItem(item) |
|
303 for name, value in values.items(): |
|
304 if name == "name": |
|
305 self.BlockName.SetValue(value) |
|
306 elif name == "extension": |
|
307 self.Inputs.SetValue(value) |
|
308 elif name == "executionOrder": |
|
309 self.ExecutionOrder.SetValue(value) |
|
310 elif name == "executionControl": |
|
311 self.ExecutionControl.SetValue(value) |
|
312 self.RefreshPreview() |
|
313 |
|
314 def GetValues(self): |
|
315 values = {} |
|
316 item = self.TypeTree.GetSelection() |
|
317 values["type"] = self.TypeTree.GetItemText(item) |
|
318 values["inputs"] = self.TypeTree.GetPyData(item)["inputs"] |
|
319 if self.BlockName.GetValue() != "": |
|
320 values["name"] = self.BlockName.GetValue() |
|
321 values["width"], values["height"] = self.Block.GetSize() |
|
322 values["extension"] = self.Inputs.GetValue() |
|
323 values["executionOrder"] = self.ExecutionOrder.GetValue() |
|
324 values["executionControl"] = self.ExecutionControl.GetValue() |
|
325 return values |
|
326 |
|
327 def OnTypeTreeItemSelected(self, event): |
|
328 self.BlockName.SetValue("") |
|
329 selected = event.GetItem() |
|
330 pydata = self.TypeTree.GetPyData(selected) |
|
331 if pydata["type"] != CATEGORY: |
|
332 blocktype = self.Controler.GetBlockType(self.TypeTree.GetItemText(selected), pydata["inputs"]) |
|
333 if blocktype: |
|
334 self.Inputs.SetValue(len(blocktype["inputs"])) |
|
335 self.Inputs.Enable(blocktype["extensible"]) |
|
336 self.BlockName.Enable(blocktype["type"] != "function") |
|
337 comment = blocktype["comment"] |
|
338 self.TypeDesc.SetValue(_(comment) + blocktype.get("usage", "")) |
|
339 wx.CallAfter(self.RefreshPreview) |
|
340 else: |
|
341 self.BlockName.Enable(False) |
|
342 self.Inputs.Enable(False) |
|
343 self.Inputs.SetValue(2) |
|
344 self.TypeDesc.SetValue("") |
|
345 wx.CallAfter(self.ErasePreview) |
|
346 else: |
|
347 self.BlockName.Enable(False) |
|
348 self.Inputs.Enable(False) |
|
349 self.Inputs.SetValue(2) |
|
350 self.TypeDesc.SetValue("") |
|
351 wx.CallAfter(self.ErasePreview) |
|
352 event.Skip() |
|
353 |
|
354 def OnNameChanged(self, event): |
|
355 if self.BlockName.IsEnabled(): |
|
356 self.RefreshPreview() |
|
357 event.Skip() |
|
358 |
|
359 def OnInputsChanged(self, event): |
|
360 if self.Inputs.IsEnabled(): |
|
361 self.RefreshPreview() |
|
362 event.Skip() |
|
363 |
|
364 def OnExecutionOrderChanged(self, event): |
|
365 self.RefreshPreview() |
|
366 event.Skip() |
|
367 |
|
368 def OnExecutionControlChanged(self, event): |
|
369 self.RefreshPreview() |
|
370 event.Skip() |
|
371 |
|
372 def ErasePreview(self): |
|
373 dc = wx.ClientDC(self.Preview) |
|
374 dc.Clear() |
|
375 self.Block = None |
|
376 |
|
377 def RefreshPreview(self): |
|
378 dc = wx.ClientDC(self.Preview) |
|
379 dc.SetFont(self.Preview.GetFont()) |
|
380 dc.Clear() |
|
381 item = self.TypeTree.GetSelection() |
|
382 if item.IsOk(): |
|
383 pydata = self.TypeTree.GetPyData(item) |
|
384 if pydata["type"] == CATEGORY: |
|
385 self.Block = None |
|
386 else: |
|
387 blocktype = self.TypeTree.GetItemText(item) |
|
388 if blocktype: |
|
389 self.Block = FBD_Block(self.Preview, blocktype, |
|
390 self.BlockName.GetValue(), |
|
391 extension = self.Inputs.GetValue(), |
|
392 inputs = pydata["inputs"], |
|
393 executionControl = self.ExecutionControl.GetValue(), |
|
394 executionOrder = self.ExecutionOrder.GetValue()) |
|
395 width, height = self.MinBlockSize |
|
396 min_width, min_height = self.Block.GetMinSize() |
|
397 width, height = max(min_width, width), max(min_height, height) |
|
398 self.Block.SetSize(width, height) |
|
399 clientsize = self.Preview.GetClientSize() |
|
400 x = (clientsize.width - width) / 2 |
|
401 y = (clientsize.height - height) / 2 |
|
402 self.Block.SetPosition(x, y) |
|
403 self.Block.Draw(dc) |
|
404 else: |
|
405 self.Block = None |
|
406 |
|
407 def OnPaint(self, event): |
|
408 if self.Block is not None: |
|
409 self.RefreshPreview() |
|
410 event.Skip() |