Coverage for src/robotide/ui/treeplugin.py: 51%

899 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

18 

19import wx 1ab

20from wx import Colour, Point 1ab

21from wx.lib.agw import customtreectrl 1ab

22from wx.lib.agw.aui import GetManager 1ab

23from wx.lib.agw.customtreectrl import GenericTreeItem 1ab

24from wx.lib.mixins import treemixin 1ab

25 

26from ..context import IS_WINDOWS 1ab

27from ..publish.messages import (RideTestRunning, RideTestPaused, RideTestPassed, RideTestFailed, RideTestSkipped, 1ab

28 RideTestExecutionStarted, RideTestStopped, RideImportSetting, RideExcludesChanged, 

29 RideIncludesChanged, RideOpenSuite, RideNewProject) 

30from ..ui.images import (RUNNING_IMAGE_INDEX, PASSED_IMAGE_INDEX, FAILED_IMAGE_INDEX, PAUSED_IMAGE_INDEX, 1ab

31 SKIPPED_IMAGE_INDEX, ROBOT_IMAGE_INDEX) 

32from ..ui.treenodehandlers import TestCaseHandler, TestDataDirectoryHandler, TestCaseFileHandler 1ab

33from ..publish import (PUBLISHER, RideTreeSelection, RideFileNameChanged, RideItem, RideUserKeywordAdded, 1ab

34 RideTestCaseAdded, RideUserKeywordRemoved, RideUserKeywordRenamed, RideTestCaseRemoved, 

35 RideDataFileRemoved, RideDataChangedToDirty, RideDataDirtyCleared, RideVariableRemoved, 

36 RideVariableAdded, RideVariableMovedUp, RideVariableMovedDown, RideVariableUpdated, 

37 RideOpenResource, RideSuiteAdded, RideSelectResource, RideDataFileSet, RideItemNameChanged) 

38from ..controller.ctrlcommands import MoveTo 1ab

39from ..pluginapi import Plugin 1ab

40from ..action import ActionInfo 1ab

41from ..widgets import PopupCreator 1ab

42from .. import utils 1ab

43from .treenodehandlers import ResourceRootHandler, action_handler_class, ResourceFileHandler 1ab

44from .images import TreeImageList 1ab

45 

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

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

48 

49 

50TREETEXTCOLOUR = Colour(0xA9, 0xA9, 0xA9) 1ab

51 

52_TREE_ARGS = {'style': wx.HSCROLL | wx.VSCROLL, 1ab

53 'agwStyle': customtreectrl.TR_DEFAULT_STYLE | customtreectrl.TR_HIDE_ROOT | customtreectrl.TR_EDIT_LABELS} 

54_TREE_ARGS['agwStyle'] |= customtreectrl.TR_TOOLTIP_ON_LONG_ITEMS | customtreectrl.TR_HAS_VARIABLE_ROW_HEIGHT 1ab

55 

56if IS_WINDOWS: 56 ↛ 57line 56 didn't jump to line 57 because the condition on line 56 was never true1ab

57 _TREE_ARGS['style'] |= wx.TR_EDIT_LABELS 

58 

59 

60class TreePlugin(Plugin): 1ab

61 __doc__ = _("""Provides a tree view for Test Suites """) 1ab

62 

63 datafile = property(lambda self: self.get_selected_datafile()) 1ab

64 defaults = {"opened": True, 1ab

65 "docked": True, 

66 "own colors": False 

67 } 

68 

69 def __init__(self, application): 1ab

70 Plugin.__init__(self, application, default_settings=self.defaults) 

71 # print("DEBUG: treeplugin.py TreePlugin __init__") 

72 self._app = application 

73 self.settings = self._app.settings.config_obj['Plugins']['Tree'] 

74 self._parent = None 

75 self._tree = self.tree 

76 """ 

77 self._tree.SetBackgroundColour(Colour(200, 222, 40)) 

78 self._tree.SetOwnBackgroundColour(Colour(200, 222, 40)) 

79 self._tree.SetForegroundColour(Colour(7, 0, 70)) 

80 self._tree.SetOwnForegroundColour(Colour(7, 0, 70)) 

81 """ 

82 self._mgr = GetManager(self._tree) 

83 """ 

84 self._action_registerer = action_registerer 

85 self.tree = parent.tree 

86 """ 

87 self.pane_id = self._tree.GetId() 

88 self._model = self.model 

89 # DEBUG: self.frame.Bind(wx.EVT_MOVE, self.OnShowTree) self.frame.Bind(wx.EVT_SHOW, self.OnShowTree) 

90 self._tree.Bind(wx.EVT_SHOW, self.on_show_tree) 

91 self._tree.Bind(wx.EVT_MOVE, self.on_tab_changed) 

92 # parent, action_registerer, , default_settings={'collapsed':True} 

93 self._pane = self._mgr.GetPane(self._tree) 

94 self.font = wx.Font(self._app.fontinfo) # self._tree.GetFont() 

95 self._tree.SetFont(self.font) 

96 # print(f"DEBUG: TreePlugin init self.pane_id={self.pane_id} \n" 

97 # f"self._pane = {self._pane}") 

98 

99 def register_frame(self, parent=None): 1ab

100 if parent: 100 ↛ exitline 100 didn't return from function 'register_frame' because the condition on line 100 was always true

101 self._parent = parent 

102 if self._mgr.GetPane("tree_content") in self._mgr._panes: 102 ↛ 105line 102 didn't jump to line 105 because the condition on line 102 was always true

103 register = self._mgr.InsertPane 

104 else: 

105 register = self._mgr.AddPane 

106 register(self._tree, wx.lib.agw.aui.AuiPaneInfo().Name("tree_content"). 

107 Caption(_("Test Suites")).LeftDockable(True)) 

108 self._mgr.Update() 

109 

110 def enable(self): 1ab

111 # DEBUG this does not work, the panel has no tree, when we dock from floating 

112 # DEBUG: Attempt to create Panel and then calling self.OnShowTree 

113 """ 

114 self.register_action(ActionInfo('View','View Test Suites Explorer', self.OnShowTree, 

115 shortcut='F12', 

116 doc='Show Test Suites tree panel', 

117 position=1)) 

118 """ 

119 self.subscribe(self.on_tree_selection, RideTreeSelection) 

120 # self.save_setting('opened', True) 

121 # DEBUG: Add toggle checkbox to menu View/Hide Tree 

122 if self.opened: 122 ↛ exitline 122 didn't return from function 'enable' because the condition on line 122 was always true

123 self.on_show_tree(None) 

124 

125 def close_tree(self): 1ab

126 self._mgr.DetachPane(self._tree) 

127 self._tree.Hide() 

128 self._mgr.Update() 

129 self.save_setting('opened', False) 

130 

131 def disable(self): 1ab

132 self.close_tree() 

133 # self.save_setting('opened', False) 

134 self.unsubscribe_all() 

135 self.unregister_actions() 

136 

137 def is_focused(self): 1ab

138 return self._tree.HasFocus() 

139 

140 def populate(self, model): 1ab

141 if model: # DEBUG: Always populate ... and model != self._model: 141 ↛ 144line 141 didn't jump to line 144 because the condition on line 141 was always true

142 self._model = model 

143 # print(f"DEBUG: Populating model... {self._model}\n\n") 

144 self._tree.populate(self._model) 

145 

146 def set_editor(self, editor): 1ab

147 self._tree.set_editor(editor) 

148 

149 def on_show_tree(self, event): 1ab

150 __ = event 

151 if not self._parent: 

152 self._parent = self.frame 

153 if not self._tree: # This is not needed because tree is always created 153 ↛ 154line 153 didn't jump to line 154 because the condition on line 153 was never true

154 return # On Windows this code is executed when closing the app, we return now 

155 

156 global_settings = self._app.settings.config_obj['General'] 

157 apply_global = global_settings['apply to panels'] 

158 use_own = self.settings['own colors'] 

159 if apply_global or not use_own: 159 ↛ 163line 159 didn't jump to line 163 because the condition on line 159 was always true

160 html_background = self.settings.get('background help', (240, 242, 80)) 

161 html_foreground = self.settings.get('foreground text', (7, 0, 70)) 

162 else: 

163 html_background = self.settings.get('background', (240, 242, 80)) 

