Coverage for src/robotide/searchtests/dialogsearchtests.py: 21%
238 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-06 10:40 +0100
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-06 10:40 +0100
1# Copyright 2008-2015 Nokia Networks
2# Copyright 2016- Robot Framework Foundation
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
16from functools import (total_ordering, cmp_to_key)
17import builtins
18import wx
19from wx import Colour
21from ..widgets import (RIDEDialog, VerticalSizer, VirtualList, Label,
22 HelpLabel, ImageProvider)
23from ..widgets.list import ListModel
25_ = wx.GetTranslation # To keep linter/code analyser happy
26builtins.__dict__['_'] = wx.GetTranslation
29class TestsDialog(RIDEDialog):
31 def __init__(self, fuzzy_search_handler, tag_search_handler, add_to_selected_handler):
32 self._fuzzy_search_handler = fuzzy_search_handler
33 self._tag_search_handler = tag_search_handler
34 self._add_to_selected_handler = add_to_selected_handler
35 self._selection_listeners = []
36 title = _("Search Tests")
37 RIDEDialog.__init__(self, title=title, size=(800, 400))
38 # set Left to Right direction (while we don't have localization)
39 self.SetLayoutDirection(wx.Layout_LeftToRight)
40 self.SetBackgroundColour(Colour(self.color_background))
41 self.SetForegroundColour(Colour(self.color_foreground))
42 self.SetSizer(VerticalSizer())
43 self.Sizer.Add(self._create_notebook(), 1, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 3)
45 def _create_notebook(self):
46 self._notebook = wx.Notebook(self, wx.ID_ANY, style=wx.NB_TOP)
47 self._notebook.SetBackgroundColour(Colour(self.color_background))
48 self._notebook.SetForegroundColour(Colour(self.color_foreground))
49 self._notebook.AddPage(self._text_search_panel(), _('Search'))
50 self._notebook.AddPage(self._tag_pattern_search_panel(), _('Tag Search'))
51 return self._notebook
53 def select_page(self, page=0):
54 self._notebook.ChangeSelection(page)
56 def _text_search_panel(self):
57 panel = wx.Panel(self._notebook)
58 panel.SetSizer(VerticalSizer())
59 self._add_search_control(panel)
60 self.tests = _TestSearchListModel([])
61 self.tests_list = VirtualList(panel, [_('Test'), _('Tags'), _('Source')], self.tests)
62 self.tests_list.SetBackgroundColour(Colour(self.color_secondary_background))
63 self.tests_list.SetForegroundColour(Colour(self.color_secondary_foreground))
64 self.tests_list.add_selection_listener(self._select_text_search_result)
65 panel.Sizer.add_expanding(self.tests_list)
66 self._fuzzy_results_text = wx.StaticText(panel, -1, _('Results: '))
67 panel.Sizer.Add(self._fuzzy_results_text, 0, wx.ALL, 3)
68 return panel
70 def _tag_pattern_search_panel(self):
71 panel = wx.Panel(self._notebook)
72 panel.SetSizer(VerticalSizer())
73 tags_controls_sizer = VerticalSizer()
74 tags_controls_sizer.Add(self._create_include_line(panel), 0, wx.ALL, 3)
75 tags_controls_sizer.Add(self._create_exclude_line(panel), 0, wx.ALL, 3)
76 controls_sizer = self._horizontal_sizer()
77 controls_sizer.Add(tags_controls_sizer)
78 controls_sizer.Add(self._create_switch_button(panel), 0, wx.CENTER, 3)
79 controls_sizer.Add(self._create_tag_search_button(panel), 0, wx.ALL | wx.EXPAND, 3)
80 controls_sizer.Add(self._create_add_to_selected_button(panel), 0, wx.ALL | wx.EXPAND, 3)
81 panel.Sizer.Add(controls_sizer)
82 panel.Sizer.Add(self._add_info_text(panel, _("Find matches using tag patterns. See RF User "
83 "Guide or 'robot --help' for more information.")), 0, wx.ALL, 3)
84 self._tags_results = _TestSearchListModel([])
85 self._tags_list = VirtualList(panel, [_('Test'), _('Tags'), _('Source')], self._tags_results)
86 self._tags_list.SetBackgroundColour(Colour(self.color_secondary_background))
87 self._tags_list.SetForegroundColour(Colour(self.color_secondary_foreground))
88 self._tags_list.add_selection_listener(self._select_tag_search_result)
89 panel.Sizer.add_expanding(self._tags_list)
90 self._tags_results_text = wx.StaticText(panel, -1, _('Results: '))
91 panel.Sizer.Add(self._tags_results_text, 0, wx.ALL, 3)
92 return panel
94 def _create_include_line(self, panel):
95 include_line = self._horizontal_sizer()
96 include_line.Add(Label(panel, label=_('Include'), size=(80, -1)))
97 self._tags_to_include_text = wx.TextCtrl(panel, value='', size=(400, -1),
98 style=wx.TE_PROCESS_ENTER | wx.TE_NOHIDESEL)
99 self._tags_to_include_text.SetBackgroundColour(Colour(self.color_secondary_background))
100 self._tags_to_include_text.SetForegroundColour(Colour(self.color_secondary_foreground))
101 self._tags_to_include_text.Bind(wx.EVT_TEXT_ENTER, self.on_search_tags)
102 include_line.Add(self._tags_to_include_text)
103 return include_line
105 def _create_switch_button(self, panel):
106 sizer = self._vertical_sizer()
107 img = ImageProvider().SWITCH_FIELDS_ICON
108 button = wx.BitmapButton(panel, -1, img, pos=(10, 20))
109 button.SetBackgroundColour(Colour(self.color_secondary_background))
110 button.SetForegroundColour(Colour(self.color_secondary_foreground))
111 self.Bind(wx.EVT_BUTTON, self.on_switch_fields, button)
112 sizer.Add(button)
113 return sizer
115 def _create_exclude_line(self, panel):
116 exclude_line = self._horizontal_sizer()
117 exclude_line.Add(Label(panel, label=_('Exclude'), size=(80, -1)))
118 self._tags_to_exclude_text = wx.TextCtrl(panel, value='', size=(400, -1),
119 style=wx.TE_PROCESS_ENTER | wx.TE_NOHIDESEL)
120 self._tags_to_exclude_text.SetBackgroundColour(Colour(self.color_secondary_background))
121 self._tags_to_exclude_text.SetForegroundColour(Colour(self.color_secondary_foreground))
122 self._tags_to_exclude_text.Bind(wx.EVT_TEXT_ENTER, self.on_search_tags)
123 exclude_line.Add(self._tags_to_exclude_text)
124 return exclude_line
126 def _create_tag_search_button(self, panel):
127 button = wx.Button(panel, label=_('Search'))
128 button.SetBackgroundColour(Colour(self.color_secondary_background))
129 button.SetForegroundColour(Colour(self.color_secondary_foreground))
130 button.Bind(wx.EVT_BUTTON, self.on_search_tags)
131 return button
133 def on_search_tags(self, event):
134 __ = event
135 self._tag_search_handler(self._tags_to_include_text.GetValue(),
136 self._tags_to_exclude_text.GetValue())
138 def _create_add_to_selected_button(self, panel):
139 button = wx.Button(panel, label=_('Add all to selected'))
140 button.SetBackgroundColour(Colour(self.color_secondary_background))
141 button.SetForegroundColour(Colour(self.color_secondary_foreground))
142 button.Bind(wx.EVT_BUTTON, self.on_add_to_selected)
143 return button
145 def on_add_to_selected(self, event):
146 __ = event
147 self._add_to_selected_handler(self._get_current_tests())
149 def on_search_tests(self, event):
150 __ = event
151 self._fuzzy_search_handler(self._search_control.GetValue())
153 def set_search_model(self, search_text, results):
154 results = list(results)
155 self._search_control.SetValue(search_text)
156 self._fuzzy_results_text.SetLabel(_('Results: %d') % len(results))
157 self.tests.sorted_tests = results
158 self._refresh_list(self.tests_list)
160 def set_tag_search_model(self, include_text, exclude_text, results):
161 results = list(results)
162 self._tags_to_include_text.SetValue(include_text)
163 self._tags_to_exclude_text.SetValue(exclude_text)
164 self._tags_results_text.SetLabel(_('Results: %d') % len(results))
165 self._tags_results.sorted_tests = results
166 self._refresh_list(self._tags_list)
168 @staticmethod
169 def _refresh_list(llist):
170 llist.refresh_items()
171 llist.Refresh()
172 if llist.GetItemCount():
173 llist.inform_listeners(0)
175 def _add_info_text(self, panel, text=""):
176 infopanel = self._horizontal_sizer()
177 infopanel.Add(HelpLabel(panel, _("Info. ") + text))
178 return infopanel
180 def _add_search_control(self, panel):
181 panel.SetSizer(VerticalSizer())
182 line1 = self._horizontal_sizer()
183 self._add_pattern_filter(line1, panel)
184 fuzzy_search_button = wx.Button(panel, label=_('Search'))
185 fuzzy_search_button.SetBackgroundColour(Colour(self.color_secondary_background))
186 fuzzy_search_button.SetForegroundColour(Colour(self.color_secondary_foreground))
187 fuzzy_search_button.Bind(wx.EVT_BUTTON, self.on_search_tests)
188 line1.Add(fuzzy_search_button, 0, wx.ALL | wx.EXPAND, 3)
189 add_to_selection_button = wx.Button(panel, label=_('Add all to selected'))
190 add_to_selection_button.SetBackgroundColour(Colour(self.color_secondary_background))
191 add_to_selection_button.SetForegroundColour(Colour(self.color_secondary_foreground))
192 add_to_selection_button.Bind(wx.EVT_BUTTON, self.on_add_to_selected)
193 line1.Add(add_to_selection_button, 0, wx.ALL | wx.EXPAND, 3)
194 panel.Sizer.Add(line1, 0, wx.ALL, 3)
195 panel.Sizer.Add(self._add_info_text(panel, _("Find matches by test name, documentation and/or tag.")),
196 0, wx.ALL, 3)
197 panel.Sizer.Layout()
199 @staticmethod
200 def _horizontal_sizer():
201 return wx.BoxSizer(wx.HORIZONTAL)
203 @staticmethod
204 def _vertical_sizer():
205 return wx.BoxSizer(wx.VERTICAL)
207 def _add_pattern_filter(self, sizer, parent):
208 self._search_control = wx.SearchCtrl(parent, value='', size=(200, -1),
209 style=wx.TE_PROCESS_ENTER)
210 self._search_control.SetBackgroundColour(Colour(self.color_secondary_background))
211 self._search_control.SetForegroundColour(Colour(self.color_secondary_foreground))
212 self._search_control.SetDescriptiveText(_('Search term'))
213 self._search_control.Bind(wx.EVT_TEXT_ENTER, self.wrapped)
214 sizer.Add(self._search_control, 0, wx.ALL, 3)
216 def wrapped(self, event):
217 __ = event
218 return self._fuzzy_search_handler(self._search_control.GetValue())
220 def _select_text_search_result(self, idx):
221 if idx != -1:
222 for listener in self._selection_listeners:
223 listener(self.tests[idx])
225 def _select_tag_search_result(self, idx):
226 for listener in self._selection_listeners:
227 listener(self._tags_results[idx])
229 def add_selection_listener(self, listener):
230 self._selection_listeners.append(listener)
232 @staticmethod
233 def _find_index_in_list(item, llist):
234 if item is None:
235 return 0
236 idx = 0
237 for tc in llist:
238 if tc[0] == item:
239 return idx
240 idx += 1
241 return 0
243 def set_focus_to_default_location(self, selected=None):
244 if self._notebook.GetSelection() == 0:
245 self._set_focus_to_default_location_in_text_search(selected)
246 else:
247 self._set_focus_to_default_location_in_tag_search(selected)
249 def _get_current_tests(self):
250 if self._notebook.GetSelection() == 0:
251 return [t for t, _ in self.tests.sorted_tests]
252 else:
253 return [test for test, _ in self._tags_results.sorted_tests]
255 def _set_focus_to_default_location_in_text_search(self, selected):
256 if self.tests.count:
257 self.tests_list.Select(self._find_index_in_list(selected, self.tests))
258 self.tests_list.SetFocus()
259 else:
260 self._search_control.SetFocus()
262 def _set_focus_to_default_location_in_tag_search(self, selected):
263 if self._tags_results.count:
264 self._tags_list.Select(self._find_index_in_list(selected, self._tags_results))
265 self._tags_list.SetFocus()
266 else:
267 self._tags_to_include_text.SetFocus()
269 def on_switch_fields(self, event):
270 include_txt = self._tags_to_include_text.GetValue()
271 exclude_txt = self._tags_to_exclude_text.GetValue()
273 if len(include_txt.strip()) > 0 or len(exclude_txt.strip()) > 0:
274 self._tags_to_include_text.SetValue(exclude_txt)
275 self._tags_to_exclude_text.SetValue(include_txt)
276 self.on_search_tags(event)
279@total_ordering
280class _TestSearchListModel(ListModel):
282 def __init__(self, tests):
283 self.sorted_tests = sorted(tests, key=cmp_to_key(lambda x, y: self.m_cmp(x[1], y[1])))
285 @property
286 def count(self):
287 return len(self.sorted_tests)
289 def __getitem__(self, item):
290 return self.sorted_tests[item]
292 def item_text(self, row, col):
293 test, _ = self.sorted_tests[row]
294 if col == 0:
295 return test.name
296 if col == 1:
297 return u', '.join(str(t) for t in test.tags)
298 return test.datafile_controller.longname
300 @staticmethod
301 def m_cmp(a, b):
302 return (a > b) - (a < b)
304 def __eq__(self, other):
305 return self.__class__.__name__.lower() == other.name.lower()
307 def __hash__(self):
308 return hash(repr(self))
310 def __lt__(self, other):
311 return self.__class__.__name__.lower() < other.name.lower()