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

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. 

15 

16import builtins 1ab

17import os 1ab

18import re 1ab

19import time 1ab

20from threading import Thread 1ab

21 

22import wx 1ab

23import wx.lib.mixins.listctrl as listmix 1ab

24from wx import Colour 1ab

25 

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

31 

32_ = wx.GetTranslation # To keep linter/code analyser happy 1ab

33builtins.__dict__['_'] = wx.GetTranslation 1ab

34 

35 

36class ReviewDialog(RIDEDialog): 1ab

37 

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() 

53 

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() 

64 

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) 

82 

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) 

132 

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")) 

158 

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) 

172 

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) 

178 

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) 

194 

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() 

214 

215 def _update_filter(self, event): 1ab

216 self._runner.parse_filter_string(event.GetString()) 

217 

218 def _update_filter_mode(self, event): 1ab

219 self._runner.set_filter_mode(event.GetInt() == 0) 

220 

221 def _update_filter_source_testcases(self, event): 1ab

222 __ = event 

223 self._runner.set_filter_source_testcases(self._filter_source_testcases.IsChecked()) 

224 

225 def _update_filter_source_resources(self, event): 1ab

226 __ = event 

227 self._runner.set_filter_source_resources(self._filter_source_resources.IsChecked()) 

228 

229 def _update_filter_regex(self, event): 1ab

230 __ = event 

231 self._runner.set_filter_use_regex(self._filter_regex_switch.IsChecked()) 

232 

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) 

239 

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) 

244 

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)) 

249 

250 def on_search(self, event): 1ab

251 __ = event 

252 self.begin_searching() 

253 self._runner.run_review() 

254 

255 def on_abort(self, event): 1ab

256 __ = event 

257 self.end_searching() 

258 

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() 

274 

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() 

285 

286 def on_result_selected(self, event): 1ab

287 self.frame.tree.select_node_by_data(self._unused_kw_list.GetClientData(event.GetData())) 

288 

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() 

294 

295 def show_dialog(self): 1ab

296 if not self.IsShown(): 

297 self._clear_search_results() 

298 self.Show() 

299 self.Raise() 

300 

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() 

308 

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() 

317 

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() 

324 

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)) 

334 

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() 

342 

343 def _update_notebook_text(self, new_text): 1ab

344 self._notebook.SetPageText(0, new_text) 

345 

346 def update_status(self, message, increase=1): 1ab

347 _ = increase 

348 self._status_label.SetLabel(message) 

349 

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() 

359 

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) 

366 

367 

368class ReviewRunner(object): 1ab

369 

370 def __init__(self, controller, model): 1ab

371 self._controller = controller 

372 self._model = model 

373 self._filter = ResultFilter() 

374 

375 def set_filter_active(self, value): 1ab

376 self._filter.active = value 1c

377 

378 def set_filter_mode(self, exclude): 1ab

379 self._filter.excludes = exclude 1c

380 

381 def set_filter_source_testcases(self, value): 1ab

382 self._filter.check_testcases = value 1c

383 

384 def set_filter_source_resources(self, value): 1ab

385 self._filter.check_resources = value 1c

386 

387 def set_filter_use_regex(self, value): 1ab

388 self._filter.use_regex = value 1c

389 

390 def parse_filter_string(self, filter_string): 1ab

391 self._filter.set_strings(filter_string.split(',')) 1c

392 

393 def get_datafile_list(self): 1ab

394 return [df for df in self._controller.datafiles if self._filter.include_file(df)] 1c

395 

396 def run_review(self): 1ab

397 self._model.begin_search() 

398 Thread(target=self._run).start() 

399 

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() 

417 

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 

424 

425 

426class ResultFilter(object): 1ab

427 

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 

435 

436 def set_strings(self, strings): 1ab

437 self._strings = [s.strip() for s in strings if s.strip()] 1c

438 

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

453 

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

460 

461 

462class ResultModel(object): 1ab

463 

464 def __init__(self): 1ab

465 self.status = self.keywords = self.searching = None 

466 self.clear_search() 

467 

468 def clear_search(self): 1ab

469 self.status = '' 

470 self.keywords = [] 

471 self.searching = False 

472 

473 def add_unused_keyword(self, keyword): 1ab

474 self.keywords += [keyword] 

475 

476 def begin_search(self): 1ab

477 self.searching = True 

478 

479 def end_search(self): 1ab

480 self.searching = False 

481 

482 

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 

497 

498 def set_dialog(self, dialog): 1ab

499 self._dlg = dialog 

500 

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") 

506 

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 

517 

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 

528 

529 def SetClientData(self, index, data): 1ab

530 self._clientData[index] = data 

531 

532 def GetClientData(self, index): 1ab

533 return self._clientData.get(index, None) 

534 

535 def RemoveClientData(self, index): 1ab

536 del self._clientData[index] 

537 

538 def ClearAll(self): 1ab

539 self.DeleteAllItems() 

540 self._clientData.clear() 

541 

542 def print_data(self): 1ab

543 print(self._clientData) 

544 

545 

546class MyCollapsiblePane(wx.CollapsiblePane): 1ab

547 

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) 

558 

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() 

568 

569 def on_change(self, event): 1ab

570 self.Fit() 

571 self.GetParent().Layout() 

572 if event: 

573 event.Skip()