164 html_foreground = self.settings.get('foreground', (7, 0, 70)) 

165 html_font_face = self.settings.get('font face', '') 

166 html_font_size = self.settings.get('font size', 11) 

167 self._tree.Show(True) 

168 # print(f"DEBUG: treeplugin on_show_tree {html_font_face=} {html_font_size=}") 

169 self._tree.SetMinSize(wx.Size(200, 225)) 

170 # self.aui_mgr.DetachPane(self._tree) 

171 # self.aui_mgr.Update() 

172 # DEBUG: Let's use own method 

173 # self.aui_mgr.AddPane(self._tree, 

174 # wx.lib.agw.aui.AuiPaneInfo().Name("tree_content"). 

175 # Caption("Test Suites").LeftDockable(True). 

176 # CloseButton(True)) 

177 # self.register_frame(self._parent) 

178 self._tree.SetBackgroundStyle(wx.BG_STYLE_SYSTEM) 

179 self._tree.SetBackgroundColour(html_background) 

180 self._tree.SetForegroundColour(html_foreground) 

181 # self.font = self._tree.GetFont() 

182 self._tree.SetFont(self.font) 

183 if html_font_face is not None: 183 ↛ 187line 183 didn't jump to line 187 because the condition on line 183 was always true

184 self.font.SetFaceName(html_font_face) 

185 self.font.SetPointSize(html_font_size) 

186 self._tree.SetFont(self.font) 

187 self._tree.Refresh() 

188 self._tree.Raise() 

189 self.save_setting('opened', True) 

190 self._tree.populate(self._model) 

191 self._update_tree() 

192 self._mgr.Update() 

193 

194 def on_tree_selection(self, message): 1ab

195 if self.is_focused(): 

196 self._tree.tree_node_selected(message.item) 

197 

198 def on_tab_changed(self, event): 1ab

199 __ = event 

200 self._update_tree() 

201 

202 def _update_tree(self, event=None): 1ab

203 __ = event 

204 self._tree.populate(self._model) 

205 self._tree.refresh_view() 

206 self._tree.Update() 

207 

208 

209class Tree(treemixin.DragAndDrop, customtreectrl.CustomTreeCtrl, wx.Panel): 1ab

210 

211 def __init__(self, parent, action_registerer, settings=None): 1ab

212 from ..controller.ui.treecontroller import TreeController 

213 self._RESOURCES_NODE_LABEL = _('External Resources') 

214 # print(f"DEBUG: treeplugin.py Tree after importing TreeController __init__ " 

215 # f"translated label={self._RESOURCES_NODE_LABEL}") 

216 self.theme = settings.get_without_default('General') 

217 self.background = self.theme['background'] 

218 self.foreground = self.theme['foreground'] 

219 self._checkboxes_for_tests = False 

220 self._test_selection_controller = self._create_test_selection_controller() 

221 self.controller = TreeController(self, action_registerer, settings=settings, 

222 test_selection=self._test_selection_controller) 

223 treemixin.DragAndDrop.__init__(self, parent, **_TREE_ARGS) 

224 self.controller.register_tree_actions() 

225 self._bind_tree_events() 

226 self._images = TreeImageList() 

227 self._animctrl = None 

228 self._silent_mode = False 

229 self.SetImageList(self._images) 

230 self.label_editor = TreeLabelEditListener(self, action_registerer) 

231 self.controller.bind_keys() 

232 self._subscribe_to_messages() 

233 self._popup_creator = PopupCreator() 

234 self._dragging = False 

235 self._clear_tree_data() 

236 self._editor = None 

237 self._execution_results = None 

238 self._resources = [] 

239 self._right_click = False 

240 self.dirty = False 

241 # DEBUG: This menu is not working because is being attached to main frame 

242 # self._menu = wx.Menu() 

243 # self._menu.Append(wx.ID_CLOSE, item="&Close", helpString="Closes panel") 

244 # self._mb = wx.MenuBar() 

245 # self._mb.Append(self._menu, "Menu") 

246 # self.GetParent().SetMenuBar(self._mb) 

247 # print(f"DEBUG: Tree tried to add menu to {self.GetParent().__repr__()} parent={parent}") 

248 self.pane_id = self.GetId() 

249 if not hasattr(self, 'OnCancelEdit'): 249 ↛ 250line 249 didn't jump to line 250 because the condition on line 249 was never true

250 self.OnCancelEdit = self._on_cancel_edit 

251 

252 @staticmethod 1ab

253 def _create_test_selection_controller(): 1ab

254 from ..controller.ui.treecontroller import TestSelectionController 

255 

256 tsc = TestSelectionController() 

257 PUBLISHER.subscribe(tsc.clear_all, RideOpenSuite) 

258 PUBLISHER.subscribe(tsc.clear_all, RideNewProject) 

259 return tsc 

260 

261 def _on_cancel_edit(self, item): 1ab

262 le = customtreectrl.TreeEvent( 

263 customtreectrl.wxEVT_TREE_END_LABEL_EDIT, self.GetId()) 

264 le._item = item 

265 le.SetEventObject(self) 

266 le._label = "" 

267 le._editCancelled = True 

268 self.GetEventHandler().ProcessEvent(le) 

269 

270 def _bind_tree_events(self): 1ab

271 self.Bind(wx.EVT_LEFT_DCLICK, self.on_double_click) 

272 self.Bind(wx.EVT_TREE_SEL_CHANGED, self.on_sel_changed) 

273 self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.on_tree_item_expanding) 

274 self.Bind(wx.EVT_RIGHT_DOWN, self.on_right_click) 

275 self.Bind(wx.EVT_TREE_SEL_CHANGING, self.on_selection) 

276 # self.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.OnRightClick) 

277 self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.on_item_activated) 

278 self.Bind(customtreectrl.EVT_TREE_ITEM_CHECKED, self.on_tree_item_checked) 

279 self.Bind(wx.EVT_TREE_ITEM_COLLAPSING, self.on_tree_item_collapsing) 

280 self.Bind(wx.EVT_CLOSE, self.on_close) 

281 

282 def on_selection(self, event): 1ab

283 if self._right_click: 283 ↛ 284line 283 didn't jump to line 284 because the condition on line 283 was never true1ahijklmgnopqrcdus

284 event.Skip() 

285 

286 def on_double_click(self, event): 1ab

287 item, _ = self.HitTest(self.ScreenToClient(wx.GetMousePosition())) 

288 if item: 

289 handler = self.controller.get_handler(item) 

290 handler.double_clicked() 

291 event.Skip() 

292 

293 def set_editor(self, editor): 1ab

294 self._editor = editor 

295 

296 def StartDragging(self): # Overrides wx method 1ab

297 self._dragging = True 

298 treemixin.DragAndDrop.StartDragging(self) 

299 

300 def OnEndDrag(self, event): # Overrides wx method 1ab

301 self._dragging = False 

302 treemixin.DragAndDrop.OnEndDrag(self, event) 

303 

304 def register_context_menu_hook(self, callable_m): 1ab

305 self._popup_creator.add_hook(callable_m) 

306 

307 def unregister_context_menu_hook(self, callable_m): 1ab

308 self._popup_creator.remove_hook(callable_m) 

309 

310 def _subscribe_to_messages(self): 1ab

311 subscriptions = [ 

312 (self._item_renamed, RideItemNameChanged), 

313 (self._item_changed, RideItem), 

314 (self._resource_added, RideOpenResource), 

315 (self._select_resource, RideSelectResource), 

316 (self._suite_added, RideSuiteAdded), 

317 (self._keyword_added, RideUserKeywordAdded), 

318 (self._test_added, RideTestCaseAdded), 

319 (self._variable_added, RideVariableAdded), 

320 (self._leaf_item_removed, RideUserKeywordRemoved), 

321 (self._leaf_item_removed, RideTestCaseRemoved), 

322 (self._leaf_item_removed, RideVariableRemoved), 

323 (self._datafile_removed, RideDataFileRemoved), 

324 (self._datafile_set, RideDataFileSet), 

325 (self._data_dirty, RideDataChangedToDirty), 

326 (self._data_undirty, RideDataDirtyCleared), 

327 (self._variable_moved_up, RideVariableMovedUp), 

328 (self._variable_moved_down, RideVariableMovedDown), 

329 (self._variable_updated, RideVariableUpdated), 

330 (self._filename_changed, RideFileNameChanged), 

331 (self._testing_started, RideTestExecutionStarted), 

332 (self._test_result, RideTestRunning), 

333 (self._test_result, RideTestPaused), 

334 (self._test_result, RideTestPassed), 

335 (self._test_result, RideTestFailed), 

336 (self._test_result, RideTestSkipped), 

337 (self._test_result, RideTestStopped), 

338 (self._handle_import_setting_message, RideImportSetting), 

339 (self._mark_excludes, RideExcludesChanged), 

340 (self._mark_excludes, RideIncludesChanged), 

341 ] 

