Robot Framework Integrated Development Environment (RIDE)
tagdialogs.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 re
17 
18 import wx
19 import wx.lib.mixins.listctrl as listmix
20 from wx import Colour
21 
22 from .. import utils
23 from ..controller.ctrlcommands import ChangeTag
24 from ..publish import RideOpenTagSearch
25 from ..widgets import ButtonWithHandler, PopupMenuItems, RIDEDialog
26 
27 
28 class ViewAllTagsDialog(RIDEDialog, listmix.ColumnSorterMixin):
29 
30  def __init__(self, controller, frame):
31  style = wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN | \
32  wx.FRAME_FLOAT_ON_PARENT
33  RIDEDialog.__init__(self, parent=frame, title="View all tags", size=(500, 400), style=style)
34  self.SetBackgroundColour(Colour(self.color_background))
35  self.SetForegroundColour(Colour(self.color_foreground))
36  # set Left to Right direction (while we don't have localization)
37  self.SetLayoutDirection(wx.Layout_LeftToRight)
38  self.frameframe = frame
39  self.treetree = self.frameframe.tree
40  self._controller_controller = controller
41  self._results_results = utils.NormalizedDict()
42  self.selected_testsselected_tests = list()
43  self.tagged_test_casestagged_test_cases = list()
44  self.unique_tagsunique_tags = 0
45  self.total_test_casestotal_test_cases = 0
46  self.itemDataMapitemDataMap = dict()
47  self.sort_statesort_state = (0, 1)
48  self._index_index = -1
49  self._build_ui_build_ui()
50  self._make_bindings_make_bindings()
51 
52  # init ColumnSorterMixin at the end because it calls self.GetListCtrl
53  # and therefore self._tags_list has to be declared
54  listmix.ColumnSorterMixin.__init__(self, 2)
55 
56 
64  def _build_ui(self):
65  # self.SetSize((500, 400))
66  # parent_x, parent_y = self.frame.GetPosition()
67  # parent_size_x, parent_size_y = self.frame.tree.GetSize()
68  # self.SetPosition((parent_x + parent_size_x + 50, parent_y + 50))
69  # self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE))
70  self.SetSizer(wx.BoxSizer(wx.VERTICAL))
71  self._build_notebook_build_notebook()
72  self._build_tag_lister_build_tag_lister()
73  self._build_controls_build_controls()
74  self._build_footer_build_footer()
75  self.CenterOnParent()
76 
77  def _build_tag_lister(self):
78  panel_tag_vw = wx.Panel(self._notebook_notebook)
79  panel_tag_vw.SetBackgroundColour(Colour(self.color_background))
80  panel_tag_vw.SetForegroundColour(Colour(self.color_foreground))
81  sizer_tag_vw = wx.BoxSizer(wx.VERTICAL)
82  panel_tag_vw.SetSizer(sizer_tag_vw)
83  self._tags_list_tags_list = TagsListCtrl(panel_tag_vw, style=wx.LC_REPORT, colorBG=self.color_secondary_background,
84  colorFG=self.color_secondary_foreground)
85  self._tags_list_tags_list.InsertColumn(0, "Tag", width=200)
86  self._tags_list_tags_list.InsertColumn(1, "Occurrences", width=25,
87  format=wx.LIST_FORMAT_CENTER)
88  self._tags_list_tags_list.SetMinSize((450, 250))
89  self._tags_list_tags_list.set_dialog(self)
90  sizer_tag_vw.Add(self._tags_list_tags_list, 1, wx.ALL | wx.EXPAND, 3)
91  self._notebook_notebook.AddPage(panel_tag_vw, "The List")
92 
93  def _build_controls(self):
94  self._clear_button_clear_button = ButtonWithHandler(self, 'Refresh', self.OnClearOnClear)
95  self._show_tagged_tests_button_show_tagged_tests_button = ButtonWithHandler(
96  self, 'Included Tag Search')
97  self._show_excluded_tests_button_show_excluded_tests_button = ButtonWithHandler(
98  self, 'Excluded Tag Search')
99  self._clear_button_clear_button.SetBackgroundColour(Colour(self.color_secondary_background))
100  self._clear_button_clear_button.SetForegroundColour(Colour(self.color_secondary_foreground))
101  self._show_tagged_tests_button_show_tagged_tests_button.SetBackgroundColour(Colour(self.color_secondary_background))
102  self._show_tagged_tests_button_show_tagged_tests_button.SetForegroundColour(Colour(self.color_secondary_foreground))
103  self._show_excluded_tests_button_show_excluded_tests_button.SetBackgroundColour(Colour(self.color_secondary_background))
104  self._show_excluded_tests_button_show_excluded_tests_button.SetForegroundColour(Colour(self.color_secondary_foreground))
105  controls = wx.BoxSizer(wx.HORIZONTAL)
106  controls.Add(self._show_tagged_tests_button_show_tagged_tests_button, 0, wx.ALL, 3)
107  controls.Add(self._show_excluded_tests_button_show_excluded_tests_button, 0, wx.ALL, 3)
108  controls.Add(self._clear_button_clear_button, 0, wx.ALL, 3)
109  self.Sizer.Add(controls, 0, wx.ALL | wx.EXPAND, 3)
110 
111  def _build_footer(self):
112  footer = wx.BoxSizer(wx.HORIZONTAL)
113  self._footer_text_footer_text = wx.StaticText(self, -1, '')
114  footer.Add(self._footer_text_footer_text)
115  self.Sizer.Add(footer, 0, wx.ALL, 3)
116 
117  def _build_notebook(self):
118  self._notebook_notebook = wx.Notebook(self, wx.ID_ANY, style=wx.NB_TOP)
119  self._notebook_notebook.SetBackgroundColour(Colour(self.color_background))
120  self._notebook_notebook.SetForegroundColour(Colour(self.color_foreground))
121  self.Sizer.Add(self._notebook_notebook, 1, wx.ALL | wx.EXPAND, 3)
122 
123  def _make_bindings(self):
124  self.Bind(wx.EVT_CLOSE, self._close_dialog_close_dialog)
125  self._tags_list_tags_list.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnRightClickOnRightClick)
126  self._tags_list_tags_list.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClickOnColClick)
127  self._tags_list_tags_list.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSelectItemOnSelectItem)
128 
129  def _tag_name_for_sort(self, tag_name):
130  return [part if index % 2 == 0 else int(part) for index, part in
131  enumerate(re.split(r'(\d+)', tag_name.lower()))]
132 
133  def _execute(self):
134  self._clear_search_results_clear_search_results()
135  self._search_for_tags_search_for_tags()
136  self.tagged_test_casestagged_test_cases = list()
137  self.unique_tagsunique_tags = 0
138 
139  for tag_name, tests in self._results_results:
140  model_entry = self._tags_list_tags_list.add_tag((tests, tag_name))
141  self.tagged_test_casestagged_test_cases += tests
142  # Mapping the lists model entry with the model for sorting.
143  self.itemDataMapitemDataMap[model_entry] = \
144  (self._tag_name_for_sort_tag_name_for_sort(tag_name), len(tests))
145  self.unique_tagsunique_tags += 1
146  self._tags_list_tags_list.SetColumnWidth(1, wx.LIST_AUTOSIZE_USEHEADER)
147  self._tags_list_tags_list.setResizeColumn(1)
148  self.tagged_test_casestagged_test_cases = list(set(self.tagged_test_casestagged_test_cases))
149  self.update_footerupdate_footer()
150  self.SortListItems(self.sort_statesort_state[0], self.sort_statesort_state[1])
151 
152  def update_footer(self):
153  footer_string = ("Total tests %d, Tests with tags %d, Unique tags %d\n"
154  "Currently selected tests %d") % \
155  (self.total_test_casestotal_test_cases, len(self.tagged_test_casestagged_test_cases),
156  self.unique_tagsunique_tags, len(self.selected_testsselected_tests))
157  self._footer_text_footer_text.SetLabel(footer_string)
158 
159  def show_dialog(self):
160  self._execute_execute()
161  if not self.IsShown():
162  self.Show()
163  self.Raise()
164 
166  self.selected_testsselected_tests = list()
167  self._tags_list_tags_list.ClearAll()
168 
170  pass
171 
172  def _search_for_tags(self):
173  unique_tags = utils.NormalizedDict()
175  self._test_cases_test_cases = []
176  for test in self.frameframe._controller.all_testcases():
177  self._test_cases_test_cases.append(test)
178  for tag in test.tags:
179  if tag.is_empty() or len(str(tag).strip()) == 0:
180  continue
181  else:
182  tag_name = str(tag)
183  if tag_name in unique_tags:
184  unique_tags[tag_name].append(test)
185  self._tags_tags[tag_name].append(tag)
186  else:
187  unique_tags[tag_name] = [test]
188  self._tags_tags[tag_name] = [tag]
189 
190  isreversed = (self.sort_statesort_state[1] != 1)
191  self.total_test_casestotal_test_cases = len(self._test_cases_test_cases)
192 
193  self._results_results = sorted(unique_tags.items(),
194  key=lambda item: item[0].lower(),
195  reverse=isreversed)
196 
197  def GetListCtrl(self):
198  return self._tags_list_tags_list
199 
200  def OnColClick(self, event):
201  self.sort_statesort_state = self.GetSortState()
202  event.Skip()
203 
205  tags = []
206  for _, tag_name in self._tags_list_tags_list.get_checked_items():
207  tags.append(tag_name)
208  return tags
209 
210  def OnIncludedTagSearch(self, event):
211  included_tags = self._add_checked_tags_into_list_add_checked_tags_into_list()
212  RideOpenTagSearch(includes=' '.join(included_tags),
213  excludes='').publish()
214 
215  def OnExcludedTagSearch(self, event):
216  excluded_tags = self._add_checked_tags_into_list_add_checked_tags_into_list()
217  RideOpenTagSearch(includes='',
218  excludes=' '.join(excluded_tags)).publish()
219 
220  def OnClear(self, event):
221  self._execute_execute()
222  for _, tests in self._results_results:
223  self.treetree.DeselectTests(tests)
224  self.update_footerupdate_footer()
225 
226  def OnSelectAll(self, event):
227  all_tests = []
228  for _, tests in self._results_results:
229  all_tests += tests
230  self.treetree.SelectTests(all_tests)
231  self._tags_list_tags_list.CheckAll()
232 
233  def OnRightClick(self, event):
234  self._index_index = event.GetIndex()
235  menu_items = ["Select all", "Clear", "---", "Rename", "Delete", "---",
236  "Show tests with this tag",
237  "Show tests without this tag"]
238  self.treetree._popup_creator.show(self, PopupMenuItems(self, menu_items),
239  self._controller_controller)
240 
241  def OnSelectItem(self, event):
242  self._index_index = event.GetIndex()
243  self._tags_list_tags_list.CheckItem(self._index_index, not self._tags_list_tags_list.IsChecked(self._index_index))
244 
245  def OnShowTestsWithThisTag(self, event):
246  if self._index_index == -1:
247  return
248  _, tag_name = self._tags_list_tags_list.get_tag(self._index_index)
249  RideOpenTagSearch(includes=tag_name, excludes="").publish()
250 
251  def OnShowTestsWithoutThisTag(self, event):
252  if self._index_index == -1:
253  return
254  _, tag_name = self._tags_list_tags_list.get_tag(self._index_index)
255  RideOpenTagSearch(includes="", excludes=tag_name).publish()
256 
257  def OnRename(self, event):
258  if self._index_index == -1:
259  return
260  tests, tag_name = self._tags_list_tags_list.get_tag(self._index_index)
261  tags_to_rename = self._tags_tags[tag_name.lower()]
262  name = wx.GetTextFromUser(message="Renaming tag '%s'." % tag_name, default_value=tag_name,
263  caption='Rename').strip()
264  if name:
265  for tag in tags_to_rename:
266  tag.controller.execute(ChangeTag(tag, name))
267  self._execute_execute()
268  for tag_name, tests in self._results_results:
269  self.treetree.DeselectTests(tests)
270 
271  def OnDelete(self, event):
272  if self._index_index == -1:
273  return
274  tests, tag_name = self._tags_list_tags_list.get_tag(self._index_index)
275  tags_to_delete = self._tags_tags[tag_name.lower()]
276  if wx.MessageBox(
277  "Delete a tag '%s' ?" % tag_name, caption='Confirm',
278  style=wx.YES_NO | wx.ICON_QUESTION) == wx.YES:
279  for tag in tags_to_delete:
280  tag.controller.execute(ChangeTag(tag, ''))
281  self._execute_execute()
282  for tag_name, tests in self._results_results:
283  self.treetree.DeselectTests(tests)
284 
285  def _close_dialog(self, event):
286  if event.CanVeto():
287  self.Hide()
288  else:
289  self.Destroy()
290 
291  def item_in_kw_list_checked(self, index, flag):
292  self.selected_testsselected_tests = list()
293  if flag is False:
294  tests, _ = self._tags_list_tags_list.get_tag(index)
295  self.treetree.DeselectTests(tests)
296  if self._tags_list_tags_list.get_number_of_checked_items() > 0:
297  for tests, _ in self._tags_list_tags_list.get_checked_items():
298  self.selected_testsselected_tests += tests
299  self.treetree.SelectTests(tests)
300  self.selected_testsselected_tests = list(set(self.selected_testsselected_tests))
301  self.update_footerupdate_footer()
302 
303 
304 class TagsListCtrl(wx.ListCtrl, listmix.CheckListCtrlMixin,
305  listmix.ListCtrlAutoWidthMixin):
306  def __init__(self, parent, style, colorBG, colorFG):
307  self.parentparent = parent
308  wx.ListCtrl.__init__(self, parent=parent, style=style)
309  if wx.VERSION < (4, 1, 0):
310  listmix.CheckListCtrlMixin.__init__(self)
311  listmix.ListCtrlAutoWidthMixin.__init__(self)
312  if wx.VERSION >= (4, 1, 0):
313  # print(f"DEBUG: CheckAll tags")
314  self.EnableCheckBoxes(True)
315  self.SetBackgroundColour(Colour(colorBG))
316  self.SetForegroundColour(Colour(colorFG))
317  self.setResizeColumn(2)
318  self._clientData_clientData = {}
319 
320  def OnCheckItem(self, index, flag):
321  if self._dlg_dlg:
322  self._dlg_dlg.item_in_kw_list_checked(index, flag)
323 
324  def get_checked_items(self):
325  items = []
326  for i in range(self.GetItemCount()):
327  if self.IsChecked(i):
328  items.append(self.get_tagget_tag(i))
329  return items
330 
332  total = 0
333  for i in range(self.GetItemCount()):
334  if self.IsChecked(i):
335  total += 1
336  return total
337 
338  def set_dialog(self, dialog):
339  self._dlg_dlg = dialog
340 
341 
358  def add_tag(self, tag_to_tests):
359 
360  model_index = wx.NewIdRef()
361  position = self.GetItemCount()
362  self._clientData_clientData[model_index] = tag_to_tests
363  self.InsertItem(position, str(tag_to_tests[1]))
364  self.SetItem(position, 1, str(len(tag_to_tests[0])))
365  # associate the model with the GUI
366  self.SetItemData(position, model_index)
367  return model_index
368 
369 
378  def get_tag(self, index):
379 
380  model_index = self.GetItemData(index)
381  return self._clientData_clientData.get(model_index, None)
382 
383 
388  def remove_tag(self, index):
389 
390  model_index = self.GetItemData(index)
391  self.DeleteItem(model_index)
392  del self._clientData_clientData[model_index]
393 
394  def ClearAll(self):
395  self.DeleteAllItems()
396  self._clientData_clientData.clear()
397 
398  def CheckAll(self):
399  for i in range(self.GetItemCount()):
400  if wx.VERSION >= (4, 1, 0):
401  # print(f"DEBUG: CheckAll tags")
402  self.CheckItem(i, True)
403  else:
404  self.CheckItem(i)
Custom dictionary implementation automatically normalizing keys.
Definition: normalizing.py:58
Sent we when want to open Search Tags)
Definition: messages.py:474
def __init__(self, parent, style, colorBG, colorFG)
Definition: tagdialogs.py:306
def OnCheckItem(self, index, flag)
Definition: tagdialogs.py:320
def remove_tag(self, index)
Remove a tag based on its position in the list.
Definition: tagdialogs.py:388
def add_tag(self, tag_to_tests)
Append a tag with associated tests to the list.
Definition: tagdialogs.py:358
def get_tag(self, index)
Get a tag with associated test from the list based on the position in the list.
Definition: tagdialogs.py:378
def item_in_kw_list_checked(self, index, flag)
Definition: tagdialogs.py:291
def __init__(self, controller, frame)
Definition: tagdialogs.py:30