Robot Framework Integrated Development Environment (RIDE)
treeplugin.py
Go to the documentation of this file.
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 
16 import os
17 
18 import wx
19 from wx import Colour
20 from wx.lib.agw import customtreectrl
21 from wx.lib.agw.aui import GetManager
22 from wx.lib.agw.customtreectrl import GenericTreeItem
23 from wx.lib.mixins import treemixin
24 
25 # from ..controller.macrocontrollers import TestCaseController
26 
27 TREETEXTCOLOUR = Colour(0xA9, 0xA9, 0xA9)
28 
29 # from ..controller.ui.treecontroller import TreeController, TestSelectionController
30 
31 from ..context import IS_WINDOWS
32 # from ..controller.filecontrollers import ResourceFileController, TestDataDirectoryController, TestCaseFileController
33 from ..publish.messages import (RideTestRunning, RideTestPaused, RideTestPassed, RideTestFailed, RideTestSkipped,
34  RideTestExecutionStarted, RideTestStopped, RideImportSetting, RideExcludesChanged,
35  RideIncludesChanged, RideOpenSuite, RideNewProject)
36 from ..ui.images import (RUNNING_IMAGE_INDEX, PASSED_IMAGE_INDEX, FAILED_IMAGE_INDEX, PAUSED_IMAGE_INDEX,
37  SKIPPED_IMAGE_INDEX, ROBOT_IMAGE_INDEX)
38 from ..ui.treenodehandlers import TestCaseHandler, TestDataDirectoryHandler, TestCaseFileHandler
39 from ..publish import (PUBLISHER, RideTreeSelection, RideFileNameChanged, RideItem, RideUserKeywordAdded,
40  RideTestCaseAdded, RideUserKeywordRemoved, RideTestCaseRemoved, RideDataFileRemoved,
41  RideDataChangedToDirty, RideDataDirtyCleared, RideVariableRemoved, RideVariableAdded,
42  RideVariableMovedUp, RideVariableMovedDown, RideVariableUpdated, RideOpenResource,
43  RideSuiteAdded, RideSelectResource, RideDataFileSet)
44 from ..controller.ctrlcommands import MoveTo
45 from ..pluginapi import Plugin
46 from ..action import ActionInfo
47 from ..widgets import PopupCreator
48 from .. import utils
49 from .treenodehandlers import ResourceRootHandler, action_handler_class, ResourceFileHandler
50 from .images import TreeImageList
51 
52 
53 
56 _TREE_ARGS = {'style': wx.HSCROLL|wx.VSCROLL } #DEBUG wx.TR_DEFAULT_STYLE}
57 _TREE_ARGS['agwStyle'] = customtreectrl.TR_DEFAULT_STYLE | customtreectrl.TR_HIDE_ROOT | \
58  customtreectrl.TR_EDIT_LABELS
59 _TREE_ARGS['agwStyle'] |= customtreectrl.TR_TOOLTIP_ON_LONG_ITEMS | customtreectrl.TR_HAS_VARIABLE_ROW_HEIGHT
60 
61 if IS_WINDOWS:
62  _TREE_ARGS['style'] |= wx.TR_EDIT_LABELS
63 
64 
65 
67  datafile = property(lambda self: self.get_selected_datafileget_selected_datafile())
68  defaults = {"opened": True,
69  "docked": True
70  }
71 
72  def __init__(self, application):
73  Plugin.__init__(self, application, default_settings=self.defaultsdefaults)
74  self.settingssettings = application.settings._config_obj['Plugins']['Tree']
75  self._parent_parent = None
76  self._tree_tree = self.treetree
77  """
78  self._tree.SetBackgroundColour(Colour(200, 222, 40))
79  self._tree.SetOwnBackgroundColour(Colour(200, 222, 40))
80  self._tree.SetForegroundColour(Colour(7, 0, 70))
81  self._tree.SetOwnForegroundColour(Colour(7, 0, 70))
82  """
83  self._mgr_mgr = GetManager(self._tree_tree)
84  """
85  self._action_registerer = action_registerer
86  self.tree = parent.tree
87  """
88  self.pane_idpane_id = self._tree_tree.GetId()
89  self._model_model= self.modelmodel
90  # self.frame.Bind(wx.EVT_MOVE, self.OnShowTree)
91  # self.frame.Bind(wx.EVT_SHOW, self.OnShowTree)
92  self._tree_tree.Bind(wx.EVT_SHOW, self.OnShowTreeOnShowTree)
93  self._tree_tree.Bind(wx.EVT_MOVE, self.OnTabChangedOnTabChanged)
94  # parent, action_registerer, , default_settings={'collapsed':True}
95  self._pane_pane = self._mgr_mgr.GetPane(self._tree_tree)
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):
100  if parent:
101  self._parent_parent = parent
102 
103  # print(f"DEBUG: TreePlugin register_frame lefpanel={self._mgr.GetPane('left_panel')}")
104  # lefpanel = self._mgr.GetPane('left_panel')
105  # self._mgr.DetachPane(lefpanel)
106  if self._mgr_mgr.GetPane("tree_content") in self._mgr_mgr._panes:
107  register = self._mgr_mgr.InsertPane
108  else:
109  register = self._mgr_mgr.AddPane
110 
111  register(self._tree_tree, wx.lib.agw.aui.AuiPaneInfo().Name("tree_content").
112  Caption("Test Suites").LeftDockable(True)) # TODO: restore .CloseButton(True) when restore is fixed
113 
114  self._mgr_mgr.Update()
115  # print(f"DEBUG: TreePlugin frame {self._parent.GetTitle()} tree {self._tree.GetName()}")
116 
117 
123  def enable(self):
124  # DEBUG this does not work, the panel has no tree, when we dock from floating
125  # TODO: Attempt to create Panel and then calling self.OnShowTree
126  self.subscribesubscribe(self.OnTreeSelectionOnTreeSelection, RideTreeSelection)
127  # self.save_setting('opened', True)
128  # TODO: Add toggle checkbox to menu View/Hide Tree
129  if self.opened:
130  self.OnShowTreeOnShowTree(None)
131  # print(f"DEBUG: TreePlugin end enable tree focused? {self.is_focused()}")
132  # self.subscribe(self.OnTabChanged, RideNotebookTabChanged)
133  # self.subscribe(self._update_preview, RideTestCaseAdded)
134  # self.subscribe(self._update_preview, RideUserKeywordAdded)
135  # self.add_self_as_tree_aware_plugin()
136 
137  def close_tree(self):
138  self._mgr_mgr.DetachPane(self._tree_tree)
139  self._tree_tree.Hide()
140  self._mgr_mgr.Update()
141  self.save_settingsave_setting('opened', False)
142  # print(f"DEBUG: TreePlugin Called close")
143 
144  def disable(self):
145  self.close_treeclose_tree()
146  # self.save_setting('opened', False)
147  self.unsubscribe_allunsubscribe_all()
148  self.unregister_actionsunregister_actions()
149 
150  def is_focused(self):
151  return self._tree_tree.HasFocus()
152 
153  def populate(self, model):
154  if model: # DEBUG: Always populate ... and model != self._model:
155  self._model_model = model
156  # print(f"DEBUG: Populating model... {self._model}\n\n")
157  self._tree_tree.populate(self._model_model)
158 
159  def set_editor(self, editor):
160  self._tree_tree.set_editor(editor)
161 
162  def OnShowTree(self, event):
163  if not self._parent_parent:
164  self._parent_parent = self.frameframe
165  if not self._tree_tree: # This is not needed because tree is always created
166  return # On Windows this code is executed when closing the app, we return now
167  # self._tree = Tree(self, self._parent.actions, self._parent._application.settings)
168  # print(f"DEBUG: TreePlugin Show created tree {self._tree.GetName()}")
169 
170  # self._pane = self._mgr.GetPane(self._tree)
171  # print(f"DEBUG: Tree OnShowTree event {event} self.pane_id={self.pane_id} \n"
172  # f"self._pane = {self._pane}")
173  HTML_BACKGROUND = self.settingssettings.get('background help', (240, 242, 80))
174  HTML_FOREGROUND = self.settingssettings.get('foreground text', (7, 0, 70))
175  HTML_FONT_FACE = self.settingssettings.get('font face', '')
176  HTML_FONT_SIZE = self.settingssettings.get('font size', 11)
177  self._tree_tree.Show(True)
178  self._tree_tree.SetMinSize(wx.Size(200, 225))
179  # self._mgr.DetachPane(self._tree)
180  # self._mgr.Update()
181  # DEBUG: Let's use own method
182  # self._mgr.AddPane(self._tree,
183  # wx.lib.agw.aui.AuiPaneInfo().Name("tree_content").
184  # Caption("Test Suites").LeftDockable(True).
185  # CloseButton(True))
186  # self.register_frame(self._parent)
187  self._tree_tree.SetBackgroundStyle(wx.BG_STYLE_SYSTEM)
188  self._tree_tree.SetBackgroundColour(HTML_BACKGROUND)
189  self._tree_tree.SetForegroundColour(HTML_FOREGROUND)
190  """
191  self.font = self._tree.GetFont()
192  # print(f"DEBUG:font {self.font}, face {HTML_FONT_FACE} size {HTML_FONT_SIZE}")
193  if HTML_FONT_FACE is not None:
194  self.font.SetFaceName(HTML_FONT_FACE)
195  self.font.SetPointSize(HTML_FONT_SIZE)
196  self._tree.SetFont(self.font)
197  self._tree.Refresh()
198  """
199  self._tree_tree.Raise()
200  self.save_settingsave_setting('opened', True)
201  self._tree_tree.populate(self._model_model)
202  # if self._model:
203  # pass
204  # print(f"DEBUG: model {self._model}")
205  self._update_tree_update_tree()
206  self._mgr_mgr.Update()
207 
208  def OnTreeSelection(self, message):
209  if self.is_focusedis_focused():
210  self._tree_tree.tree_node_selected(message.item)
211 
212  def OnTabChanged(self, event):
213  # print(f"DEBUG: Called OnTabChanged")
214  self._update_tree_update_tree()
215  # print(f"DEBUG: Called Tree._refresh_view {self._tree.GetParent().GetClassName()}")
216 
217  def _update_tree(self, event=None):
218  # if self._tree.is_focused():
219  #print(f"DEBUG: Populating model... {self._model}\n\n")
220  #self.populate(self._model)
221  self._tree_tree.populate(self._model_model)
222  self._tree_tree._refresh_view()
223  # print(f"DEBUG: Called Tree._update_tree BEFORE Update")
224  self._tree_tree.Update()
225 
226 
227 class Tree(treemixin.DragAndDrop, customtreectrl.CustomTreeCtrl, wx.Panel):
228 
231  _RESOURCES_NODE_LABEL = 'External Resources'
232 
233  def __init__(self, parent, action_registerer, settings=None):
234  from ..controller.ui.treecontroller import TreeController
235 
236  # self._panel = wx.Panel.__init__(self, parent)
237  self._checkboxes_for_tests_checkboxes_for_tests = False
238  self._test_selection_controller_test_selection_controller = \
239  self._create_test_selection_controller_create_test_selection_controller()
240  self._controller_controller = TreeController(
241  self, action_registerer, settings=settings,
242  test_selection=self._test_selection_controller_test_selection_controller)
243  treemixin.DragAndDrop.__init__(self, parent, **_TREE_ARGS)
244  self._controller_controller.register_tree_actions()
245  self._bind_tree_events_bind_tree_events()
246  self._images_images = TreeImageList()
247  self._animctrl_animctrl = None
248  self._silent_mode_silent_mode = False
249  self.SetImageList(self._images_images)
250  self.label_editorlabel_editor = TreeLabelEditListener(self, action_registerer)
251  self._controller_controller.bind_keys()
252  self._subscribe_to_messages_subscribe_to_messages()
253  self._popup_creator_popup_creator = PopupCreator()
254  self._dragging_dragging = False
255  self._clear_tree_data_clear_tree_data()
256  self._editor_editor = None
257  self._execution_results_execution_results = None
258  self._resources_resources = []
259  self._right_click_right_click = False
260  """
261  self.SetBackgroundColour('white') # TODO get background color from def
262  """
263  # DEBUG: This menu is not working because is being attached to main frame
264  # self._menu = wx.Menu()
265  # self._menu.Append(wx.ID_CLOSE, item="&Close", helpString="Closes panel")
266  # self._mb = wx.MenuBar()
267  # self._mb.Append(self._menu, "Menu")
268  # self.GetParent().SetMenuBar(self._mb)
269  # print(f"DEBUG: Tree tried to add menu to {self.GetParent().__repr__()} parent={parent}")
270  self.pane_idpane_id = self.GetId()
271  if not hasattr(self, 'OnCancelEdit'):
272  self.OnCancelEditOnCancelEdit = self._on_cancel_edit_on_cancel_edit
273 
275  from ..controller.ui.treecontroller import TestSelectionController
276 
277  tsc = TestSelectionController()
278  PUBLISHER.subscribe(tsc.clear_all, RideOpenSuite)
279  PUBLISHER.subscribe(tsc.clear_all, RideNewProject)
280  return tsc
281 
282  def _on_cancel_edit(self, item):
283  le = customtreectrl.TreeEvent(
284  customtreectrl.wxEVT_TREE_END_LABEL_EDIT, self.GetId())
285  le._item = item
286  le.SetEventObject(self)
287  le._label = ""
288  le._editCancelled = True
289  self.GetEventHandler().ProcessEvent(le)
290 
291  def _bind_tree_events(self):
292  self.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClickOnDoubleClick)
293  self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChangedOnSelChanged)
294  self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.OnTreeItemExpandingOnTreeItemExpanding)
295  self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightClickOnRightClick)
296  self.Bind(wx.EVT_TREE_SEL_CHANGING, self.OnSelectionOnSelection)
297  # self.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.OnRightClick)
298  self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnItemActivatedOnItemActivated)
299  self.Bind(customtreectrl.EVT_TREE_ITEM_CHECKED, self.OnTreeItemCheckedOnTreeItemChecked)
300  self.Bind(wx.EVT_TREE_ITEM_COLLAPSING, self.OnTreeItemCollapsingOnTreeItemCollapsing)
301  self.Bind(wx.EVT_CLOSE, self.OnCloseOnClose)
302  # print(f"DEBUG: Tree _bind_tree_events return after Bind OnClose")
303 
304  def OnSelection(self, event):
305  if self._right_click_right_click:
306  event.Skip()
307 
308  def OnDoubleClick(self, event):
309  item, pos = self.HitTest(self.ScreenToClient(wx.GetMousePosition()))
310  if item:
311  handler = self._controller_controller.get_handler(item)
312  handler.double_clicked()
313  event.Skip()
314 
315  def set_editor(self, editor):
316  self._editor_editor = editor
317 
318  def StartDragging(self):
319  self._dragging_dragging = True
320  treemixin.DragAndDrop.StartDragging(self)
321 
322  def OnEndDrag(self, event):
323  self._dragging_dragging = False
324  treemixin.DragAndDrop.OnEndDrag(self, event)
325 
326  def register_context_menu_hook(self, callable):
327  self._popup_creator_popup_creator.add_hook(callable)
328 
329  def unregister_context_menu_hook(self, callable):
330  self._popup_creator_popup_creator.remove_hook(callable)
331 
333  subscriptions = [
334  (self._item_changed_item_changed, RideItem),
335  (self._resource_added_resource_added, RideOpenResource),
336  (self._select_resource_select_resource, RideSelectResource),
337  (self._suite_added_suite_added, RideSuiteAdded),
338  (self._keyword_added_keyword_added, RideUserKeywordAdded),
339  (self._test_added_test_added, RideTestCaseAdded),
340  (self._variable_added_variable_added, RideVariableAdded),
341  (self._leaf_item_removed_leaf_item_removed, RideUserKeywordRemoved),
342  (self._leaf_item_removed_leaf_item_removed, RideTestCaseRemoved),
343  (self._leaf_item_removed_leaf_item_removed, RideVariableRemoved),
344  (self._datafile_removed_datafile_removed, RideDataFileRemoved),
345  (self._datafile_set_datafile_set, RideDataFileSet),
346  (self._data_dirty_data_dirty, RideDataChangedToDirty),
347  (self._data_undirty_data_undirty, RideDataDirtyCleared),
348  (self._variable_moved_up_variable_moved_up, RideVariableMovedUp),
349  (self._variable_moved_down_variable_moved_down, RideVariableMovedDown),
350  (self._variable_updated_variable_updated, RideVariableUpdated),
351  (self._filename_changed_filename_changed, RideFileNameChanged),
352  (self._testing_started_testing_started, RideTestExecutionStarted),
353  (self._test_result_test_result, RideTestRunning),
354  (self._test_result_test_result, RideTestPaused),
355  (self._test_result_test_result, RideTestPassed),
356  (self._test_result_test_result, RideTestFailed),
357  (self._test_result_test_result, RideTestSkipped),
358  (self._test_result_test_result, RideTestStopped),
359  (self._handle_import_setting_message_handle_import_setting_message, RideImportSetting),
360  (self._mark_excludes_mark_excludes, RideExcludesChanged),
361  (self._mark_excludes_mark_excludes, RideIncludesChanged),
362  ]
363  for listener, topic in subscriptions:
364  PUBLISHER.subscribe(listener, topic)
365 
366  def _mark_excludes(self, message):
367  tree = self._controller_controller.find_node_by_controller(message.old_controller)
368  self._render_datafile_render_datafile(self.GetItemParent(tree), message.new_controller)
369  self._remove_datafile_node_remove_datafile_node(tree)
370 
371  def _set_item_excluded(self, node):
372  self.SetItemTextColour(node, wx.TheColourDatabase.Find("GRAY"))
373  self.SetItemItalic(node, True)
374  self.SetItemText(node, "%s (excluded)" % self.GetItemText(node))
375 
376  def _handle_import_setting_message(self, message):
377  if message.is_resource():
378  self._set_resource_color_set_resource_color(
379  message.import_controller.get_imported_controller())
380  self._set_resource_color_set_resource_color(
381  message.import_controller.get_previous_imported_controller())
382 
383  def _set_resource_color(self, resource_controller):
384  if not resource_controller:
385  return
386  node = self._controller_controller.find_node_by_controller(resource_controller)
387  if node:
388  self.SetItemTextColour(
389  node, self._get_resource_text_color_get_resource_text_color(resource_controller))
390 
391  def _get_resource_text_color(self, resource_controller):
392  if resource_controller.is_used():
393  return self.GetDefaultAttributes().colFg
394  else:
395  return wx.LIGHT_GREY
396 
397  def _testing_started(self, message):
398  self._for_all_drawn_tests_for_all_drawn_tests(
399  self._root_root, lambda t: self.SetItemImage(t, ROBOT_IMAGE_INDEX))
400  self._execution_results_execution_results = message.results
401  self._images_images.set_execution_results(message.results)
402 
403  def _test_result(self, message):
404  from ..controller.macrocontrollers import TestCaseController
405 
406  test: TestCaseController = message.item
407  if not test:
408  # test object will be None when running with DataDriver
409  # when runner is interrupted, is also None, so let's stop animation
410  if self._animctrl_animctrl:
411  self._animctrl_animctrl.Stop()
412  self._animctrl_animctrl.Animation.Destroy()
413  self._animctrl_animctrl.Destroy()
414  self._animctrl_animctrl = None
415  return
416  if isinstance(message, RideTestPassed):
417  test.run_passed = True
418  elif isinstance(message, RideTestFailed):
419  test.run_passed = False
420  elif isinstance(message, RideTestSkipped):
421  test.run_passed = False
422  else:
423  test.run_passed = None
424  wx.CallAfter(self._set_icon_from_execution_results_set_icon_from_execution_results, message.item)
425 
426  def _set_icon_from_execution_results(self, controller):
427  node = self._controller_controller.find_node_by_controller(controller)
428  if not node:
429  return
430  img_index = self._get_icon_index_for_get_icon_index_for(controller)
431  # Always set the static icon
432  self.SetItemImage(node, img_index)
433  if self._animctrl_animctrl:
434  self._animctrl_animctrl.Stop()
435  self._animctrl_animctrl.Animation.Destroy()
436  self._animctrl_animctrl.Destroy()
437  self._animctrl_animctrl = None
438  self.DeleteItemWindow(node)
439  if img_index in (RUNNING_IMAGE_INDEX, PAUSED_IMAGE_INDEX):
440  from wx.adv import Animation, AnimationCtrl
441  import os
442 
445  _BASE = os.path.join(os.path.dirname(__file__), '..', 'widgets')
446  if img_index == RUNNING_IMAGE_INDEX:
447  img = os.path.join(_BASE, 'robot-running.gif')
448  else:
449  img = os.path.join(_BASE, 'robot-pause.gif')
450  ani = Animation(img)
451  obj = self
452  rect = (node.GetX()+20, node.GetY()) # Overlaps robot icon
453  self._animctrl_animctrl = AnimationCtrl(obj, -1, ani, rect)
454  """
455  self._animctrl.SetBackgroundColour(obj.GetBackgroundColour())
456  """
457  self._animctrl_animctrl.SetBackgroundColour('white')
458  self.SetItemWindow(node, self._animctrl_animctrl, False)
459  self._animctrl_animctrl.Play()
460  # Make visible the running or paused test
461  self.EnsureVisible(node.GetParent())
462  self.EnsureVisible(node)
463 
464  def _get_icon_index_for(self, controller):
465  if not self._execution_results_execution_results:
466  return ROBOT_IMAGE_INDEX
467  if self._execution_results_execution_results.is_paused(controller):
468  return PAUSED_IMAGE_INDEX
469  if self._execution_results_execution_results.is_running(controller):
470  return RUNNING_IMAGE_INDEX
471  if self._execution_results_execution_results.has_passed(controller):
472  return PASSED_IMAGE_INDEX
473  if self._execution_results_execution_results.has_failed(controller):
474  return FAILED_IMAGE_INDEX
475  if self._execution_results_execution_results.has_skipped(controller):
476  return SKIPPED_IMAGE_INDEX
477  return ROBOT_IMAGE_INDEX
478 
479  def populate(self, model):
480  self._clear_tree_data_clear_tree_data()
481  self._populate_model_populate_model(model)
482  self._refresh_view_refresh_view()
483  self.SetFocus() # Needed for keyboard shortcuts
484 
485  def _clear_tree_data(self):
486  self.DeleteAllItems()
487  self._root_root = self.AddRoot('')
488  self._resource_root_resource_root = self._create_resource_root_create_resource_root()
489  self._datafile_nodes_datafile_nodes = []
490  self._resources_resources = []
491  self._controller_controller.clear_history()
492 
494  return self._create_node_create_node(self._root_root, self._RESOURCES_NODE_LABEL_RESOURCES_NODE_LABEL,
495  self._images_images.directory)
496 
497  def _populate_model(self, model):
498  handler = ResourceRootHandler(model, self, self._resource_root_resource_root,
499  self._controller_controller.settings)
500  # print(f"DEBUG: _populate_model model={model} self._resource_root={self._resource_root}"
501  # f" self._controller.settings={self._controller.settings}")
502  self.SetPyData(self._resource_root_resource_root, handler)
503  if model.data:
504  self._render_datafile_render_datafile(self._root_root, model.data, 0)
505  for res in model.external_resources:
506  if not res.parent:
507  self._render_datafile_render_datafile(self._resource_root_resource_root, res)
508 
509  def _resource_added(self, message):
510  ctrl = message.datafile
511  if self._controller_controller.find_node_by_controller(ctrl):
512  return
513 
514  parent = None
515  if ctrl.parent:
516  parent = self._get_dir_node_get_dir_node(ctrl.parent)
517  else:
518  parent = self._resource_root_resource_root
519  self._render_datafile_render_datafile(parent, ctrl)
520 
521  def _get_dir_node(self, ctrl):
522  if ctrl is None:
523  return self._root_root
524  dir_node = self._get_datafile_node_get_datafile_node(ctrl.data)
525  if dir_node is None:
526  parent = self._get_dir_node_get_dir_node(ctrl.parent)
527  self._render_datafile_render_datafile(parent, ctrl)
528  dir_node = self._get_datafile_node_get_datafile_node(ctrl.data)
529  return dir_node
530 
531  def _select_resource(self, message):
532  self.select_controller_nodeselect_controller_node(message.item)
533 
534  def select_controller_node(self, controller):
535  self.SelectItem(self._controller_controller.find_node_by_controller(controller))
536 
537  def _suite_added(self, message):
538  self.add_datafileadd_datafile(message.parent, message.suite)
539 
540  def _refresh_view(self):
541  self.Show()
542  self.Refresh()
543  # print(f"DEBUG: Called Tree._refresh_view {self.GetParent().GetClassName()}")
544  if self._resource_root_resource_root:
545  self.Expand(self._resource_root_resource_root)
546  if self._datafile_nodes_datafile_nodes:
547  self._expand_and_render_children_expand_and_render_children(self._datafile_nodes_datafile_nodes[0])
548  wx.CallAfter(self.SelectItem, self._datafile_nodes_datafile_nodes[0])
549  self.Update()
550  # print(f"DEBUG: Called Tree._refresh_view parent={self.GetParent().GetClassName()} self={self}")
551 
552  def _render_datafile(self, parent_node, controller, index=None):
553  node = self._create_node_with_handler_create_node_with_handler(parent_node, controller, index)
554  if not node:
555  return None
556  if controller.dirty:
557  self._controller_controller.mark_node_dirty(node)
558  self._datafile_nodes_datafile_nodes.append(node)
559  self.SetItemHasChildren(node, True)
560 
561  for child in controller.children:
562  self._render_datafile_render_datafile(node, child)
563  return node
564 
565  def _normalize(self, path):
566  return os.path.normcase(os.path.normpath(os.path.abspath(path)))
567 
568  def _create_node_with_handler(self, parent_node, controller, index=None):
569  from ..controller.filecontrollers import ResourceFileController
570  if controller.display_name.startswith("#"): # If it is a comment don't create
571  return None
572 
573  if IS_WINDOWS and isinstance(controller, ResourceFileController):
574  resourcefile = self._normalize_normalize(controller.filename)
575  pname = parent_node.GetText()
576  self._resources_resources.append((pname,resourcefile))
577  if IS_WINDOWS:
578  count = 0
579  for (p, r) in self._resources_resources:
580  if (p, r) == (pname, resourcefile):
581  count += 1
582  if count > 3:
583  return None
584  handler_class = action_handler_class(controller)
585  with_checkbox = (handler_class == TestCaseHandler and self._checkboxes_for_tests_checkboxes_for_tests)
586  node = self._create_node_create_node(parent_node, controller.display_name, self._images_images[controller],
587  index, with_checkbox=with_checkbox)
588  if isinstance(controller, ResourceFileController) and not controller.is_used():
589  self.SetItemTextColour(node, TREETEXTCOLOUR) # wxPython3 hack
590  action_handler = handler_class(controller, self, node, self._controller_controller.settings)
591  self.SetPyData(node, action_handler)
592 
593  # if we have a TestCase node we have to make sure that
594  # we retain the checked state
595  if (handler_class == TestCaseHandler and self._checkboxes_for_tests_checkboxes_for_tests) \
596  and self._test_selection_controller_test_selection_controller.is_test_selected(controller):
597  self.CheckItem(node, True)
598  if controller.is_excluded():
599  self._set_item_excluded_set_item_excluded(node)
600  return node
601 
603  self._checkboxes_for_tests_checkboxes_for_tests = True
604 
606  assert node is not None
607  self._render_children_render_children(node)
608  self.Expand(node)
609 
610  def _render_children(self, node):
611  handler = self._controller_controller.get_handler(node)
612  if not handler or not handler.can_be_rendered:
613  return
614  self._create_child_nodes_create_child_nodes(
615  node, handler, lambda item: item.is_test_suite)
616  handler.set_rendered()
617 
618  def _create_child_nodes(self, node, handler, predicate):
619  for childitem in self._children_of_children_of(handler):
620  index = self._get_insertion_index_get_insertion_index(node, predicate)
621  self._create_node_with_handler_create_node_with_handler(node, childitem, index)
622 
623  def _children_of(self, handler):
624  return [v for v in handler.variables if v.has_data()] + \
625  list(handler.tests) + list(handler.keywords)
626 
627  def _create_node(self, parent_node, label, img, index=None, with_checkbox=False):
628  node = self._wx_node_wx_node(parent_node, index, label, with_checkbox)
629  self.SetItemImage(node, img.normal, wx.TreeItemIcon_Normal)
630  self.SetItemImage(node, img.expanded, wx.TreeItemIcon_Expanded)
631  return node
632 
633  def _wx_node(self, parent_node, index, label, with_checkbox):
634  ct_type = 1 if with_checkbox else 0
635  if index is not None:
636  # blame wxPython for this ugliness
637  if isinstance(index, int):
638  return self.InsertItemByIndex(
639  parent_node, index, label, ct_type=ct_type)
640  else:
641  return self.InsertItem(
642  parent_node, index, label, ct_type=ct_type)
643  return self.AppendItem(parent_node, label, ct_type=ct_type)
644 
645  def add_datafile(self, parent, suite):
646  snode = self._render_datafile_render_datafile(
647  self._get_datafile_node_get_datafile_node(parent.data), suite)
648  self.SelectItem(snode)
649 
650  def add_test(self, parent_node, test):
651  self._add_dataitem_add_dataitem(
652  parent_node, test, lambda item: item.is_user_keyword)
653 
654  def add_keyword(self, parent_node, kw):
655  self._add_dataitem_add_dataitem(parent_node, kw, lambda item: item.is_test_suite)
656 
657  def _add_dataitem(self, parent_node, dataitem, predicate):
658  node = self._get_or_create_node_get_or_create_node(parent_node, dataitem, predicate)
659  self._select_select(node)
660  self._controller_controller.mark_node_dirty(parent_node)
661 
662  def _get_or_create_node(self, parent_node, dataitem, predicate):
663  if not self.IsExpanded(parent_node):
664  self._expand_and_render_children_expand_and_render_children(parent_node)
665  return self._controller_controller.find_node_with_label(
666  parent_node, dataitem.display_name)
667 
668  index = self._get_insertion_index_get_insertion_index(parent_node, predicate)
669  return self._create_node_with_handler_create_node_with_handler(parent_node, dataitem, index)
670 
671  def _select(self, node):
672  if node:
673  wx.CallAfter(self.SelectItem, node)
674 
675  def _get_insertion_index(self, parent_node, predicate):
676  if not predicate:
677  return None
678  item, cookie = self.GetFirstChild(parent_node)
679  while item:
680  if predicate(self._controller_controller.get_handler(item)):
681  index = self.GetPrevSibling(item)
682  if not index:
683  index = 0
684  return index
685  item, cookie = self.GetNextChild(parent_node, cookie)
686  return None
687 
688  def _keyword_added(self, message):
689  self.add_keywordadd_keyword(self._get_datafile_node_get_datafile_node(self.get_selected_datafileget_selected_datafile()),
690  message.item)
691 
692  def _variable_added(self, message):
693  self._get_or_create_node_get_or_create_node(
694  self._get_datafile_node_get_datafile_node(self.get_selected_datafileget_selected_datafile()),
695  message.item,
696  lambda item: not item.is_variable or item.index > message.index)
697 
698  def _leaf_item_removed(self, message):
699  node = self._controller_controller.find_node_by_controller(message.item)
700  parent_node = self._get_datafile_node_get_datafile_node(message.datafile)
701  # DEBUG The below call causes not calling delete_node
702  # self._test_selection_controller.select(message.item, False)
703  self._controller_controller.mark_node_dirty(parent_node)
704  self.delete_nodedelete_node(node)
705 
706  def _test_added(self, message):
707  self.add_testadd_test(self._get_datafile_node_get_datafile_node(self.get_selected_datafileget_selected_datafile()),
708  message.item)
709 
710  def _datafile_removed(self, message):
711  dfnode = self._get_datafile_node_get_datafile_node(message.datafile.data)
712  self._datafile_nodes_datafile_nodes.remove(dfnode)
713  self.DeleteChildren(dfnode)
714  self.Delete(dfnode)
715 
716  def _datafile_set(self, message):
717  wx.CallAfter(self._refresh_datafile_when_file_set_refresh_datafile_when_file_set, message.item)
718 
719  def _filename_changed(self, message):
720  df = message.datafile
721  node = self._controller_controller.find_node_by_controller(df)
722  if not node:
723  raise AssertionError('No node found with controller "%s"' % df)
724  wx.CallAfter(self.SetItemText, node, df.display_name)
725 
726  def add_keyword_controller(self, controller):
727  parent = self._get_datafile_node_get_datafile_node(self.get_selected_datafileget_selected_datafile())
728  self.add_keywordadd_keyword(parent, controller)
729 
730  def delete_node(self, node):
731  if node is None:
732  return
733  parent = self.GetItemParent(node)
734  self._controller_controller.mark_node_dirty(parent)
735  if self.IsSelected(node):
736  self.Delete(node)
737  wx.CallAfter(self.SelectItem, parent)
738 
739  def _data_dirty(self, message):
740  self._controller_controller.mark_controller_dirty(message.datafile)
741 
742  def _data_undirty(self, message):
743  # print(f"DEBUG: treeplugin _data_undirty calling unset_dirty")
744  self.unset_dirtyunset_dirty()
745 
746  def unset_dirty(self):
747  # print(f"DEBUG:treeplugin unset_dirty ENTER")
748  for node in self._datafile_nodes_datafile_nodes:
749  text = self.GetItemText(node)
750  handler = self._controller_controller.get_handler(node)
751  if text.startswith('*') and not handler.controller.dirty:
752  self.SetItemText(node, text[1:])
753 
754 
758  def select_node_by_data(self, controller):
759  parent_node = self._get_datafile_node_get_datafile_node(controller.datafile)
760  if not parent_node:
761  return None
762  if not self.IsExpanded(parent_node):
763  self._expand_and_render_children_expand_and_render_children(parent_node)
764  node = self._controller_controller.find_node_by_controller(controller)
765  if node != self.GetSelection():
766  self.SelectItem(node)
767  return node
768 
770  parent_node = self._get_datafile_node_get_datafile_node(uk.parent.parent)
771  if not parent_node:
772  return
773  if not self.IsExpanded(parent_node):
774  self._expand_and_render_children_expand_and_render_children(parent_node)
775  node = self._controller_controller.find_node_with_label(
776  parent_node, utils.normalize(uk.name))
777  if node != self.GetSelection():
778  self.SelectItem(node)
779 
780  def _get_datafile_node(self, datafile):
781  for node in self._datafile_nodes_datafile_nodes:
782  if self._controller_controller.get_handler(node).item == datafile:
783  return node
784  return None
785 
786 
791  datafile = self._get_selected_datafile_node_get_selected_datafile_node()
792  if not datafile:
793  return None
794  return self._controller_controller.get_handler(datafile).item
795 
796 
801  dfnode = self._get_selected_datafile_node_get_selected_datafile_node()
802 
803  if dfnode:
804  return self._controller_controller.get_handler(dfnode).controller
805  else:
806  return None
807 
809  node = self.GetSelection()
810  if not node or node in (self._resource_root_resource_root, self._root_root):
811  return None
812  while node not in self._datafile_nodes_datafile_nodes:
813  node = self.GetItemParent(node)
814  return node
815 
816 
818  def get_selected_item(self):
819  selection = self.GetSelection()
820  if not selection:
821  return None
822  handler = self._controller_controller.get_handler(selection)
823  return handler and handler.controller or None
824 
825  def tree_node_selected(self, node):
826  # print(f"DEBUG: TreePlugin tree node selected {str(node)}")
827  pass
828 
829  def move_up(self, node):
830  prev = self.GetPrevSibling(node)
831  if prev.IsOk():
832  self._switch_items(prev, node, node)
833 
834  def move_down(self, node):
835  next = self.GetNextSibling(node)
836  if next.IsOk():
837  self._switch_items_switch_items(node, next, node)
838 
839 
843  def _switch_items(self, first, second, currently_selected):
844  selection = self.GetItemData(currently_selected).controller
845  controller = self._controller_controller.get_handler(first).controller
846  self.Delete(first)
847  self._create_node_with_handler_create_node_with_handler(self.GetItemParent(second), controller, second)
848  self.select_node_by_dataselect_node_by_data(selection)
849 
850  def _refresh_datafile_when_file_set(self, controller):
851  # remove invalid cases selection when data file is changed in text editor
852  self._test_selection_controller_test_selection_controller.remove_invalid_cases_selection(controller)
853  # Prevent tab selections based on tree item selected events
854  self._start_silent_mode_start_silent_mode()
855  current = self.get_selected_datafile_controllerget_selected_datafile_controller()
856  if not current: # If tree is not yet in use - do not expand anything.
857  self._end_silent_mode_end_silent_mode()
858  return
859 
860  item = self.GetSelection()
861  current_txt = self.GetItemText(item) if item.IsOk() else ''
862  # after refresh current and current_txt might have been changed
863  node = self._refresh_datafile_refresh_datafile(controller)
864  if node is None:
865  # TODO: Find out why this sometimes happens
866  return
867  self._expand_and_render_children_expand_and_render_children(node)
868  if current == controller:
869  select_item = self._controller_controller.find_node_with_label(
870  node, current_txt)
871  if select_item is None:
872  select_item = node
873  wx.CallAfter(self.SelectItem, select_item)
874  wx.CallAfter(self._end_silent_mode_end_silent_mode)
875  else:
876  self._end_silent_mode_end_silent_mode()
877 
878  def _uncheck_tests(self, controller):
879  self._test_selection_controller_test_selection_controller.unselect_all(controller.tests)
880 
882  self._silent_mode_silent_mode = True
883 
884  def _end_silent_mode(self):
885  self._silent_mode_silent_mode = False
886 
887  def refresh_datafile(self, controller, event):
888  to_be_selected = self._get_pending_selection_get_pending_selection(event)
889  new_node = self._refresh_datafile_refresh_datafile(controller)
890  self._handle_pending_selection_handle_pending_selection(to_be_selected, new_node)
891 
892  def _refresh_datafile(self, controller):
893  orig_node = self._get_data_controller_node_get_data_controller_node(controller)
894  if orig_node is not None:
895  insertion_index = self._get_datafile_index_get_datafile_index(orig_node)
896  parent = self.GetItemParent(orig_node)
897  self._remove_datafile_node_remove_datafile_node(orig_node)
898  return self._render_datafile_render_datafile(parent, controller, insertion_index)
899 
900  def _get_pending_selection(self, event):
901  if hasattr(event, 'Item'):
902  item = event.GetItem()
903  event.Veto()
904  elif hasattr(event, 'Position'):
905  item, flags = self.HitTest(event.Position)
906  if not self._click_on_item_click_on_item(item, flags):
907  return
908  else:
909  return
910  return self.GetItemText(item)
911 
912  def _get_data_controller_node(self, controller):
913  for node in self._datafile_nodes_datafile_nodes:
914  if self.GetItemData(node).controller == controller:
915  return node
916  return None
917 
918  def _click_on_item(self, item, flags):
919  return item is not None and item.IsOk() and \
920  flags & wx.TREE_HITTEST_ONITEM
921 
922  def _get_datafile_index(self, node):
923  insertion_index = self.GetPrevSibling(node)
924  if not insertion_index:
925  insertion_index = 0
926  return insertion_index
927 
928  def _remove_datafile_node(self, node):
929  for child in self.GetItemChildren(node):
930  if child in self._datafile_nodes_datafile_nodes:
931  self._remove_datafile_node_remove_datafile_node(child)
932  self._datafile_nodes_datafile_nodes.remove(node)
933  self.Delete(node)
934 
935  def _handle_pending_selection(self, to_be_selected, parent_node):
936  if to_be_selected:
937  self._expand_and_render_children_expand_and_render_children(parent_node)
938  select_item = self._controller_controller.find_node_with_label(
939  parent_node, to_be_selected)
940  wx.CallAfter(self.SelectItem, select_item)
941 
942  def OnSelChanged(self, event):
943  node = event.GetItem()
944  if not node.IsOk() or self._dragging_dragging:
945  event.Skip()
946  return
947  self._controller_controller.add_to_history(node)
948  handler = self._controller_controller.get_handler(node)
949  if handler and handler.item:
950  self._update_data_file_namespace_update_data_file_namespace(node)
952  node=node,
953  item=handler.controller,
954  silent=self._silent_mode_silent_mode).publish()
955  self.SetFocus()
956 
958  while True:
959  if not node:
960  return
961  handler = self._controller_controller.get_handler(node)
962  if hasattr(handler.controller, 'get_namespace'):
963  data_file_ns = handler.controller.get_namespace()
964  if not data_file_ns:
965  return
966  cur_dir = handler.controller.directory
967  if data_file_ns:
968  data_file_ns.update_cur_dir_global_var(cur_dir)
969  return
970  else:
971  node = node.GetParent()
972 
973  def OnTreeItemExpanding(self, event):
974  node = event.GetItem()
975  if node.IsOk():
976  self._render_children_render_children(node)
977 
978  # This exists because CustomTreeItem does not remove animations
979  def OnTreeItemCollapsing(self, event):
980  item = event.GetItem()
981  self._hide_item_hide_item(item)
982  event.Skip()
983 
984  def _hide_item(self, item):
985  for item in item.GetChildren():
986  itemwindow = item.GetWindow()
987  if itemwindow:
988  itemwindow.Hide()
989  if self.ItemHasChildren(item):
990  self._hide_item_hide_item(item)
991 
992 
998  def SelectAllTests(self, item: GenericTreeItem, selected=True):
999  test_controllers = self.retrieveTestCaseControllersretrieveTestCaseControllers(item)
1000  self._test_selection_controller_test_selection_controller.select_all(test_controllers,selected)
1001 
1002  def retrieveTestCaseControllers(self, item: GenericTreeItem):
1003  from ..controller.filecontrollers import TestDataDirectoryController, TestCaseFileController
1004 
1005  data = item.GetData()
1006  if isinstance(data, TestDataDirectoryHandler):
1007  item_controller: TestDataDirectoryController = data.tests.parent
1008  elif isinstance(data, TestCaseFileHandler):
1009  item_controller: TestCaseFileController = data.controller
1010  else:
1011  raise Exception("Unexpected type of handler: " + str(data))
1012  test_controllers = item_controller.retrieve_test_controllers()
1013  return test_controllers
1014 
1015  def SelectTests(self, tests):
1016  self._test_selection_controller_test_selection_controller.select_all(tests)
1017 
1018  def ExpandAllSubNodes(self, item):
1019  self._expand_or_collapse_nodes_expand_or_collapse_nodes(item, self.Expand)
1020 
1021  def CollapseAllSubNodes(self, item):
1022  self._expand_or_collapse_nodes_expand_or_collapse_nodes(item, self.Collapse)
1023 
1024  def _expand_or_collapse_nodes(self, item, callback):
1025  if not self.HasAGWFlag(customtreectrl.TR_HIDE_ROOT) or \
1026  item != self.GetRootItem():
1027  callback(item)
1028  for child in item.GetChildren():
1029  self._expand_or_collapse_nodes_expand_or_collapse_nodes(child, callback)
1030 
1031  def _for_all_drawn_tests(self, item, func):
1032  if self._is_test_node_is_test_node(item):
1033  func(item)
1034  for child in item.GetChildren():
1035  self._for_all_drawn_tests_for_all_drawn_tests(child, func)
1036 
1037  def _is_test_node(self, node):
1038  return node.GetType() == 1
1039 
1040  def DeselectTests(self, tests):
1041  self._test_selection_controller_test_selection_controller.unselect_all(tests)
1042 
1043  def SelectFailedTests(self, item):
1044  all_controllers = self.retrieveTestCaseControllersretrieveTestCaseControllers(item)
1045  test_controllers = filter(
1046  lambda ctrl: ctrl.run_passed == False,
1047  all_controllers)
1048  self._test_selection_controller_test_selection_controller.unselect_all(all_controllers)
1049  self._test_selection_controller_test_selection_controller.select_all(test_controllers)
1050 
1051  def SelectPassedTests(self, item):
1052  all_controllers = self.retrieveTestCaseControllersretrieveTestCaseControllers(item)
1053  test_controllers = filter(
1054  lambda ctrl: ctrl.run_passed == True,
1055  all_controllers)
1056  self._test_selection_controller_test_selection_controller.unselect_all(all_controllers)
1057  self._test_selection_controller_test_selection_controller.select_all(test_controllers)
1058 
1059  def OnClose(self, event):
1060  print("DEBUG: Tree OnClose hidding")
1061  self.Hide()
1062 
1063  def OnTreeItemChecked(self, event):
1064  node: GenericTreeItem = event.GetItem()
1065  handler: TestCaseHandler = self._controller_controller.get_handler(node=node)
1066  self._test_selection_controller_test_selection_controller.select(
1067  handler.controller, node.IsChecked())
1068 
1069  def OnItemActivated(self, event):
1070  node = event.GetItem()
1071  if self.IsExpanded(node):
1072  self.Collapse(node)
1073  elif self.ItemHasChildren(node):
1074  self._expand_and_render_children_expand_and_render_children(node)
1075 
1076  def OnLeftArrow(self, event):
1077  node = self.GetSelection()
1078  if self.IsExpanded(node):
1079  self.Collapse(node)
1080  else:
1081  event.Skip()
1082 
1083  def OnRightClick(self, event):
1084  if not self._right_click_right_click:
1085  self._right_click_right_click = True
1086  handler = None
1087  # if hasattr(event, 'GetItem'):
1088  # handler = self._controller.get_handler(event.GetItem())
1089  item, pos = self.HitTest(self.ScreenToClient(wx.GetMousePosition()), wx.TREE_HITTEST_ONITEMLABEL)
1090  if item:
1091  # print(f"DEBUG: tree mouse RightClick pos={pos}")
1092  handler = self.GetItemData(item)
1093  if handler:
1094  # if not self.IsExpanded(handler.node):
1095  # self.Expand(handler.node)
1096  handler.show_popup()
1097  self._right_click_right_click = False
1098 
1099  def OnNewTestCase(self, event):
1100  handler = self._controller_controller.get_handler()
1101  if handler:
1102  handler.OnNewTestCase(event)
1103 
1104  def OnDrop(self, target, dragged):
1105  dragged = self._controller_controller.get_handler(dragged)
1106  target = self._controller_controller.get_handler(target)
1107  if target and target.accepts_drag(dragged):
1108  dragged.controller.execute(MoveTo(target.controller))
1109  self.Refresh() # DEBUG Always refresh
1110 
1111  def IsValidDragItem(self, item):
1112  return self._controller_controller.get_handler(item).is_draggable
1113 
1114  def OnMoveUp(self, event):
1115  handler = self._controller_controller.get_handler()
1116  if handler.is_draggable:
1117  handler.OnMoveUp(event)
1118 
1119  def OnMoveDown(self, event):
1120  handler = self._controller_controller.get_handler()
1121  if handler.is_draggable:
1122  handler.OnMoveDown(event)
1123 
1124  def _item_changed(self, message):
1125  controller = message.item
1126  node = self._controller_controller.find_node_by_controller(controller)
1127  if node:
1128  self.SetItemText(node, message.item.name)
1129 
1130  if controller.dirty:
1131  self._controller_controller.mark_node_dirty(
1132  self._get_datafile_node_get_datafile_node(controller.datafile))
1133 
1134  def _variable_moved_up(self, message):
1135  if self._should_update_variable_positions_should_update_variable_positions(message):
1136  self._do_action_if_datafile_node_is_expanded_do_action_if_datafile_node_is_expanded(self.move_upmove_up, message)
1137 
1138  def _variable_moved_down(self, message):
1139  if self._should_update_variable_positions_should_update_variable_positions(message):
1140  self._do_action_if_datafile_node_is_expanded_do_action_if_datafile_node_is_expanded(self.move_downmove_down, message)
1141 
1143  return message.item != message.other and message.item.has_data() and \
1144  message.other.has_data()
1145 
1147  if self.IsExpanded(self._get_datafile_node_get_datafile_node(data.item.datafile)):
1148  node = self._controller_controller.find_node_by_controller(data.item)
1149  action(node)
1150 
1151  def _variable_updated(self, message):
1152  self._item_changed_item_changed(message)
1153 
1154  def highlight(self, data, text):
1155  self.select_node_by_dataselect_node_by_data(data)
1156  self._editor_editor.highlight(text)
1157 
1158  def node_is_resource_file(self, node):
1159  return self._controller_controller.get_handler(node).__class__ == \
1160  ResourceFileHandler
1161 
1162 
1164 
1165  def __init__(self, tree, action_registerer):
1166  self._tree_tree = tree
1167  tree.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, self.OnBeginLabelEditOnBeginLabelEdit)
1168  tree.Bind(wx.EVT_TREE_END_LABEL_EDIT, self.OnLabelEditedOnLabelEdited)
1169  tree.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDownOnLeftDown)
1170  if IS_WINDOWS:
1171  # Delete key does not work in windows without registration
1172  delete_key_action = ActionInfo(
1173  None, None, action=self.OnDeleteOnDelete, shortcut='Del')
1174  action_registerer.register_shortcut(delete_key_action)
1175  self._editing_label_editing_label = False
1176  self._on_label_edit_called_on_label_edit_called = False
1177 
1178  def OnBeginLabelEdit(self, event):
1179  # See http://code.google.com/p/robotframework-ride/issues/detail?id=756
1180  self._editing_label_editing_label = True
1181  if not self._on_label_edit_called_on_label_edit_called:
1182  self.OnLabelEditOnLabelEdit()
1183  event.Veto()
1184  # On windows CustomTreeCtrl will create Editor component
1185  # And we want this to be done by the handler -- as it knows if
1186  # there should be one or not. And because this will make it work
1187  # the same way as when pressing F2 .. so in other words there is
1188  # a bug if we don't Veto this event
1189 
1190  def OnLabelEdit(self, event=None):
1191  if not self._on_label_edit_called_on_label_edit_called:
1192  self._on_label_edit_called_on_label_edit_called = True
1193  handler = self._tree_tree._controller.get_handler()
1194  if handler and not handler.begin_label_edit():
1195  self._on_label_edit_called_on_label_edit_called = False
1196  self._editing_label_editing_label = False
1197 
1198  def OnLabelEdited(self, event):
1199  self._editing_label_editing_label = False
1200  self._on_label_edit_called_on_label_edit_called = False
1201  self._tree_tree._controller.get_handler(event.GetItem()) \
1202  .end_label_edit(event)
1203 
1204  # Reset edit control as it doesn't seem to reset it in case the focus
1205  # goes directly away from the tree control
1206  # Use CallAfter to prevent messing up the current end label edit
1207  # .. and the another CallAfter because of
1208  # customtreectrl.TreeTextCtrl#OnChar will call CallAfter(self.Finish)
1209  # when Enter is pressed --> Results in PyDeadObject if called after
1210  # ResetEditControl..
1211  wx.CallAfter(wx.CallAfter, self._stop_editing_stop_editing)
1212 
1213  def _stop_editing(self):
1214  control = self._tree_tree.GetEditControl()
1215  if control and wx.Window.FindFocus():
1216  control.StopEditing()
1217 
1218  def OnDelete(self, event):
1219  editor = self._tree_tree.GetEditControl()
1220  if editor and wx.Window.FindFocus() == editor:
1221  start, end = editor.GetSelection()
1222  editor.Remove(start, max(end, start + 1))
1223 
1224  def OnLeftDown(self, event):
1225  # See http://code.google.com/p/robotframework-ride/issues/detail?id=756
1226  if IS_WINDOWS and self._editing_label_editing_label:
1227  # This method works only on Windows, luckily the issue 756 exists
1228  # only on Windows
1229  self._tree_tree.OnCancelEdit(self._tree_tree.GetSelection())
1230  event.Skip()
1231 
1232  def _get_handler(self, item=None):
1233  return self._tree_tree._get_handler(item)
Used to create menu entries, keyboard shortcuts and/or toolbar buttons.
Definition: actioninfo.py:176
Entry point to RIDE plugin API – all plugins must extend this class.
Definition: plugin.py:50
def get_selected_datafile(self)
Returns the data file that is currently selected in the tree.
Definition: plugin.py:286
def subscribe(self, listener, *topics)
Start to listen to messages with the given topics.
Definition: plugin.py:396
def unsubscribe_all(self)
Stops to listen to all messages this plugin has subscribed to.
Definition: plugin.py:411
def unregister_actions(self)
Unregisters all actions registered by this plugin.
Definition: plugin.py:230
def save_setting(self, name, value, override=True, delay=0)
Saves the specified setting into the RIDE configuration file.
Definition: plugin.py:148
def __init__(self, tree, action_registerer)
Definition: treeplugin.py:1165
Provides a tree view for Test Suites.
Definition: treeplugin.py:66
def register_frame(self, parent=None)
Definition: treeplugin.py:99
def _update_tree(self, event=None)
Definition: treeplugin.py:217
def __init__(self, application)
Definition: treeplugin.py:72
def disable(self)
Called by RIDE when the plugin is disabled.
Definition: treeplugin.py:144
def OnTreeSelection(self, message)
Definition: treeplugin.py:208
def enable(self)
self.register_action(ActionInfo('View','View Test Suites Explorer', self.OnShowTree,...
Definition: treeplugin.py:123
def _get_icon_index_for(self, controller)
Definition: treeplugin.py:464
def _refresh_datafile(self, controller)
Definition: treeplugin.py:892
def _switch_items(self, first, second, currently_selected)
Changes the order of given items, first is expected to be directly above the second.
Definition: treeplugin.py:843
def get_selected_datafile_controller(self)
Returns controller associated with currently active data file.
Definition: treeplugin.py:800
def _create_test_selection_controller(self)
Definition: treeplugin.py:274
def OnMoveUp(self, event)
Definition: treeplugin.py:1114
def _variable_updated(self, message)
Definition: treeplugin.py:1151
def OnLeftArrow(self, event)
Definition: treeplugin.py:1076
def OnSelChanged(self, event)
Definition: treeplugin.py:942
def add_keyword_controller(self, controller)
Definition: treeplugin.py:726
def _children_of(self, handler)
Definition: treeplugin.py:623
def _uncheck_tests(self, controller)
Definition: treeplugin.py:878
def _resource_added(self, message)
Definition: treeplugin.py:509
def _wx_node(self, parent_node, index, label, with_checkbox)
Definition: treeplugin.py:633
def _get_pending_selection(self, event)
Definition: treeplugin.py:900
def _get_or_create_node(self, parent_node, dataitem, predicate)
Definition: treeplugin.py:662
def OnMoveDown(self, event)
Definition: treeplugin.py:1119
def _variable_moved_up(self, message)
Definition: treeplugin.py:1134
def OnItemActivated(self, event)
Definition: treeplugin.py:1069
def _handle_import_setting_message(self, message)
Definition: treeplugin.py:376
def OnTreeItemCollapsing(self, event)
Definition: treeplugin.py:979
def _get_datafile_index(self, node)
Definition: treeplugin.py:922
def _set_icon_from_execution_results(self, controller)
Definition: treeplugin.py:426
def get_selected_item(self)
Returns model object associated with currently selected tree node.
Definition: treeplugin.py:818
def __init__(self, parent, action_registerer, settings=None)
Definition: treeplugin.py:233
def delete_node(self, node)
Definition: treeplugin.py:730
def _test_result(self, message)
Definition: treeplugin.py:403
def ExpandAllSubNodes(self, item)
Definition: treeplugin.py:1018
def OnRightClick(self, event)
Definition: treeplugin.py:1083
def _item_changed(self, message)
Definition: treeplugin.py:1124
def _handle_pending_selection(self, to_be_selected, parent_node)
Definition: treeplugin.py:935
def _create_child_nodes(self, node, handler, predicate)
Definition: treeplugin.py:618
def _get_insertion_index(self, parent_node, predicate)
Definition: treeplugin.py:675
def register_context_menu_hook(self, callable)
Definition: treeplugin.py:326
def _get_resource_text_color(self, resource_controller)
Definition: treeplugin.py:391
def _populate_model(self, model)
Definition: treeplugin.py:497
def _render_datafile(self, parent_node, controller, index=None)
Definition: treeplugin.py:552
def _is_test_node(self, node)
Definition: treeplugin.py:1037
def OnSelection(self, event)
Definition: treeplugin.py:304
def OnTreeItemExpanding(self, event)
Definition: treeplugin.py:973
def _remove_datafile_node(self, node)
Definition: treeplugin.py:928
def _hide_item(self, item)
Definition: treeplugin.py:984
def _should_update_variable_positions(self, message)
Definition: treeplugin.py:1142
def _data_undirty(self, message)
Definition: treeplugin.py:742
def _set_item_excluded(self, node)
Definition: treeplugin.py:371
def _data_dirty(self, message)
Definition: treeplugin.py:739
def node_is_resource_file(self, node)
Definition: treeplugin.py:1158
def select_user_keyword_node(self, uk)
Definition: treeplugin.py:769
def select_controller_node(self, controller)
Definition: treeplugin.py:534
def _suite_added(self, message)
Definition: treeplugin.py:537
def _create_node(self, parent_node, label, img, index=None, with_checkbox=False)
Definition: treeplugin.py:627
def populate(self, model)
Definition: treeplugin.py:479
def set_editor(self, editor)
Definition: treeplugin.py:315
def OnTreeItemChecked(self, event)
Definition: treeplugin.py:1063
def _datafile_set(self, message)
Definition: treeplugin.py:716
def OnEndDrag(self, event)
Definition: treeplugin.py:322
def _click_on_item(self, item, flags)
Definition: treeplugin.py:918
def _expand_or_collapse_nodes(self, item, callback)
Definition: treeplugin.py:1024
def _filename_changed(self, message)
Definition: treeplugin.py:719
def OnNewTestCase(self, event)
Definition: treeplugin.py:1099
def DeselectTests(self, tests)
Definition: treeplugin.py:1040
def move_down(self, node)
Definition: treeplugin.py:834
def _variable_added(self, message)
Definition: treeplugin.py:692
def _test_added(self, message)
Definition: treeplugin.py:706
def _leaf_item_removed(self, message)
Definition: treeplugin.py:698
def OnDrop(self, target, dragged)
Definition: treeplugin.py:1104
def SelectFailedTests(self, item)
Definition: treeplugin.py:1043
def add_keyword(self, parent_node, kw)
Definition: treeplugin.py:654
def CollapseAllSubNodes(self, item)
Definition: treeplugin.py:1021
def _update_data_file_namespace(self, node)
Definition: treeplugin.py:957
def _on_cancel_edit(self, item)
Definition: treeplugin.py:282
def unregister_context_menu_hook(self, callable)
Definition: treeplugin.py:329
def IsValidDragItem(self, item)
Definition: treeplugin.py:1111
def refresh_datafile(self, controller, event)
Definition: treeplugin.py:887
def _create_node_with_handler(self, parent_node, controller, index=None)
Definition: treeplugin.py:568
def _select_resource(self, message)
Definition: treeplugin.py:531
def _get_selected_datafile_node(self)
Definition: treeplugin.py:808
def _normalize(self, path)
Definition: treeplugin.py:565
def set_checkboxes_for_tests(self)
Definition: treeplugin.py:602
def _set_resource_color(self, resource_controller)
Definition: treeplugin.py:383
def SelectAllTests(self, GenericTreeItem item, selected=True)
Select tests for execution :param item: The node of the graphical tree where the user triggered the a...
Definition: treeplugin.py:998
def _mark_excludes(self, message)
Definition: treeplugin.py:366
def _refresh_datafile_when_file_set(self, controller)
Definition: treeplugin.py:850
def OnClose(self, event)
Definition: treeplugin.py:1059
def _datafile_removed(self, message)
Definition: treeplugin.py:710
def _select(self, node)
Definition: treeplugin.py:671
def move_up(self, node)
Definition: treeplugin.py:829
def get_selected_datafile(self)
Returns currently selected data file.
Definition: treeplugin.py:790
def _get_dir_node(self, ctrl)
Definition: treeplugin.py:521
def select_node_by_data(self, controller)
Find and select the tree item associated with the given controller.
Definition: treeplugin.py:758
def OnDoubleClick(self, event)
Definition: treeplugin.py:308
def highlight(self, data, text)
Definition: treeplugin.py:1154
def _add_dataitem(self, parent_node, dataitem, predicate)
Definition: treeplugin.py:657
def tree_node_selected(self, node)
Definition: treeplugin.py:825
def _for_all_drawn_tests(self, item, func)
Definition: treeplugin.py:1031
def _keyword_added(self, message)
Definition: treeplugin.py:688
def _variable_moved_down(self, message)
Definition: treeplugin.py:1138
def _expand_and_render_children(self, node)
Definition: treeplugin.py:605
def _testing_started(self, message)
Definition: treeplugin.py:397
def SelectPassedTests(self, item)
Definition: treeplugin.py:1051
def add_datafile(self, parent, suite)
Definition: treeplugin.py:645
def SelectTests(self, tests)
Definition: treeplugin.py:1015
def _do_action_if_datafile_node_is_expanded(self, action, data)
Definition: treeplugin.py:1146
def retrieveTestCaseControllers(self, GenericTreeItem item)
Definition: treeplugin.py:1002
def _render_children(self, node)
Definition: treeplugin.py:610
def add_test(self, parent_node, test)
Definition: treeplugin.py:650
def _get_datafile_node(self, datafile)
Definition: treeplugin.py:780
def _get_data_controller_node(self, controller)
Definition: treeplugin.py:912
def action_handler_class(controller)