342 for listener, topic in subscriptions: 

343 PUBLISHER.subscribe(listener, topic) 

344 

345 def _mark_excludes(self, message): 1ab

346 tree = self.controller.find_node_by_controller(message.old_controller) 

347 self._render_datafile(self.GetItemParent(tree), message.new_controller) 

348 self._remove_datafile_node(tree) 

349 

350 def _set_item_excluded(self, node): 1ab

351 self.SetItemTextColour(node, wx.TheColourDatabase.Find(colourName="GREY")) 

352 self.SetItemItalic(node, True) 

353 self.SetItemText(node, _("%s (excluded)") % self.GetItemText(node)) 

354 

355 def _handle_import_setting_message(self, message): 1ab

356 if message.is_resource(): 

357 self._set_resource_color( 

358 message.import_controller.get_imported_controller()) 

359 self._set_resource_color( 

360 message.import_controller.get_previous_imported_controller()) 

361 

362 def _set_resource_color(self, resource_controller): 1ab

363 if not resource_controller: 

364 return 

365 node = self.controller.find_node_by_controller(resource_controller) 

366 if node: 

367 self.SetItemTextColour( 

368 node, self._get_resource_text_color(resource_controller)) 

369 

370 def _get_resource_text_color(self, resource_controller): 1ab

371 if resource_controller.is_used(): 

372 return self.GetDefaultAttributes().colFg 

373 else: 

374 return wx.LIGHT_GREY 

375 

376 def _testing_started(self, message): 1ab

377 self._for_all_drawn_tests( 

378 self.root, lambda t: self.SetItemImage(t, ROBOT_IMAGE_INDEX)) 

379 self._execution_results = message.results 

380 self._images.set_execution_results(message.results) 

381 

382 def _test_result(self, message): 1ab

383 from ..controller.macrocontrollers import TestCaseController 

384 

385 test: TestCaseController = message.item 

386 if not test: 

387 # test object will be None when running with DataDriver 

388 # when runner is interrupted, is also None, so let's stop animation 

389 if self._animctrl: 

390 self._animctrl.Stop() 

391 self._animctrl.Animation.Destroy() 

392 self._animctrl.Destroy() 

393 self._animctrl = None 

394 return 

395 if isinstance(message, RideTestPassed): 

396 test.run_passed = True 

397 elif isinstance(message, RideTestFailed): 

398 test.run_passed = False 

399 elif isinstance(message, RideTestSkipped): 

400 test.run_passed = False 

401 else: 

402 test.run_passed = None 

403 wx.CallAfter(self._set_icon_from_execution_results, message.item) 

404 

405 def _set_icon_from_execution_results(self, controller): 1ab

406 node = self.controller.find_node_by_controller(controller) 

407 if not node: 

408 return 

409 img_index = self._get_icon_index_for(controller) 

410 # Always set the static icon 

411 self.SetItemImage(node, img_index) 

412 if self._animctrl: 

413 self._animctrl.Stop() 

414 self._animctrl.Animation.Destroy() 

415 self._animctrl.Destroy() 

416 self._animctrl = None 

417 self.DeleteItemWindow(node) 

418 if img_index in (RUNNING_IMAGE_INDEX, PAUSED_IMAGE_INDEX): 

419 from wx.adv import Animation, AnimationCtrl 

420 import os 

421 _BASE = os.path.join(os.path.dirname(__file__), '..', 'widgets') 

422 if img_index == RUNNING_IMAGE_INDEX: 

423 img = os.path.join(_BASE, 'robot-running.gif') 

424 else: 

425 img = os.path.join(_BASE, 'robot-pause.gif') 

426 ani = Animation(img) 

427 obj = self 

428 rect = Point(node.GetX()+20, node.GetY()) # Overlaps robot icon 

429 try: 

430 self._animctrl = AnimationCtrl(obj, -1, ani, rect) 

431 self._animctrl.SetBackgroundColour('white') 

432 self.SetItemWindow(node, self._animctrl, False) 

433 self._animctrl.Play() 

434 except AttributeError: # In fast executions the element self._animctrl.Play() does not exists 

435 pass 

436 # Make visible the running or paused test 

437 parent = node.GetParent() 

438 self.EnsureVisible(parent) # DEBUG add animation to parent if suite setup/teardown started 

439 self.ExpandAllChildren(parent) 

440 self.EnsureVisible(node) 

441 self.ExpandAllChildren(node) 

442 self.Update() 

443 

444 def _get_icon_index_for(self, controller): 1ab

445 if not self._execution_results: 

446 return ROBOT_IMAGE_INDEX 

447 if self._execution_results.is_paused(controller): 

448 return PAUSED_IMAGE_INDEX 

449 if self._execution_results.is_running(controller): 

450 return RUNNING_IMAGE_INDEX 

451 if self._execution_results.has_passed(controller): 

452 return PASSED_IMAGE_INDEX 

453 if self._execution_results.has_failed(controller): 

454 return FAILED_IMAGE_INDEX 

455 if self._execution_results.has_skipped(controller): 

456 return SKIPPED_IMAGE_INDEX 

457 return ROBOT_IMAGE_INDEX 

458 

459 def populate(self, model): 1ab

460 self._clear_tree_data() 

461 self._populate_model(model) 

462 self.refresh_view() 

463 self.SetFocus() # Needed for keyboard shortcuts 

464 

465 def _clear_tree_data(self): 1ab

466 self.DeleteAllItems() 

467 self.root = self.AddRoot('') 

468 self._resource_root = self._create_resource_root() 

469 self.datafile_nodes = [] 

470 self._resources = [] 

471 self.controller.clear_history() 

472 

473 def _create_resource_root(self): 1ab

474 return self._create_node(self.root, self._RESOURCES_NODE_LABEL, 

475 self._images.directory) 

476 

477 def _populate_model(self, model): 1ab

478 handler = ResourceRootHandler(model, self, self._resource_root, 

479 self.controller.settings) 

480 # print(f"DEBUG: _populate_model model={model} self._resource_root={self._resource_root}" 

481 # f" self._controller.settings={self._controller.settings}") 

482 self.SetPyData(self._resource_root, handler) 

483 if model.data: 

484 self._render_datafile(self.root, model.data, 0) 

485 for res in model.external_resources: 

486 if not res.parent: 486 ↛ 485line 486 didn't jump to line 485 because the condition on line 486 was always true

487 self._render_datafile(self._resource_root, res) 

488 

489 def _resource_added(self, message): 1ab

490 ctrl = message.datafile 

491 if self.controller.find_node_by_controller(ctrl): 

492 return 

493 if ctrl.parent: 

494 parent = self._get_dir_node(ctrl.parent) 

495 else: 

496 parent = self._resource_root 

497 self._render_datafile(parent, ctrl) 

498 

499 def _get_dir_node(self, ctrl): 1ab

500 if ctrl is None: 

501 return self.root 

502 dir_node = self._get_datafile_node(ctrl.data) 

503 if dir_node is None: 503 ↛ 507line 503 didn't jump to line 507 because the condition on line 503 was always true

504 parent = self._get_dir_node(ctrl.parent) 

505 self._render_datafile(parent, ctrl) 

506 dir_node = self._get_datafile_node(ctrl.data) 

507 return dir_node 

508 

509 def _select_resource(self, message): 1ab

510 self.select_controller_node(message.item) 

511 

512 def select_controller_node(self, controller): 1ab

513 self.SelectItem(self.controller.find_node_by_controller(controller)) 

514 

515 def _suite_added(self, message): 1ab

