Coverage for src/robotide/ui/review.py: 27%
433 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-06 10:40 +0100
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-06 10:40 +0100
1# Copyright 2008-2015 Nokia Networks
2# Copyright 2016- Robot Framework Foundation
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
16import builtins 1ab
17import os 1ab
18import re 1ab
19import time 1ab
20from threading import Thread 1ab
22import wx 1ab
23import wx.lib.mixins.listctrl as listmix 1ab
24from wx import Colour 1ab
26from ..context import IS_MAC 1ab
27from ..spec.iteminfo import LibraryKeywordInfo 1ab
28from ..ui.searchdots import DottedSearch 1ab
29from ..usages.commands import FindUsages 1ab
30from ..widgets import ButtonWithHandler, Label, RIDEDialog 1ab
32_ = wx.GetTranslation # To keep linter/code analyser happy 1ab
33builtins.__dict__['_'] = wx.GetTranslation 1ab
36class ReviewDialog(RIDEDialog): 1ab
38 def __init__(self, controller, frame): 1ab
39 RIDEDialog.__init__(self, parent=frame, title=_("Search unused keywords"),
40 style=wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN |
41 wx.FRAME_FLOAT_ON_PARENT)
42 # set Left to Right direction (while we don't have localization)
43 self.SetLayoutDirection(wx.Layout_LeftToRight)
44 self.index = 0
45 self.frame = frame
46 self._dots = None
47 self._search_model = ResultModel()
48 self._runner = ReviewRunner(controller, self._search_model)
49 self._build_ui()
50 self._make_bindings()
51 self._set_default_values()
52 self.CenterOnParent()
54 def _build_ui(self): 1ab
55 self.SetSize((800, 600))
56 self.SetBackgroundColour(Colour(self.color_background))
57 self.SetForegroundColour(Colour(self.color_foreground))
58 self.SetSizer(wx.BoxSizer(wx.VERTICAL))
59 self._build_header()
60 self._build_filter()
61 self._build_notebook()
62 self._build_unused_keywords()
63 self._build_controls()
65 def _build_header(self): 1ab
66 label_introduction = wx.StaticText(self, label=_("This dialog helps you finding unused "
67 "keywords within your opened project.\nIf "
68 "you want, you can restrict the search to "
69 "a set of files with the filter."))
70 label_filter_is = wx.StaticText(self, label=_('Filter is'))
71 self.label_filter_status = wx.StaticText(self, label=_('inactive'))
72 header_sizer = wx.BoxSizer(wx.HORIZONTAL)
73 header_sizer.Add(label_introduction, 0, wx.ALL | wx.EXPAND, 3)
74 header_sizer.AddStretchSpacer(1)
75 header_sizer.Add(label_filter_is, 0,
76 wx.LEFT | wx.TOP | wx.BOTTOM | wx.ALIGN_BOTTOM, 3)
77 if wx.VERSION < (4, 1, 0):
78 header_sizer.Add(self.label_filter_status, 0, wx.ALL | wx.ALIGN_BOTTOM | wx.ALIGN_RIGHT, 3)
79 else:
80 header_sizer.Add(self.label_filter_status, 0, wx.ALL | wx.ALIGN_BOTTOM, 3)
81 self.Sizer.Add(header_sizer, 0, wx.ALL | wx.EXPAND, 3)
83 def _build_filter(self): 1ab
84 self._filter_pane = MyCollapsiblePane(self, label=_("Filter"),
85 style=wx.CP_DEFAULT_STYLE | wx.CP_NO_TLW_RESIZE)
86 self._filter_pane.SetBackgroundColour(Colour(self.color_background))
87 self._filter_pane.SetForegroundColour(Colour(self.color_foreground))
88 self._filter_input = wx.TextCtrl(self._filter_pane.GetPane(),
89 size=(-1, 20))
90 self._filter_regex_switch = wx.CheckBox(self._filter_pane.GetPane(),
91 wx.ID_ANY, label=_("Use RegEx"))
92 self._filter_info = wx.StaticText(self._filter_pane.GetPane(),
93 label=_("Here you can define one or more strings separated"
94 " by comma (e.g. common,abc,123).\nThe filter "
95 "matches if at least one string is part of "
96 "the filename.\nIf you don\'t enter any strings, "
97 "all opened files are included"))
98 filter_source_box = wx.StaticBox(self._filter_pane.GetPane(), label=_("Search"))
99 self._filter_source_testcases = wx.CheckBox(self._filter_pane.GetPane(),
100 wx.ID_ANY,
101 label=_("Test case files"))
102 self._filter_source_resources = wx.CheckBox(self._filter_pane.GetPane(),
103 wx.ID_ANY,
104 label=_("Resource files"))
105 self._filter_mode = wx.RadioBox(self._filter_pane.GetPane(),
106 label=_("Mode"),
107 choices=[_("exclude"), _("include")])
108 self._filter_test_button = wx.Button(self._filter_pane.GetPane(),
109 wx.ID_INFO, label=_("Test the filter"))
110 self._filter_test_button.SetBackgroundColour(Colour(self.color_secondary_background))
111 self._filter_test_button.SetForegroundColour(Colour(self.color_secondary_foreground))
112 filter_box_sizer = wx.BoxSizer(wx.HORIZONTAL)
113 filter_box_sizer.SetSizeHints(self._filter_pane.GetPane())
114 filter_source_sizer = wx.StaticBoxSizer(filter_source_box, wx.VERTICAL)
115 checkbox_border = 0 if IS_MAC else 3
116 filter_source_sizer.Add(self._filter_source_testcases, 0, wx.ALL, checkbox_border)
117 filter_source_sizer.Add(self._filter_source_resources, 0, wx.ALL, checkbox_border)
118 filter_options = wx.BoxSizer(wx.VERTICAL)
119 filter_options.Add(filter_source_sizer, 0,
120 wx.BOTTOM | wx.RIGHT | wx.LEFT | wx.EXPAND, 3)
121 filter_options.Add(self._filter_mode, 0, wx.ALL | wx.EXPAND, 3)
122 filter_input_sizer = wx.BoxSizer(wx.VERTICAL)
123 filter_input_sizer.SetMinSize((600, -1))
124 filter_input_sizer.Add(self._filter_info, 0, wx.ALL | wx.ALIGN_LEFT, 3)
125 filter_input_sizer.Add(self._filter_input, 0, wx.ALL | wx.EXPAND, 3)
126 filter_input_sizer.Add(self._filter_regex_switch, 0, wx.ALL | wx.ALIGN_RIGHT, 3)
127 filter_input_sizer.Add(self._filter_test_button, 0, wx.ALL | wx.ALIGN_CENTER, 3)
128 filter_box_sizer.Add(filter_options, 0, wx.ALL | wx.EXPAND, 3)
129 filter_box_sizer.Add(filter_input_sizer, 0, wx.ALL | wx.EXPAND, 3)
130 self._filter_pane.GetPane().SetSizer(filter_box_sizer)
131 self.Sizer.Add(self._filter_pane, 0, wx.ALL | wx.EXPAND, 3)
133 def _build_unused_keywords(self): 1ab
134 panel_unused_kw = wx.Panel(self._notebook)
135 sizer_unused_kw = wx.BoxSizer(wx.VERTICAL)
136 panel_unused_kw.SetSizer(sizer_unused_kw)
137 self._unused_kw_list = ResultListCtrl(panel_unused_kw,
138 style=wx.LC_REPORT)
139 self._unused_kw_list.InsertColumn(0, _("Keyword"), width=400)
140 self._unused_kw_list.InsertColumn(1, _("File"), width=250)
141 self._unused_kw_list.SetMinSize((650, 250))
142 self._unused_kw_list.set_dialog(self)
143 self._unused_kw_list.SetBackgroundColour(Colour(self.color_background))
144 self._unused_kw_list.SetForegroundColour(Colour(self.color_foreground))
145 self._delete_button = wx.Button(panel_unused_kw, wx.ID_ANY,
146 _('Delete marked keywords'))
147 self._delete_button.SetBackgroundColour(Colour(self.color_secondary_background))
148 self._delete_button.SetForegroundColour(Colour(self.color_secondary_foreground))
149 sizer_unused_kw.Add(self._unused_kw_list, 1, wx.ALL | wx.EXPAND, 3)
150 unused_kw_controls = wx.BoxSizer(wx.HORIZONTAL)
151 unused_kw_controls.AddStretchSpacer(1)
152 if wx.VERSION < (4, 1, 0):
153 unused_kw_controls.Add(self._delete_button, 0, wx.ALL | wx.ALIGN_RIGHT, 3)
154 else:
155 unused_kw_controls.Add(self._delete_button, 0, wx.ALL, 3)
156 sizer_unused_kw.Add(unused_kw_controls, 0, wx.ALL | wx.EXPAND, 3)
157 self._notebook.AddPage(panel_unused_kw, _("Unused Keywords"))
159 def _build_controls(self): 1ab
160 self._search_button = ButtonWithHandler(self, _('Search'), handler=self.on_search)
161 self._search_button.SetBackgroundColour(Colour(self.color_secondary_background))
162 self._search_button.SetForegroundColour(Colour(self.color_secondary_foreground))
163 self._abort_button = ButtonWithHandler(self, _('Abort'), handler=self.on_abort)
164 self._abort_button.SetBackgroundColour(Colour(self.color_secondary_background))
165 self._abort_button.SetForegroundColour(Colour(self.color_secondary_foreground))
166 self._status_label = Label(self, label='')
167 controls = wx.BoxSizer(wx.HORIZONTAL)
168 controls.Add(self._search_button, 0, wx.ALL, 3)
169 controls.Add(self._abort_button, 0, wx.ALL, 3)
170 controls.Add(self._status_label, 1, wx.ALL | wx.EXPAND, 3)
171 self.Sizer.Add(controls, 0, wx.ALL | wx.EXPAND, 3)
173 def _build_notebook(self): 1ab
174 self._notebook = wx.Notebook(self, wx.ID_ANY, style=wx.NB_TOP)
175 self._notebook.SetBackgroundColour(Colour(self.color_background))
176 self._notebook.SetForegroundColour(Colour(self.color_foreground))
177 self.Sizer.Add(self._notebook, 1, wx.ALL | wx.EXPAND, 3)
179 def _make_bindings(self): 1ab
180 self.Bind(wx.EVT_CLOSE, self._close_dialog)
181 self.Bind(wx.EVT_TEXT, self._update_filter, self._filter_input)
182 self.Bind(wx.EVT_RADIOBOX, self._update_filter_mode, self._filter_mode)
183 self.Bind(wx.EVT_CHECKBOX, self._update_filter_source_testcases,
184 self._filter_source_testcases)
185 self.Bind(wx.EVT_CHECKBOX, self._update_filter_source_resources,
186 self._filter_source_resources)
187 self.Bind(wx.EVT_BUTTON, self.on_delete_marked_keywords, self._delete_button)
188 self.Bind(wx.EVT_BUTTON, self.on_show_files_to_be_searched, self._filter_test_button)
189 self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.on_result_selected, self._unused_kw_list)
190 self.Bind(wx.EVT_CHECKBOX, self._update_filter_regex, self._filter_regex_switch)
191 self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self._toggle_filter_active, self._filter_pane)
192 self.Bind(wx.EVT_LIST_ITEM_CHECKED, self._unused_kw_list.OnCheckItem, self._unused_kw_list)
193 self.Bind(wx.EVT_LIST_ITEM_UNCHECKED, self._unused_kw_list.OnCheckItem, self._unused_kw_list)
195 def _set_default_values(self): 1ab
196 check_testcases = True
197 self._filter_source_testcases.SetValue(check_testcases)
198 self._runner.set_filter_source_testcases(check_testcases)
199 check_resources = True
200 self._filter_source_resources.SetValue(check_resources)
201 self._runner.set_filter_source_resources(check_resources)
202 filter_mode = 0
203 self._filter_mode.SetSelection(filter_mode)
204 self._runner.set_filter_mode(filter_mode == 0)
205 use_regex = False
206 self._filter_regex_switch.SetValue(use_regex)
207 self._runner.set_filter_use_regex(use_regex)
208 filter_string = ''
209 self._filter_input.ChangeValue(filter_string)
210 self._runner.parse_filter_string(filter_string)
211 self._disable_filter()
212 self._abort_button.Disable()
213 self._delete_button.Disable()
215 def _update_filter(self, event): 1ab
216 self._runner.parse_filter_string(event.GetString())
218 def _update_filter_mode(self, event): 1ab
219 self._runner.set_filter_mode(event.GetInt() == 0)
221 def _update_filter_source_testcases(self, event): 1ab
222 __ = event
223 self._runner.set_filter_source_testcases(self._filter_source_testcases.IsChecked())
225 def _update_filter_source_resources(self, event): 1ab
226 __ = event
227 self._runner.set_filter_source_resources(self._filter_source_resources.IsChecked())
229 def _update_filter_regex(self, event): 1ab
230 __ = event
231 self._runner.set_filter_use_regex(self._filter_regex_switch.IsChecked())
233 def _toggle_filter_active(self, event): 1ab
234 if event.GetCollapsed():
235 self._disable_filter()
236 else:
237 self._enable_filter()
238 self._filter_pane.on_change(event)
240 def _disable_filter(self): 1ab
241 self._runner.set_filter_active(False)
242 self.label_filter_status.SetLabel(_('inactive'))
243 self.label_filter_status.SetForegroundColour(wx.RED)
245 def _enable_filter(self): 1ab
246 self._runner.set_filter_active(True)
247 self.label_filter_status.SetLabel(_('active'))
248 self.label_filter_status.SetForegroundColour((0, 200, 0))
250 def on_search(self, event): 1ab
251 __ = event
252 self.begin_searching()
253 self._runner.run_review()
255 def on_abort(self, event): 1ab
256 __ = event
257 self.end_searching()
259 def on_delete_marked_keywords(self, event): 1ab
260 __ = event
261 item = self._unused_kw_list.get_next_checked_item()
262 while item:
263 index = item[0]
264 kw = item[1]
265 listitem = item[2]
266 item_id = listitem.GetData()
267 self._unused_kw_list.DeleteItem(index)
268 self._unused_kw_list.RemoveClientData(item_id)
269 kw.delete()
270 self._update_notebook_text(_("Unused Keywords (%d)") % self._unused_kw_list.GetItemCount())
271 self.update_status("")
272 item = self._unused_kw_list.get_next_checked_item()
273 self.item_in_kw_list_checked()
275 def on_show_files_to_be_searched(self, event): 1ab
276 __ = event
277 df_list = self._runner.get_datafile_list()
278 if not df_list:
279 string_list = _("(None)")
280 else:
281 string_list = "\n".join(df.name for df in df_list)
282 message = _("Keywords of the following files will be included in the search:\n\n")+string_list
283 dlg = RIDEDialog(parent=self, title=_("Included files"), message=message, style=wx.OK | wx.ICON_INFORMATION)
284 dlg.ShowModal()
286 def on_result_selected(self, event): 1ab
287 self.frame.tree.select_node_by_data(self._unused_kw_list.GetClientData(event.GetData()))
289 def item_in_kw_list_checked(self): 1ab
290 if self._unused_kw_list.get_number_of_checked_items() > 0:
291 self._delete_button.Enable(True)
292 else:
293 self._delete_button.Disable()
295 def show_dialog(self): 1ab
296 if not self.IsShown():
297 self._clear_search_results()
298 self.Show()
299 self.Raise()
301 def _close_dialog(self, event): 1ab
302 if self._search_model.searching:
303 self.end_searching()
304 if event.CanVeto():
305 self.Hide()
306 else:
307 self.Destroy()
309 def begin_searching(self): 1ab
310 self._abort_button.Enable()
311 self._search_button.Disable()
312 self._filter_pane.Disable()
313 self._unused_kw_list.Disable()
314 self._clear_search_results()
315 self._dots = DottedSearch(self, self._update_unused_keywords)
316 self._dots.start()
318 def _clear_search_results(self): 1ab
319 self._unused_kw_list.ClearAll()
320 self._update_notebook_text(_('Unused Keywords'))
321 self._delete_button.Disable()
322 self._status_label.SetLabel('')
323 self._search_model.clear_search()
325 def add_result_unused_keyword(self, index, keyword): 1ab
326 keyword_info = keyword.info
327 self._unused_kw_list.InsertItem(index, keyword_info.name)
328 filename = os.path.basename(keyword_info.item.source)
329 self._unused_kw_list.SetItem(index, 1, filename)
330 self._unused_kw_list.SetItemData(index, index)
331 self._unused_kw_list.SetClientData(index, keyword)
332 self._unused_kw_list.SetItemBackgroundColour(index, Colour(self.color_secondary_background))
333 self._unused_kw_list.SetItemTextColour(index, Colour(self.color_secondary_foreground))
335 def _update_unused_keywords(self, dots): 1ab
336 count_before = self._unused_kw_list.GetItemCount()
337 for index, kw in list(enumerate(self._search_model.keywords))[count_before:]:
338 self.add_result_unused_keyword(index, kw)
339 self.update_status(_("Searching.%s \t- %s") % (dots, self._search_model.status))
340 if not self._search_model.searching:
341 self.end_searching()
343 def _update_notebook_text(self, new_text): 1ab
344 self._notebook.SetPageText(0, new_text)
346 def update_status(self, message, increase=1): 1ab
347 _ = increase
348 self._status_label.SetLabel(message)
350 def end_searching(self): 1ab
351 self._dots.stop()
352 self._search_model.end_search()
353 self._update_notebook_text(_('Unused Keywords (%d)') % (self._unused_kw_list.GetItemCount()))
354 self.update_status(_("Search finished - Found %d Unused Keywords") % (self._unused_kw_list.GetItemCount()))
355 self._unused_kw_list.Enable()
356 self._abort_button.Disable()
357 self._filter_pane.Enable()
358 self._search_button.Enable()
360 @staticmethod 1ab
361 def send_radiobox_event(mycontrol): 1ab
362 cmd = wx.CommandEvent(wx.EVT_RADIOBOX.evtType[0])
363 cmd.SetEventObject(mycontrol)
364 cmd.SetId(mycontrol.GetId())
365 mycontrol.GetEventHandler().ProcessEvent(cmd)
368class ReviewRunner(object): 1ab
370 def __init__(self, controller, model): 1ab
371 self._controller = controller
372 self._model = model
373 self._filter = ResultFilter()
375 def set_filter_active(self, value): 1ab
376 self._filter.active = value 1c
378 def set_filter_mode(self, exclude): 1ab
379 self._filter.excludes = exclude 1c
381 def set_filter_source_testcases(self, value): 1ab
382 self._filter.check_testcases = value 1c
384 def set_filter_source_resources(self, value): 1ab
385 self._filter.check_resources = value 1c
387 def set_filter_use_regex(self, value): 1ab
388 self._filter.use_regex = value 1c
390 def parse_filter_string(self, filter_string): 1ab
391 self._filter.set_strings(filter_string.split(',')) 1c
393 def get_datafile_list(self): 1ab
394 return [df for df in self._controller.datafiles if self._filter.include_file(df)] 1c
396 def run_review(self): 1ab
397 self._model.begin_search()
398 Thread(target=self._run).start()
400 def _run(self): 1ab
401 self._stop_requested = False
402 self._model.status = _('listing datafiles')
403 for df in self.get_datafile_list():
404 libname = os.path.basename(df.source).rsplit('.', 1)[0]
405 self._model.status = _('searching from ') + str(libname)
406 for keyword in df.keywords:
407 time.sleep(0) # GIVE SPACE TO OTHER THREADS -- Thread.yield in Java
408 self._model.status = "%s.%s" % (libname, keyword.name)
409 if not self._model.searching:
410 break
411 # Check if it is unused
412 if not isinstance(keyword, LibraryKeywordInfo) and keyword.name and self._is_unused(keyword):
413 self._model.add_unused_keyword(keyword)
414 if not self._model.searching:
415 break
416 self._model.end_search()
418 def _is_unused(self, keyword): 1ab
419 try:
420 next(self._controller.execute(FindUsages(keyword.name, keyword_info=keyword.info)))
421 return False
422 except StopIteration:
423 return True
426class ResultFilter(object): 1ab
428 def __init__(self): 1ab
429 self._strings = []
430 self.excludes = True
431 self.check_testcases = True
432 self.check_resources = True
433 self.use_regex = False
434 self.active = False
436 def set_strings(self, strings): 1ab
437 self._strings = [s.strip() for s in strings if s.strip()] 1c
439 def include_file(self, datafile): 1ab
440 from ..controller.filecontrollers import (TestCaseFileController, ResourceFileController, 1c
441 TestDataDirectoryController)
442 if isinstance(datafile, TestDataDirectoryController): 1c
443 return False 1c
444 if not self.active: 1c
445 return True 1c
446 if not self.check_testcases and isinstance(datafile, TestCaseFileController): 1c
447 return False 1c
448 if not self.check_resources and isinstance(datafile, ResourceFileController): 1c
449 return False 1c
450 if not self._strings: 1c
451 return True 1c
452 return self.excludes ^ any(self._results(datafile.name)) 1c
454 def _results(self, name): 1ab
455 for string in self._strings: 1c
456 if self.use_regex: 1c
457 yield bool(re.match(string, name)) 1c
458 else:
459 yield string in name 1c
462class ResultModel(object): 1ab
464 def __init__(self): 1ab
465 self.status = self.keywords = self.searching = None
466 self.clear_search()
468 def clear_search(self): 1ab
469 self.status = ''
470 self.keywords = []
471 self.searching = False
473 def add_unused_keyword(self, keyword): 1ab
474 self.keywords += [keyword]
476 def begin_search(self): 1ab
477 self.searching = True
479 def end_search(self): 1ab
480 self.searching = False
483class ResultListCtrl(wx.ListCtrl, listmix.CheckListCtrlMixin, listmix.ListCtrlAutoWidthMixin): 1ab
484 def __init__(self, parent, style): 1ab
485 self.parent = parent
486 wx.ListCtrl.__init__(self, parent=parent, style=style)
487 if wx.VERSION < (4, 1, 0):
488 listmix.CheckListCtrlMixin.__init__(self)
489 else:
490 self.EnableCheckBoxes(True)
491 listmix.ListCtrlAutoWidthMixin.__init__(self)
492 self.SetBackgroundColour(Colour(self.GetTopLevelParent().color_background))
493 self.SetForegroundColour(Colour(self.GetTopLevelParent().color_foreground))
494 self.setResizeColumn(2)
495 self._clientData = {}
496 self._dlg = None
498 def set_dialog(self, dialog): 1ab
499 self._dlg = dialog
501 def OnCheckItem(self, event): # Overrides wx method 1ab
502 if self._dlg:
503 self._dlg.item_in_kw_list_checked()
504 else:
505 print("No dialog set")
507 def get_next_checked_item(self): 1ab
508 for i in range(self.GetItemCount()):
509 if wx.VERSION >= (4, 1, 0):
510 checked = self.IsItemChecked(i)
511 else:
512 checked = self.IsChecked(i)
513 if checked:
514 item = self.GetItem(i)
515 return [i, self.GetClientData(item.GetData()), item]
516 return None
518 def get_number_of_checked_items(self): 1ab
519 ssum = 0
520 for i in range(self.GetItemCount()):
521 if wx.VERSION >= (4, 1, 0):
522 checked = self.IsItemChecked(i)
523 else:
524 checked = self.IsChecked(i)
525 if checked:
526 ssum += 1
527 return ssum
529 def SetClientData(self, index, data): 1ab
530 self._clientData[index] = data
532 def GetClientData(self, index): 1ab
533 return self._clientData.get(index, None)
535 def RemoveClientData(self, index): 1ab
536 del self._clientData[index]
538 def ClearAll(self): 1ab
539 self.DeleteAllItems()
540 self._clientData.clear()
542 def print_data(self): 1ab
543 print(self._clientData)
546class MyCollapsiblePane(wx.CollapsiblePane): 1ab
548 def __init__(self, parent, *args, **kwargs): 1ab
549 wx.CollapsiblePane.__init__(self, parent, *args, **kwargs)
550 """
551 self.SetBackgroundColour(Colour(200, 222, 40))
552 self.SetOwnBackgroundColour(Colour(200, 222, 40))
553 self.SetForegroundColour(Colour(7, 0, 70))
554 self.SetOwnForegroundColour(Colour(7, 0, 70))
555 """
556 self.Bind(wx.EVT_SIZE, self._recalc_size)
557 self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.on_change)
559 def _recalc_size(self, event=None): 1ab
560 if self.IsExpanded():
561 expand_button_height = 32 # good guess...
562 height = 150 if IS_MAC else 135
563 self.SetSizeHints(650, height + expand_button_height)
564 if self.IsCollapsed():
565 self.SetSizeHints(650, 40)
566 if event:
567 event.Skip()
569 def on_change(self, event): 1ab
570 self.Fit()
571 self.GetParent().Layout()
572 if event:
573 event.Skip()