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

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 

16from functools import (total_ordering, cmp_to_key) 

17import builtins 

18import wx 

19from wx import Colour 

20 

21from ..widgets import (RIDEDialog, VerticalSizer, VirtualList, Label, 

22 HelpLabel, ImageProvider) 

23from ..widgets.list import ListModel 

24 

25_ = wx.GetTranslation # To keep linter/code analyser happy 

26builtins.__dict__['_'] = wx.GetTranslation 

27 

28 

29class TestsDialog(RIDEDialog): 

30 

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) 

44 

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 

52 

53 def select_page(self, page=0): 

54 self._notebook.ChangeSelection(page) 

55 

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 

69 

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 

93 

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 

104 

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 

114 

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 

125 

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 

132 

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

137 

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 

144 

145 def on_add_to_selected(self, event): 

146 __ = event 

147 self._add_to_selected_handler(self._get_current_tests()) 

148 

149 def on_search_tests(self, event): 

150 __ = event 

151 self._fuzzy_search_handler(self._search_control.GetValue()) 

152 

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) 

159 

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) 

167 

168 @staticmethod 

169 def _refresh_list(llist): 

170 llist.refresh_items() 

171 llist.Refresh() 

172 if llist.GetItemCount(): 

173 llist.inform_listeners(0) 

174 

175 def _add_info_text(self, panel, text=""): 

176 infopanel = self._horizontal_sizer() 

177 infopanel.Add(HelpLabel(panel, _("Info. ") + text)) 

178 return infopanel 

179 

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

198 

199 @staticmethod 

200 def _horizontal_sizer(): 

201 return wx.BoxSizer(wx.HORIZONTAL) 

202 

203 @staticmethod 

204 def _vertical_sizer(): 

205 return wx.BoxSizer(wx.VERTICAL) 

206 

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) 

215 

216 def wrapped(self, event): 

217 __ = event 

218 return self._fuzzy_search_handler(self._search_control.GetValue()) 

219 

220 def _select_text_search_result(self, idx): 

221 if idx != -1: 

222 for listener in self._selection_listeners: 

223 listener(self.tests[idx]) 

224 

225 def _select_tag_search_result(self, idx): 

226 for listener in self._selection_listeners: 

227 listener(self._tags_results[idx]) 

228 

229 def add_selection_listener(self, listener): 

230 self._selection_listeners.append(listener) 

231 

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 

242 

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) 

248 

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] 

254 

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

261 

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

268 

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

272 

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) 

277 

278 

279@total_ordering 

280class _TestSearchListModel(ListModel): 

281 

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

284 

285 @property 

286 def count(self): 

287 return len(self.sorted_tests) 

288 

289 def __getitem__(self, item): 

290 return self.sorted_tests[item] 

291 

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 

299 

300 @staticmethod 

301 def m_cmp(a, b): 

302 return (a > b) - (a < b) 

303 

304 def __eq__(self, other): 

305 return self.__class__.__name__.lower() == other.name.lower() 

306 

307 def __hash__(self): 

308 return hash(repr(self)) 

309 

310 def __lt__(self, other): 

311 return self.__class__.__name__.lower() < other.name.lower()