516 self.add_datafile(message.parent, message.suite) 

517 

518 def refresh_view(self): 1ab

519 self.Show() 

520 self.Refresh() 

521 # print(f"DEBUG: Called Tree._refresh_view {self.GetParent().GetClassName()}") 

522 if self._resource_root: 522 ↛ 524line 522 didn't jump to line 524 because the condition on line 522 was always true

523 self.Expand(self._resource_root) 

524 if self.datafile_nodes: 

525 self._expand_and_render_children(self.datafile_nodes[0]) 

526 wx.CallAfter(self.SelectItem, self.datafile_nodes[0]) 

527 self.Update() 

528 # print(f"DEBUG: Called Tree._refresh_view parent={self.GetParent().GetClassName()} self={self}") 

529 

530 def _render_datafile(self, parent_node, controller, index=None): 1ab

531 node = self._create_node_with_handler(parent_node, controller, index) 1aef

532 if not node: 532 ↛ 533line 532 didn't jump to line 533 because the condition on line 532 was never true1aef

533 return None 

534 if controller.dirty: 1aef

535 self.controller.mark_node_dirty(node) 1ef

536 self.datafile_nodes.append(node) 1aef

537 self.SetItemHasChildren(node, True) 1aef

538 

539 for child in controller.children: 1aef

540 self._render_datafile(node, child) 

541 return node 1aef

542 

543 @staticmethod 1ab

544 def _normalize(path): 1ab

545 return os.path.normcase(os.path.normpath(os.path.abspath(path))) 

546 

547 def _create_node_with_handler(self, parent_node, controller, index=None): 1ab

548 from ..controller.filecontrollers import ResourceFileController 1acdef

549 if (controller.display_name.startswith("#") or 549 ↛ 551line 549 didn't jump to line 551 because the condition on line 549 was never true1acdef

550 controller.display_name.startswith("...")): # If it is a comment or continuation marker don't create 

551 return None 

552 

553 handler_class = action_handler_class(controller) 1acdef

554 with_checkbox = (handler_class == TestCaseHandler and self._checkboxes_for_tests) 1acdef

555 node = self._create_node(parent_node, controller.display_name, self._images[controller], 1acdef

556 index, with_checkbox=with_checkbox) 

557 if isinstance(controller, ResourceFileController) and not controller.is_used(): 1acdef

558 self.SetItemTextColour(node, TREETEXTCOLOUR) # wxPython3 hack 1ae

559 action_handler = handler_class(controller, self, node, self.controller.settings) 1acdef

560 self.SetPyData(node, action_handler) 1acdef

561 

562 # if we have a TestCase node we have to make sure that 

563 # we retain the checked state 

564 if (handler_class == TestCaseHandler and self._checkboxes_for_tests) \ 564 ↛ 566line 564 didn't jump to line 566 because the condition on line 564 was never true1acdef

565 and self._test_selection_controller.is_test_selected(controller): 

566 self.CheckItem(node, True) 

567 if controller.is_excluded(): 567 ↛ 568line 567 didn't jump to line 568 because the condition on line 567 was never true1acdef

568 self._set_item_excluded(node) 

569 return node 1acdef

570 

571 def set_checkboxes_for_tests(self): 1ab

572 self._checkboxes_for_tests = True 

573 

574 def _expand_and_render_children(self, node): 1ab

575 assert node is not None 1acdef

576 self._render_children(node) 1acdef

577 self.Expand(node) 1acdef

578 

579 def _render_children(self, node): 1ab

580 handler = self.controller.get_handler(node) 1acdef

581 if not handler or not handler.can_be_rendered: 1acdef

582 return 1acdef

583 self._create_child_nodes( 1aef

584 node, handler, lambda item: item.is_test_suite) 

585 handler.set_rendered() 1aef

586 

587 def _create_child_nodes(self, node, handler, predicate): 1ab

588 for childitem in self._children_of(handler): 1aef

589 index = self._get_insertion_index(node, predicate) 1aef

590 self._create_node_with_handler(node, childitem, index) 1aef

591 

592 @staticmethod 1ab

593 def _children_of(handler): 1ab

594 return [v for v in handler.variables if v.has_data()] + list(handler.tests) + list(handler.keywords) 1aef

595 

596 def _create_node(self, parent_node, label, img, index=None, with_checkbox=False): 1ab

597 node = self._wx_node(parent_node, index, label, with_checkbox) 1acdef

598 self.SetItemImage(node, img.normal, wx.TreeItemIcon_Normal) 1acdef

599 self.SetItemImage(node, img.expanded, wx.TreeItemIcon_Expanded) 1acdef

600 return node 1acdef

601 

602 def _wx_node(self, parent_node, index, label, with_checkbox): 1ab

603 ct_type = 1 if with_checkbox else 0 1acdef

604 if index is not None: 1acdef

605 # blame wxPython for this ugliness 

606 if isinstance(index, int): 1acdef

607 return self.InsertItemByIndex( 1ae

608 parent_node, index, label, ct_type=ct_type) 

609 else: 

610 return self.InsertItem( 1acdf

611 parent_node, index, label, ct_type=ct_type) 

612 return self.AppendItem(parent_node, label, ct_type=ct_type) 1aef

613 

614 def add_datafile(self, parent, suite): 1ab

615 snode = self._render_datafile( 

616 self._get_datafile_node(parent.data), suite) 

617 self.SelectItem(snode) 

618 

619 def add_test(self, parent_node, test): 1ab

620 self._add_dataitem( 1DvEFGHc

621 parent_node, test, lambda item: item.is_user_keyword) 

622 

623 def add_keyword(self, parent_node, kw): 1ab

624 self._add_dataitem(parent_node, kw, lambda item: item.is_test_suite) 1xtyzIJKABCd

625 

626 def _add_dataitem(self, parent_node, dataitem, predicate): 1ab

627 node = self._get_or_create_node(parent_node, dataitem, predicate) 1DvEFGHxtyzIJKABCcd

628 self._select(node) 1cd

629 self.controller.mark_node_dirty(parent_node) 1cd

630 

631 def _get_or_create_node(self, parent_node, dataitem, predicate): 1ab

632 if not self.IsExpanded(parent_node): 632 ↛ 633line 632 didn't jump to line 633 because the condition on line 632 was never true1DvEFGHxtyzIJKABCcd

633 self._expand_and_render_children(parent_node) 

634 return self.controller.find_node_with_label( 

635 parent_node, dataitem.display_name) 

636 

637 index = self._get_insertion_index(parent_node, predicate) 1cd

638 return self._create_node_with_handler(parent_node, dataitem, index) 1cd

639 

640 def _select(self, node): 1ab

641 if node: 

642 wx.CallAfter(self.SelectItem, node) 

643 

644 def _get_insertion_index(self, parent_node, predicate): 1ab

645 if not predicate: 645 ↛ 646line 645 didn't jump to line 646 because the condition on line 645 was never true1acdef

646 return None 

647 item, cookie = self.GetFirstChild(parent_node) 1acdef

648 while item: 1acdef

649 if predicate(self.controller.get_handler(item)): 1acdf

650 index = self.GetPrevSibling(item) 1acd

651 if not index: 1acd

652 index = 0 

653 return index 1acd

654 item, cookie = self.GetNextChild(parent_node, cookie) 1acdf

655 return None 1aef

656 

657 def _keyword_added(self, message): 1ab

658 self.add_keyword(self._get_datafile_node(self.get_selected_datafile()), 1xtyzIJKABCd

659 message.item) 

660 

661 def _variable_added(self, message): 1ab

662 self._get_or_create_node( 

663 self._get_datafile_node(self.get_selected_datafile()), 

664 message.item, 

665 lambda item: not item.is_variable or item.index > message.index) 

666 

667 def _leaf_item_removed(self, message): 1ab

668 node = self.controller.find_node_by_controller(message.item) 

669 parent_node = self._get_datafile_node(message.datafile) 

670 # DEBUG The below call causes not calling delete_node 

671 # self._test_selection_controller.select(message.item, False) 

672 self.controller.mark_node_dirty(parent_node) 

673 self.delete_node(node) 

674 

675 def _test_added(self, message): 1ab

676 self.add_test(self._get_datafile_node(self.get_selected_datafile()), 1DvEFGHc

677 message.item) 

678 

679 def _datafile_removed(self, message): 1ab

680 dfnode = self._get_datafile_node(message.datafile.data) 

681 self.datafile_nodes.remove(dfnode) 

682 self.DeleteChildren(dfnode) 

683 self.Delete(dfnode) 

684 

685 def _datafile_set(self, message): 1ab

686 wx.CallAfter(self._refresh_datafile_when_file_set, message.item) 1aDvEFGHxtyzIJKABC

687 

688 def _filename_changed(self, message): 1ab

689 from ..controller import ResourceFileController 

690 df = message.datafile 

691 node = self.controller.find_node_by_controller(df) 

692 if not node: 

693 raise AssertionError('No node found with controller "%s"' % df) 

694 old_name = node.GetText() 

695 # print(f"DEBUG: treeplugin.py Tree _filename_changed message={message} df={df}" 

696 # f" node={node} {old_name=} df.display_name={df.display_name}") 

697 wx.CallAfter(self.SetItemText, node, df.display_name) 

698 if isinstance(df, ResourceFileController): 

699 RideItemNameChanged(item=node, old_name=old_name, new_name=df.display_name).publish() 

700 

701 def add_keyword_controller(self, controller): 1ab

702 parent = self._get_datafile_node(self.get_selected_datafile()) 

703 self.add_keyword(parent, controller) 

704 

705 def delete_node(self, node): 1ab

706 if node is None: 

707 return 

708 parent = self.GetItemParent(node) 

709 self.controller.mark_node_dirty(parent) 

710 self.Delete(node) 

711 wx.CallAfter(self.SelectItem, parent) 

712 

713 def _data_dirty(self, message): 1ab

714 # print("DEBUG: TreePlugin tree _data_dirty ENTER") 

715 if not self.dirty: 1DvEFGHxtyzIJKABCcdef

716 self.dirty = True 1Dcdef

717 self.controller.mark_controller_dirty(message.datafile) 1Dcdef

718 

719 def _data_undirty(self, message): 1ab

720 _ = message 

721 # print("DEBUG: TreePlugin tree _data_undirty") 

722 self.unset_dirty() 

723 

724 def unset_dirty(self): 1ab

725 for node in self.datafile_nodes: 

726 text = self.GetItemText(node) 

727 handler = self.controller.get_handler(node) 

728 if text.startswith('*') and not handler.controller.dirty: 

729 self.SetItemText(node, text[1:]) 

730 self.dirty = False 

731 

732 def select_node_by_data(self, controller): 1ab

733 """Find and select the tree item associated with the given controller. 

734 

735 Controller can be any of the controllers that are represented in the 

736 tree.""" 

737 parent_node = self._get_datafile_node(controller.datafile) 1awcd

738 # print(f"DEBUG: treeplugin.py Tree select_node_by_data parent_node={parent_node}") 

739 if not parent_node: 1awcd

740 return None 1w

741 # if not self.IsExpanded(parent_node): 

742 self.EnsureVisible(parent_node) 1acd

743 self._expand_and_render_children(parent_node) 1acd

744 node = self.controller.find_node_by_controller(controller) 1acd

745 if node and node != self.GetSelection(): 745 ↛ 747line 745 didn't jump to line 747 because the condition on line 745 was always true1acd

746 self.SelectItem(node) 1acd

747 return node 1acd

748 

749 def select_user_keyword_node(self, uk): 1ab

750 parent_node = self._get_datafile_node(uk.parent.parent) 1s

751 if not parent_node: 751 ↛ 752line 751 didn't jump to line 752 because the condition on line 751 was never true1s

752 return 

753 self.EnsureVisible(parent_node) 1s

754 if not self.IsExpanded(parent_node): 754 ↛ 755line 754 didn't jump to line 755 because the condition on line 754 was never true1s

755 self._expand_and_render_children(parent_node) 

756 node = self.controller.find_node_with_label(parent_node, utils.normalize(uk.name)) 1s

757 # print(f"DEBUG: treeplugin.py Tree select_user_keyword_node node= {node}") 

758 if node != self.GetSelection(): 758 ↛ exitline 758 didn't return from function 'select_user_keyword_node' because the condition on line 758 was always true1s

759 self.SelectItem(node) 1s

760 

761 def _get_datafile_node(self, datafile): 1ab

762 # print(f"DEBUG: treeplugin.py Tree _get_datafile_node ENTER dataffile= {datafile}") 

763 for node in self.datafile_nodes: 1awDvEFGHxtyzIJKABCcds

764 item = self.controller.get_handler(node).item 1awcds

765 if item == datafile: # This only works before editing a resource item because the obj id changes 1awcds

766 return node 1acds

767 if type(item) == type(datafile) and hasattr(item, 'name') and item.name == datafile.name: 1awcs

768 return node 

769 return None 1awDvEFGHxtyzIJKABC

770 

771 def get_selected_datafile(self): 1ab

772 """Returns currently selected data file. 

773 

774 If a test or user keyword node is selected, returns parent of that 

775 item.""" 

776 datafile = self._get_selected_datafile_node() 1awDvEFGHxtyzIJKABCcdu

777 if not datafile: 1awDvEFGHxtyzIJKABCcdu

778 return None 1awDvEFGHxtyzIJKABC

779 return self.controller.get_handler(datafile).item 1cdu

780 

781 def get_selected_datafile_controller(self): 1ab

782 """Returns controller associated with currently active data file. 

783 

784 If a test or user keyword node is selected, returns parent of that 

785 item.""" 

786 dfnode = self._get_selected_datafile_node() 1ahijklmMNwOPQRSTUVgnopqrLvt

787 

788 if dfnode: 788 ↛ 789line 788 didn't jump to line 789 because the condition on line 788 was never true1ahijklmMNwOPQRSTUVgnopqrLvt

789 return self.controller.get_handler(dfnode).controller 

790 else: 

791 return None 1ahijklmMNwOPQRSTUVgnopqrLvt

792 

793 def _get_selected_datafile_node(self): 1ab

794 node = self.GetSelection() 1ahijklmMNwOPQRSTUVgnopqrLDvEFGHxtyzIJKABCcdu

795 if not node or node in (self._resource_root, self.root): 1ahijklmMNwOPQRSTUVgnopqrLDvEFGHxtyzIJKABCcdu

796 return None 1ahijklmMNwOPQRSTUVgnopqrLDvEFGHxtyzIJKABC

797 while node not in self.datafile_nodes: 1cdu

798 node = self.GetItemParent(node) 1u

799 return node 1cdu

800 

801 def get_selected_item(self): 1ab

802 """Returns model object associated with currently selected tree node. 

803 """ 

804 selection = self.GetSelection() 1awgWX

805 if not selection: 805 ↛ 806line 805 didn't jump to line 806 because the condition on line 805 was never true1awgWX

806 return None 

807 handler = self.controller.get_handler(selection) 1awgWX

808 return handler and handler.controller or None 1awgWX

809 

810 def tree_node_selected(self, node): 1ab

811 # print(f"DEBUG: TreePlugin tree node selected {str(node)}") 

812 pass 

813 

814 def move_up(self, node): 1ab

815 prev = self.GetPrevSibling(node) 

816 if prev.IsOk(): 

817 self._switch_items(prev, node, node) 

818 

819 def move_down(self, node): 1ab

820 _next = self.GetNextSibling(node) 

821 if _next.IsOk(): 

822 self._switch_items(node, _next, node) 

823 

824 def _switch_items(self, first, second, currently_selected): 1ab

825 """Changes the order of given items, first is expected to be directly 

826 above the second""" 

827 selection = self.GetItemData(currently_selected).controller 

828 controller = self.controller.get_handler(first).controller 

829 self.Delete(first) 

830 self._create_node_with_handler(self.GetItemParent(second), controller, second) 

831 self.select_node_by_data(selection) 

832 

833 def _refresh_datafile_when_file_set(self, controller): 1ab

834 # remove invalid cases selection when data file is changed in text editor 

835 self._test_selection_controller.remove_invalid_cases_selection(controller) 1hijklmgnopqrLvt

836 # Prevent tab selections based on tree item selected events 

837 self._start_silent_mode() 1hijklmgnopqrLvt

838 current = self.get_selected_datafile_controller() 1hijklmgnopqrLvt

839 if not current: # If tree is not yet in use - do not expand anything. 839 ↛ 843line 839 didn't jump to line 843 because the condition on line 839 was always true1hijklmgnopqrLvt

840 self._end_silent_mode() 1hijklmgnopqrLvt

841 return 1hijklmgnopqrLvt

842 

843 item = self.GetSelection() 

844 current_txt = self.GetItemText(item) if item.IsOk() else '' 

845 # after refresh current and current_txt might have been changed 

846 node = self._refresh_datafile(controller) 

847 if node is None: 

848 # DEBUG: Find out why this sometimes happens 

849 return 

850 self._expand_and_render_children(node) 

851 if current == controller: 

852 select_item = self.controller.find_node_with_label( 

853 node, current_txt) 

854 if select_item is None: 

855 select_item = node 

856 wx.CallAfter(self.SelectItem, select_item) 

857 wx.CallAfter(self._end_silent_mode) 

858 else: 

859 self._end_silent_mode() 

860 

861 def _uncheck_tests(self, controller): 1ab

862 self._test_selection_controller.unselect_all(controller.tests) 

863 

864 def _start_silent_mode(self): 1ab

865 self._silent_mode = True 1hijklmgnopqrLvt

866 

867 def _end_silent_mode(self): 1ab

868 self._silent_mode = False 1hijklmgnopqrLvt

869 

870 def refresh_datafile(self, controller, event): 1ab

871 to_be_selected = self._get_pending_selection(event) 1ef

872 new_node = self._refresh_datafile(controller) 1ef

873 self._handle_pending_selection(to_be_selected, new_node) 1ef

874 

875 def _refresh_datafile(self, controller): 1ab

876 orig_node = self._get_data_controller_node(controller) 1ef

877 if orig_node is not None: 877 ↛ exitline 877 didn't return from function '_refresh_datafile' because the condition on line 877 was always true1ef

878 insertion_index = self._get_datafile_index(orig_node) 1ef

879 parent = self.GetItemParent(orig_node) 1ef

880 self._remove_datafile_node(orig_node) 1ef

881 return self._render_datafile(parent, controller, insertion_index) 1ef

882 

883 def _get_pending_selection(self, event): 1ab

884 if hasattr(event, 'Item'): 884 ↛ 885line 884 didn't jump to line 885 because the condition on line 884 was never true1ef

885 item = event.GetItem() 

886 event.Veto() 

887 elif hasattr(event, 'Position'): 887 ↛ 888line 887 didn't jump to line 888 because the condition on line 887 was never true1ef

888 item, flags = self.HitTest(event.Position) 

889 if not self._click_on_item(item, flags): 

890 return 

891 else: 

892 return 1ef

893 return self.GetItemText(item) 

894 

895 def _get_data_controller_node(self, controller): 1ab

896 for node in self.datafile_nodes: 896 ↛ 899line 896 didn't jump to line 899 because the loop on line 896 didn't complete1ef

897 if self.GetItemData(node).controller == controller: 1ef

898 return node 1ef

899 return None 

900 

901 @staticmethod 1ab

902 def _click_on_item(item, flags): 1ab

903 return item is not None and item.IsOk() and flags & wx.TREE_HITTEST_ONITEM 

904 

905 def _get_datafile_index(self, node): 1ab

906 insertion_index = self.GetPrevSibling(node) 1ef

907 if not insertion_index: 1ef

908 insertion_index = 0 1e

909 return insertion_index 1ef

910 

911 def _remove_datafile_node(self, node): 1ab

912 for child in self.GetItemChildren(node): 1ef

913 if child in self.datafile_nodes: 913 ↛ 914line 913 didn't jump to line 914 because the condition on line 913 was never true1ef

914 self._remove_datafile_node(child) 

915 self.datafile_nodes.remove(node) 1ef

916 self.Delete(node) 1ef

917 

918 def _handle_pending_selection(self, to_be_selected, parent_node): 1ab

919 if to_be_selected: 919 ↛ 920line 919 didn't jump to line 920 because the condition on line 919 was never true1ef

920 self._expand_and_render_children(parent_node) 

921 select_item = self.controller.find_node_with_label( 

922 parent_node, to_be_selected) 

923 wx.CallAfter(self.SelectItem, select_item) 

924 

925 def on_sel_changed(self, event): 1ab

926 node = event.GetItem() 1ahijklmgnopqrcdus

927 if not node.IsOk() or self._dragging: 927 ↛ 928line 927 didn't jump to line 928 because the condition on line 927 was never true1ahijklmgnopqrcdus

928 event.Skip() 

929 return 

930 self.controller.add_to_history(node) 1ahijklmgnopqrcdus

931 handler = self.controller.get_handler(node) 1ahijklmgnopqrcdus

932 if handler and handler.item: 932 ↛ 938line 932 didn't jump to line 938 because the condition on line 932 was always true1ahijklmgnopqrcdus

933 self._update_data_file_namespace(node) 1ahijklmgnopqrcdus

934 RideTreeSelection( 1ahijklmgnopqrcdus

935 node=node, 

936 item=handler.controller, 

937 silent=self._silent_mode).publish() 

938 self.SetFocus() 1ahijklmgnopqrcdus

939 

940 def _update_data_file_namespace(self, node): 1ab

941 while True: 1ahijklmgnopqrcdus

942 if not node: 942 ↛ 943line 942 didn't jump to line 943 because the condition on line 942 was never true1ahijklmgnopqrcdus

943 return 

944 handler = self.controller.get_handler(node) 1ahijklmgnopqrcdus

945 if hasattr(handler.controller, 'get_namespace'): 1ahijklmgnopqrcdus

946 data_file_ns = handler.controller.get_namespace() 1ahijklmgnopqrcdus

947 if not data_file_ns: 1ahijklmgnopqrcdus

948 return 1cdus

949 cur_dir = handler.controller.directory 1ahijklmgnopqru

950 if data_file_ns: 950 ↛ 952line 950 didn't jump to line 952 because the condition on line 950 was always true1ahijklmgnopqru

951 data_file_ns.update_cur_dir_global_var(cur_dir) 1ahijklmgnopqru

952 return 1ahijklmgnopqr

953 else: 

954 node = node.GetParent() 1cdus

955 

956 def on_tree_item_expanding(self, event): 1ab

957 node = event.GetItem() 1aef

958 if node.IsOk(): 958 ↛ exitline 958 didn't return from function 'on_tree_item_expanding' because the condition on line 958 was always true1aef

959 self._render_children(node) 1aef

960 

961 # This exists because CustomTreeItem does not remove animations 

962 def on_tree_item_collapsing(self, event): 1ab

963 item = event.GetItem() 

964 self._hide_item(item) 

965 event.Skip() 

966 

967 def _hide_item(self, item): 1ab

968 for item in item.GetChildren(): 

969 itemwindow = item.GetWindow() 

970 if itemwindow: 

971 itemwindow.Hide() 

972 if self.ItemHasChildren(item): 

973 self._hide_item(item) 

974 

975 def SelectAllTests(self, item: GenericTreeItem, selected=True): 1ab

976 """ 

977 Select tests for execution 

978 :param item: The node of the graphical tree where the user triggered the action from 

979 :param selected: Whether we want to select or un-select for execution 

980 :return: Nothing 

981 """ 

982 test_controllers = self.retrieveTestCaseControllers(item) 

983 self._test_selection_controller.select_all(test_controllers, selected) 

984 

985 @staticmethod 1ab

986 def retrieveTestCaseControllers(item: GenericTreeItem): 1ab

987 from ..controller.filecontrollers import TestDataDirectoryController, TestCaseFileController 

988 

989 data = item.GetData() 

990 if isinstance(data, TestDataDirectoryHandler): 

991 item_controller: TestDataDirectoryController = data.tests.parent 

992 elif isinstance(data, TestCaseFileHandler): 

993 item_controller: TestCaseFileController = data.controller 

994 else: 

995 raise AttributeError("Unexpected type of handler: " + str(data)) 

996 test_controllers = item_controller.retrieve_test_controllers() 

997 return test_controllers 

998 

999 def SelectTests(self, tests): 1ab

1000 self._test_selection_controller.select_all(tests) 

1001 

1002 def ExpandAllSubNodes(self, item): 1ab

1003 self._expand_or_collapse_nodes(item, self.Expand) 

1004 

1005 def CollapseAllSubNodes(self, item): 1ab

1006 self._expand_or_collapse_nodes(item, self.Collapse) 

1007 

1008 def _expand_or_collapse_nodes(self, item, callback): 1ab

1009 if not self.HasAGWFlag(customtreectrl.TR_HIDE_ROOT) or item != self.GetRootItem(): 

1010 callback(item) 

1011 for child in item.GetChildren(): 

1012 self._expand_or_collapse_nodes(child, callback) 

1013 

1014 def _for_all_drawn_tests(self, item, func): 1ab

1015 if self._is_test_node(item): 

1016 func(item) 

1017 for child in item.GetChildren(): 

1018 self._for_all_drawn_tests(child, func) 

1019 

1020 @staticmethod 1ab

1021 def _is_test_node(node): 1ab

1022 return node.GetType() == 1 

1023 

1024 def DeselectTests(self, tests): 1ab

1025 self._test_selection_controller.unselect_all(tests) 

1026 

1027 def SelectFailedTests(self, item): 1ab

1028 all_controllers = self.retrieveTestCaseControllers(item) 

1029 test_controllers = filter( 

1030 lambda ctrl: ctrl.run_passed is False, 

1031 all_controllers) 

1032 self._test_selection_controller.unselect_all(all_controllers) 

1033 self._test_selection_controller.select_all(test_controllers) 

1034 

1035 def SelectPassedTests(self, item): 1ab

1036 all_controllers = self.retrieveTestCaseControllers(item) 

1037 test_controllers = filter( 

1038 lambda ctrl: ctrl.run_passed is True, 

1039 all_controllers) 

1040 self._test_selection_controller.unselect_all(all_controllers) 

1041 self._test_selection_controller.select_all(test_controllers) 

1042 

1043 def on_close(self, event): 1ab

1044 __ = event 

1045 print("DEBUG: Tree OnClose hidding") 

1046 self.Hide() 

1047 

1048 def on_tree_item_checked(self, event): 1ab

1049 node: GenericTreeItem = event.GetItem() 

1050 handler: TestCaseHandler = self.controller.get_handler(node=node) 

1051 self._test_selection_controller.select( 

1052 handler.controller, node.IsChecked()) 

1053 

1054 def on_item_activated(self, event): 1ab

1055 node = event.GetItem() 

1056 if self.IsExpanded(node): 

1057 self.Collapse(node) 

1058 elif self.ItemHasChildren(node): 

1059 self._expand_and_render_children(node) 

1060 

1061 def on_left_arrow(self, event): 1ab

1062 node = self.GetSelection() 

1063 if self.IsExpanded(node): 

1064 self.Collapse(node) 

1065 else: 

1066 event.Skip() 

1067 

1068 def on_right_click(self, event): 1ab

1069 __ = event 

1070 if not self._right_click: 

1071 self._right_click = True 

1072 handler = None 

1073 item, _ = self.HitTest(self.ScreenToClient(wx.GetMousePosition()), wx.TREE_HITTEST_ONITEMLABEL) 

1074 if item: 

1075 # print(f"DEBUG: tree mouse RightClick pos={pos}") 

1076 handler = self.GetItemData(item) 

1077 if handler: 

1078 handler.show_popup() 

1079 self._right_click = False 

1080 

1081 def on_new_test_case(self, event): 1ab

1082 handler = self.controller.get_handler() 

1083 if handler: 

1084 handler.on_new_test_case(event) 

1085 

1086 def OnDrop(self, target, dragged): # Overrides wx method 1ab

1087 dragged = self.controller.get_handler(dragged) 

1088 target = self.controller.get_handler(target) 

1089 if target and target.accepts_drag(dragged): 

1090 dragged.controller.execute(MoveTo(target.controller)) 

1091 self.Refresh() # DEBUG Always refresh 

1092 

1093 def IsValidDragItem(self, item): # Overrides wx method 1ab

1094 return self.controller.get_handler(item).is_draggable 

1095 

1096 def on_move_up(self, event): 1ab

1097 handler = self.controller.get_handler() 

1098 if handler.is_draggable: 

1099 handler.on_move_up(event) 

1100 

1101 def on_move_down(self, event): 1ab

1102 handler = self.controller.get_handler() 

1103 if handler.is_draggable: 

1104 handler.on_move_down(event) 

1105 

1106 def _item_changed(self, message): 1ab

1107 # print(f"DEBUG: treeplugin.py Tree _item_changed ENTER message={message}") 

1108 if isinstance(message, RideItemNameChanged): 1108 ↛ 1109line 1108 didn't jump to line 1109 because the condition on line 1108 was never true1xtyzABC

1109 return 

1110 controller = message.item 1xtyzABC

1111 node = self.controller.find_node_by_controller(controller) 1xtyzABC

1112 if node: 1112 ↛ 1113line 1112 didn't jump to line 1113 because the condition on line 1112 was never true1xtyzABC

1113 self.SetItemText(node, message.item.name) 

1114 self.controller.mark_node_dirty(self._get_datafile_node(controller.datafile)) 

1115 # if controller.dirty: 

1116 

1117 def _item_renamed(self, message): 1ab

1118 from ..lib.robot.parsing.settings import Resource 

1119 # print(f"DEBUG: treeplugin.py Tree _item_renamed ENTER message={message}") 

1120 if not isinstance(message, RideItemNameChanged): 

1121 return 

1122 controller = message.item 

1123 # print(f"DEBUG: treeplugin.py Tree _item_renamed DEFINED controller={controller}" 

1124 # f" old_name={message.old_name} new_name={message.new_name}\n") 

1125 node = (self.controller.find_node_by_controller(controller) or 

1126 self.controller.find_node_with_label(controller, message.old_name)) 

1127 # print(f"DEBUG: treeplugin.py Tree _item_renamed ENTER controller={controller}" 

1128 # f" NODE={node}") 

1129 if hasattr(controller, 'datafile') and hasattr(controller.datafile, 'setting_table'): 

1130 imports = controller.datafile.setting_table.imports 

1131 # print(f"DEBUG: treeplugin.py Tree _item_renamed HAS IMPORTS imports={imports}") 

1132 for imp in imports: 

1133 if isinstance(imp, Resource) and hasattr(message.item, 'keyword'): 

1134 # print(f"DEBUG: treeplugin.py Tree _item_renamed IMPORT message.item.name={message.item.keyword}," 

1135 # f" old_name={message.old_name} resource name={imp.name}") 

1136 # print(f"DEBUG: treeplugin.py Tree _item_renamed IMPORT message.item.name={message}," 

1137 # f" old_name={message.old_name} import={imp}") 

1138 self._rename_resource_kw(new_name=message.item.keyword, old_name=message.old_name, resource=imp) 

1139 # if node and hasattr(message, 'old_name'): 

1140 # print(f"DEBUG: treeplugin.py Tree _item_renamed RENAMED node={node}, old_name={message.old_name}\n" 

1141 # f"new_name={message.new_name}") 

1142 # Here we need to rename all resourc prefixed occurrencees 

1143 

1144 self.Refresh() 

1145 if node and hasattr(message.item, 'name'): 

1146 self.EnsureVisible(node) 

1147 self.SelectItem(node) 

1148 self.SetItemText(node, message.item.name) 

1149 new_name=self.GetItemText(node) 

1150 # print(f"DEBUG: treeplugin.py Tree _item_renamed AFTER RENAMED new_name={new_name}" 

1151 # f" controller={controller.datafile}") 

1152 else: 

1153 return 

1154 if controller.dirty: 

1155 self.controller.mark_node_dirty( 

1156 self._get_datafile_node(controller.datafile)) 

1157 

1158 def _rename_resource_kw(self, new_name, old_name, resource): 1ab

1159 prefix_new = new_name.split('.')[0] 

1160 prefix_old = old_name.split('.')[0] 

1161 prefix_res = os.path.basename(resource.name).split('.')[0] 

1162 if not prefix_new == prefix_old == prefix_res: 

1163 return 

1164 keyword_name = old_name.split('.')[-1] 

1165 new_keyword_name = new_name.split('.')[-1] 

1166 # print(f"DEBUG: treeplugin.py Tree _rename_resource_kw {prefix_res=} {keyword_name=}") 

1167 res_name=os.path.basename(resource.name) 

1168 # print(f"DEBUG: treeplugin.py Tree _rename_resource_kw {res_name=}") 

1169 res_node=self.FindItem(self._resource_root, res_name) 

1170 if res_node: 

1171 self.EnsureVisible(res_node) 

1172 # print(f"DEBUG: treeplugin.py Tree _rename_resource_kw root node {res_node=}") 

1173 # node = self.controller.find_node_with_label(res_node, keyword_name) 

1174 self._expand_and_render_children(res_node) 

1175 node = self.FindItem(res_node, keyword_name) 

1176 if node: 

1177 from ..controller.ctrlcommands import RenameKeywordOccurrences 

1178 from ..ui.progress import RenameProgressObserver 

1179 nlabel = node.GetText() 

1180 # print(f"DEBUG: treeplugin.py Tree _rename_resource_kw node label {nlabel} {keyword_name}") 

1181 if nlabel == keyword_name: 

1182 # print(f"DEBUG: treeplugin.py Tree _rename_resource_kw {node=} {nlabel} change to {new_keyword_name=}") 

1183 self.EnsureVisible(node) 

1184 # self.SetItemText(node, new_keyword_name) 

1185 self.SelectItem(node) 

1186 controller = self.get_selected_datafile_controller() 

1187 keywords = controller.keywords 

1188 keyword_names = controller.get_keyword_names() 

1189 # print(f"DEBUG: treeplugin.py Tree _rename_resource_kw already changed to {new_keyword_name=}" 

1190 # f" controller={controller} keywords={keywords} \n" 

1191 # f"{keyword_names=}") 

1192 for k in keywords.items: 

1193 # print(f"DEBUG: treeplugin.py Tree _rename_resource_kw keywords: {k=}") 

1194 if k.name == keyword_name: 

1195 # print(f"DEBUG: treeplugin.py Tree _rename_resource_kw CHANGING: {k.name=}," 

1196 # f" {k.source=} ") # {k.details=}") 

1197 if controller.validate_keyword_name(new_keyword_name): 

1198 k.name = new_keyword_name 

1199 node.SetText(new_keyword_name) 

1200 RideUserKeywordRenamed(datafile=controller.datafile, item=k, 

1201 old_name=keyword_name).publish() 

1202 controller.mark_dirty() 

1203 # print(f"DEBUG: treeplugin.py Tree _rename_resource_kw DONE CHANGING: {k.name=}" 

1204 # f"background={self.background}, foreground={self.foreground}") 

1205 # self.controller.mark_node_dirty(self._get_datafile_node(controller.datafile)) 

1206 self.observer = RenameProgressObserver(self.GetParent(), background=self.background, 

1207 foreground=self.foreground) 

1208 RenameKeywordOccurrences(keyword_name, new_keyword_name, self.observer) 

1209 self.observer.finish() 

1210 self.Collapse(res_node) 

1211 self.Expand(res_node) 

1212 self.Refresh() 

1213 self.SelectItem(node) 

1214 else: 

1215 from ..widgets import RIDEDialog 

1216 message_box = RIDEDialog(title=_('Validation Error'), 

1217 message=_("Invalid keyword name: ") % f"{new_keyword_name}", 

1218 style=wx.ICON_ERROR | wx.OK) 

1219 message_box.ShowModal() 

1220 return 

1221 

1222 def _variable_moved_up(self, message): 1ab

1223 if self._should_update_variable_positions(message): 

1224 self._do_action_if_datafile_node_is_expanded(self.move_up, message) 

1225 

1226 def _variable_moved_down(self, message): 1ab

1227 if self._should_update_variable_positions(message): 

1228 self._do_action_if_datafile_node_is_expanded(self.move_down, message) 

1229 

1230 @staticmethod 1ab

1231 def _should_update_variable_positions(message): 1ab

1232 return message.item != message.other and message.item.has_data() and message.other.has_data() 

1233 

1234 def _do_action_if_datafile_node_is_expanded(self, action, data): 1ab

1235 if self.IsExpanded(self._get_datafile_node(data.item.datafile)): 

1236 node = self.controller.find_node_by_controller(data.item) 

1237 action(node) 

1238 

1239 def _variable_updated(self, message): 1ab

1240 self._item_changed(message) 

1241 

1242 def highlight(self, data, text): 1ab

1243 self.select_node_by_data(data) 

1244 self._editor.highlight(text) 

1245 

1246 def node_is_resource_file(self, node): 1ab

1247 return self.controller.get_handler(node).__class__ == \ 1Y

1248 ResourceFileHandler 

1249 

1250 

1251class TreeLabelEditListener(object): 1ab

1252 

1253 def __init__(self, tree, action_registerer): 1ab

1254 self._tree = tree 

1255 tree.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, self.on_begin_label_edit) 

1256 tree.Bind(wx.EVT_TREE_END_LABEL_EDIT, self.on_label_edited) 

1257 tree.Bind(wx.EVT_LEFT_DOWN, self.on_left_down) 

1258 if IS_WINDOWS: 1258 ↛ 1260line 1258 didn't jump to line 1260 because the condition on line 1258 was never true

1259 # Delete key does not work in windows without registration 

1260 delete_key_action = ActionInfo( 

1261 None, None, action=self.on_delete, shortcut='Del') 

1262 action_registerer.register_shortcut(delete_key_action) 

1263 self._editing_label = False 

1264 self._on_label_edit_called = False 

1265 

1266 def on_begin_label_edit(self, event): 1ab

1267 # See http://code.google.com/p/robotframework-ride/issues/detail?id=756 

1268 self._editing_label = True 

1269 if not self._on_label_edit_called: 

1270 self.on_label_edit() 

1271 event.Veto() 

1272 # On windows CustomTreeCtrl will create Editor component 

1273 # And we want this to be done by the handler -- as it knows if 

1274 # there should be one or not. And because this will make it work 

1275 # the same way as when pressing F2 so in other words there is 

1276 # a bug if we don't Veto this event 

1277 

1278 def on_label_edit(self, event=None): 1ab

1279 __ = event 

1280 if not self._on_label_edit_called: 

1281 self._on_label_edit_called = True 

1282 handler = self._tree.controller.get_handler() 

1283 if handler and not handler.begin_label_edit(): 

1284 self._on_label_edit_called = False 

1285 self._editing_label = False 

1286 

1287 def on_label_edited(self, event): 1ab

1288 self._editing_label = False 

1289 self._on_label_edit_called = False 

1290 self._tree.controller.get_handler(event.GetItem()).end_label_edit(event) 

1291 

1292 # Reset edit control as it doesn't seem to reset it in case the focus 

1293 # goes directly away from the tree control 

1294 # Use CallAfter to prevent messing up the current end label edit 

1295 # and the another CallAfter because of 

1296 # customtreectrl.TreeTextCtrl#OnChar will call CallAfter(self.Finish) 

1297 # when Enter is pressed --> Results in PyDeadObject if called after 

1298 # ResetEditControl 

1299 wx.CallAfter(wx.CallAfter, self._stop_editing) 

1300 

1301 def _stop_editing(self): 1ab

1302 control = self._tree.GetEditControl() 

1303 if control and wx.Window.FindFocus(): 

1304 control.StopEditing() 

1305 

1306 def on_delete(self, event): 1ab

1307 __ = event 

1308 editor = self._tree.GetEditControl() 

1309 if editor and wx.Window.FindFocus() == editor: 

1310 start, end = editor.GetSelection() 

1311 editor.Remove(start, max(end, start + 1)) 

1312 

1313 def on_left_down(self, event): 1ab

1314 # See http://code.google.com/p/robotframework-ride/issues/detail?id=756 

1315 if IS_WINDOWS and self._editing_label: 

1316 # This method works only on Windows, luckily the issue 756 exists 

1317 # only on Windows 

1318 self._tree.OnCancelEdit(self._tree.GetSelection()) 

1319 event.Skip() 

1320 

1321 def get_handler(self, item=None): 1ab

1322 return self._tree.get_handler(item)