Coverage for src/robotide/editor/editors.py: 38%
313 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.
16import builtins 1ab
17import wx 1ab
19from abc import abstractmethod 1ab
20from wx import Colour 1ab
21from .settingeditors import ( 1ab
22 DocumentationEditor, SettingEditor, TagsEditor,
23 ImportSettingListEditor, VariablesListEditor, MetadataListEditor)
24from .. import robotapi, context 1ab
25from ..controller.settingcontrollers import ( 1ab
26 DocumentationController, VariableController, TagsController)
27from ..publish import ( 1ab
28 RideItemSettingsChanged, RideInitFileRemoved, RideFileNameChanged)
29from ..usages.UsageRunner import ResourceFileUsages 1ab
30from ..widgets import ( 1ab
31 ButtonWithHandler, Label, HeaderLabel, HorizontalSizer, HtmlWindow)
33_ = wx.GetTranslation # To keep linter/code analyser happy 1ab
34builtins.__dict__['_'] = wx.GetTranslation 1ab
36LIGHT_GREY = 'light grey' 1ab
39class WelcomePage(HtmlWindow): 1ab
40 undo = cut = copy = paste = delete = comment = uncomment = save \ 1ab
41 = show_content_assist = tree_item_selected = lambda *args: None
43 def __init__(self, parent): 1ab
44 HtmlWindow.__init__(self, parent, text=context.get_about_ride()) 1astuv
46 def close(self): 1ab
47 self.Show(False)
49 def w_destroy(self): 1ab
50 self.close()
51 self.Destroy()
54class EditorPanel(wx.lib.scrolledpanel.ScrolledPanel): 1ab
55 """Base class for all editor panels"""
56 # DEBUG: Move outside default editor package, document
57 name = doc = '' 1ab
58 title = None 1ab
59 undo = cut = copy = paste = delete = comment = uncomment = save \ 1ab
60 = show_content_assist = lambda self: None
62 def __init__(self, plugin, parent, controller, tree): 1ab
63 wx.lib.scrolledpanel.ScrolledPanel.__init__(self, parent) 1aefcdg
64 from ..preferences import RideSettings 1aefcdg
65 _settings = RideSettings() 1aefcdg
66 self.general_settings = _settings['General'] 1aefcdg
67 self.color_background = self.general_settings.get('background', LIGHT_GREY) 1aefcdg
68 self.color_foreground = self.general_settings.get('foreground', 'black') 1aefcdg
69 self.color_secondary_background = self.general_settings.get('secondary background', LIGHT_GREY) 1aefcdg
70 self.color_secondary_foreground = self.general_settings.get('secondary foreground', 'black') 1aefcdg
71 self.color_background_help = self.general_settings.get('background help', (240, 242, 80)) 1aefcdg
72 self.color_foreground_text = self.general_settings.get('foreground text', (7, 0, 70)) 1aefcdg
73 self.font_face = self.general_settings.get('font face', '') 1aefcdg
74 self.font_size = self.general_settings.get('font size', 11) 1aefcdg
75 self.SetBackgroundColour(Colour(self.color_background)) 1aefcdg
76 self.SetForegroundColour(Colour(self.color_foreground)) 1aefcdg
77 self.plugin = plugin 1aefcdg
78 self.controller = controller 1aefcdg
79 try: 1aefcdg
80 self.language = controller.datafile_controller.language 1aefcdg
81 except AttributeError:
82 self.language = ['en']
83 self._tree = tree 1aefcdg
85 def tree_item_selected(self, item): 1ab
86 """ Maybe this needs to be published """
87 pass
90class _RobotTableEditor(EditorPanel): 1ab
91 name = 'table' 1ab
92 doc = 'table editor' 1ab
93 kweditor = None 1ab
94 _settings_open_id = 'robot table settings open' 1ab
96 def __init__(self, plugin, parent, controller, tree): 1ab
97 EditorPanel.__init__(self, plugin, parent, controller, tree) 1aefcdg
98 self.sizer = wx.BoxSizer(wx.VERTICAL) 1aefcdg
99 self.Bind(wx.EVT_IDLE, self.on_idle) 1aefcdg
100 self.SetSizer(self.sizer) 1aefcdg
101 if self.title: 101 ↛ 102line 101 didn't jump to line 102 because the condition on line 101 was never true1aefcdg
102 self.sizer.Add(self._create_header(self.title),
103 0, wx.EXPAND | wx.ALL, 6)
104 self._editors = [] 1aefcdg
105 self._last_shown_tooltip = None 1aefcdg
106 self._reset_last_show_tooltip() 1aefcdg
107 self._populate() 1aefcdg
108 self.plugin.subscribe(self._settings_changed, RideItemSettingsChanged) 1aefcdg
110 @abstractmethod 1ab
111 def _populate(self): 1ab
112 pass
114 def _should_settings_be_open(self): 1ab
115 if self._settings_open_id not in self.plugin.global_settings:
116 return False
117 return self.plugin.global_settings[self._settings_open_id]
119 def _store_settings_open_status(self): 1ab
120 self.plugin.global_settings[self._settings_open_id] = \
121 self._settings.IsExpanded()
123 def _settings_changed(self, message): 1ab
124 if message.item == self.controller:
125 for editor in self._editors:
126 editor.update_value()
128 def on_idle(self, event): 1ab
129 __ = event 1ahijklmnopqr
130 if self._last_shown_tooltip and self._mouse_outside_tooltip(): 130 ↛ 131line 130 didn't jump to line 131 because the condition on line 130 was never true1ahijklmnopqr
131 self._last_shown_tooltip.hide()
132 self._reset_last_show_tooltip()
134 def _mouse_outside_tooltip(self): 1ab
135 mx, my = wx.GetMousePosition()
136 tx, ty = self._last_shown_tooltip.screen_position
137 dx, dy = self._last_shown_tooltip.pw_size
138 return (mx < tx or mx > tx+dx) or (my < ty or my > ty+dy)
140 def tooltip_allowed(self, tooltip): 1ab
141 if wx.GetMouseState().ControlDown() or \
142 self._last_shown_tooltip is tooltip:
143 return False
144 self._last_shown_tooltip = tooltip
145 return True
147 def _reset_last_show_tooltip(self): 1ab
148 self._last_shown_tooltip = None 1aefcdg
150 def close(self): 1ab
151 self.plugin.unsubscribe( 1cd
152 self._settings_changed, RideItemSettingsChanged)
153 self.Unbind(wx.EVT_MOTION) 1cd
154 self.Show(False) 1cd
156 def w_destroy(self): 1ab
157 self.close() 1cd
158 self.DestroyLater() 1cd
160 def _create_header(self, text, readonly=False): 1ab
161 if readonly:
162 text += _(' (READ ONLY)')
163 self._title_display = HeaderLabel(self, text)
164 return self._title_display
166 def _add_settings(self): 1ab
167 self._settings = self._create_settings()
168 self._restore_settings_open_status()
169 self._editors.append(self._settings)
170 self.sizer.Add(self._settings, 0, wx.ALL | wx.EXPAND, 2)
172 def _create_settings(self): 1ab
173 settings = Settings(self)
174 settings.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self._collabsible_changed)
175 settings.build(self.controller.settings, self.plugin, self._tree)
176 return settings
178 def _restore_settings_open_status(self): 1ab
179 if self._should_settings_be_open():
180 self._settings.Expand()
181 wx.CallAfter(self._collabsible_changed)
182 else:
183 self._settings.Collapse()
184 self.GetSizer().Layout()
185 self.Refresh()
186 self.Parent.GetSizer().Layout()
187 self.Parent.Refresh()
189 def _collabsible_changed(self, event=None): 1ab
190 self._store_settings_open_status()
191 self.GetSizer().Layout()
192 self.Refresh()
193 self.Parent.GetSizer().Layout()
194 self.Parent.Refresh()
195 if event:
196 event.Skip()
198 def highlight_cell(self, obj, row, column): 1ab
199 """Highlight the given object at the given row and column"""
200 if isinstance(obj, robotapi.Setting):
201 setting_editor = self._get_settings_editor(obj)
202 if setting_editor and hasattr(setting_editor, "highlight"):
203 setting_editor.highlight(column)
204 elif row >= 0 and column >= 0:
205 self.kweditor.select(row, column)
207 def _get_settings_editor(self, setting): 1ab
208 """Return the settings editor for the given setting object"""
209 for child in self.GetChildren():
210 if isinstance(child, SettingEditor) and child._item == setting:
211 return child
212 return None
214 def highlight(self, text, expand=True): 1ab
215 for editor in self._editors:
216 editor.highlight(text, expand=expand)
219class Settings(wx.CollapsiblePane): 1ab
220 BORDER = 2 1ab
222 def __init__(self, parent): 1ab
223 wx.CollapsiblePane.__init__(
224 self, parent, wx.ID_ANY, _('Settings'),
225 style=wx.CP_DEFAULT_STYLE | wx.CP_NO_TLW_RESIZE)
226 from ..preferences import RideSettings
227 _settings = RideSettings()
228 self.general_settings = _settings['General']
229 self.color_background = self.general_settings.get('background', LIGHT_GREY)
230 self.color_foreground = self.general_settings.get('foreground', 'black')
231 self.color_secondary_background = self.general_settings.get('secondary background', LIGHT_GREY)
232 self.color_secondary_foreground = self.general_settings.get('secondary foreground', 'black')
233 self.color_background_help = self.general_settings.get('background help', (240, 242, 80))
234 self.color_foreground_text = self.general_settings.get('foreground text', (7, 0, 70))
235 self.font_face = self.general_settings.get('font face', '')
236 self.font_size = self.general_settings.get('font size', 11)
237 self.SetBackgroundColour(Colour(self.color_background))
238 self.SetForegroundColour(Colour(self.color_foreground))
239 self._sizer = wx.BoxSizer(wx.VERTICAL)
240 self._editors = []
241 self.Bind(wx.EVT_SIZE, self._recalc_size)
243 def Expand(self): 1ab
244 wx.CollapsiblePane.Expand(self)
246 def GetPane(self): 1ab
247 pane = wx.CollapsiblePane.GetPane(self)
248 """
249 pane.SetBackgroundColour(Colour(200, 222, 40))
250 pane.SetOwnBackgroundColour(Colour(200, 222, 40))
251 pane.SetForegroundColour(Colour(7, 0, 70))
252 pane.SetOwnForegroundColour(Colour(7, 0, 70))
253 """
254 pane.tooltip_allowed = self.GetParent().tooltip_allowed
255 return pane
257 def close(self): 1ab
258 for editor in self._editors:
259 editor.close()
261 def update_value(self): 1ab
262 for editor in self._editors:
263 editor.update_value()
265 def create_editor_for(self, controller, plugin, tree): 1ab
266 editor_cls = self._get_editor_class(controller)
267 return editor_cls(self.GetPane(), controller, plugin, tree)
269 @staticmethod 1ab
270 def _get_editor_class(controller): 1ab
271 if isinstance(controller, DocumentationController):
272 return DocumentationEditor
273 if isinstance(controller, TagsController):
274 return TagsEditor
275 return SettingEditor
277 def build(self, settings, plugin, tree): 1ab
278 for setting in settings:
279 # print(f"DEBUG: editors.py Settings build plugin={plugin.name} setting={setting.label}")
280 editor = self.create_editor_for(setting, plugin, tree)
281 self._sizer.Add(editor, 0, wx.ALL | wx.EXPAND, self.BORDER)
282 self._editors.append(editor)
283 editor.Refresh()
284 self.GetPane().SetSizer(self._sizer)
285 self._sizer.Layout()
287 def _recalc_size(self, event=None): 1ab
288 expand_button_height = 34
289 total_height = 0
290 if self.IsExpanded():
291 for editor in self._editors:
292 total_height += editor.BestSize[1]
293 total_height += 2 * self.BORDER + 1
294 self.SetSizeHints(-1, total_height + expand_button_height)
295 if event:
296 event.Skip()
298 def highlight(self, text, expand=True): 1ab
299 match = False
300 for editor in self._editors:
301 if editor.contains(text):
302 editor.highlight(text)
303 match = True
304 else:
305 editor.clear_highlight()
306 if match and expand:
307 self.Expand()
308 self.Parent.GetSizer().Layout()
311class _FileEditor(_RobotTableEditor): 1ab
313 def __init__(self, *args): 1ab
314 _RobotTableEditor.__init__(self, *args) 1efcdg
315 self.SetupScrolling() 1efcdg
316 self.plugin.subscribe( 1efcdg
317 self._update_source_and_name, RideFileNameChanged)
319 def _update_source(self, message=None): 1ab
320 _ = message
321 self._source.SetValue(self.controller.data.source)
323 def _update_source_and_name(self, message): 1ab
324 _ = message
325 self._title_display.SetLabel(self.controller.name)
326 self._update_source()
328 def tree_item_selected(self, item): 1ab
329 if isinstance(item, VariableController):
330 self._var_editor.select(item.name)
332 def _populate(self): 1ab
333 datafile = self.controller.data
334 header = self._create_header(
335 datafile.name, not self.controller.is_modifiable())
336 self.sizer.Add(header, 0, wx.EXPAND | wx.ALL, 5)
337 self.sizer.Add(self._create_source_label(datafile.source), 0, wx.EXPAND | wx.ALL, 1)
338 self.sizer.Add((0, 10))
339 self._add_settings()
340 self._add_import_settings()
341 self._add_variable_table()
343 def _create_source_label(self, source): 1ab
344 sizer = wx.BoxSizer(wx.HORIZONTAL)
345 sizer.Add((5, 0))
346 sizer.Add(Label(self, label=_('Source'), size=(context.SETTING_LABEL_WIDTH, context.SETTING_ROW_HEIGHT)))
347 self._source = wx.TextCtrl(self, style=wx.TE_READONLY | wx.NO_BORDER)
348 self._source.SetBackgroundColour(Colour(self.color_background))
349 self._source.SetForegroundColour(Colour(self.color_foreground))
351 self._source.SetValue(source)
352 self._source.SetMaxSize(wx.Size(-1, context.SETTING_ROW_HEIGHT))
353 sizer.Add(self._source, 1, wx.EXPAND)
354 return sizer
356 def _add_import_settings(self): 1ab
357 import_editor = ImportSettingListEditor(self, self._tree, self.controller.imports)
358 self.sizer.Add(import_editor, 1, wx.EXPAND)
359 self._editors.append(import_editor)
361 def _add_variable_table(self): 1ab
362 self._var_editor = VariablesListEditor(self, self._tree, self.controller.variables)
363 self.sizer.Add(self._var_editor, 1, wx.EXPAND)
364 self._editors.append(self._var_editor)
366 def close(self): 1ab
367 self.plugin.unsubscribe(self._update_source_and_name, RideFileNameChanged) 1cd
368 for editor in self._editors: 368 ↛ 369line 368 didn't jump to line 369 because the loop on line 368 never started1cd
369 editor.close()
370 self._editors = [] 1cd
371 _RobotTableEditor.close(self) 1cd
373 # Stubs so that ctrl+d ctrl+i don't throw exceptions
374 delete_rows = insert_rows = lambda s: None 1ab
377class FindUsagesHeader(HorizontalSizer): 1ab
379 def __init__(self, parent, header, usages_callback, color_foreground, color_background): 1ab
380 HorizontalSizer.__init__(self)
381 self._header = HeaderLabel(parent, header)
382 self.add_expanding(self._header)
383 self.add_sizer(ButtonWithHandler(parent, _('Find Usages'), handler=usages_callback,
384 color_secondary_foreground=color_foreground,
385 color_secondary_background=color_background))
387 def SetLabel(self, label): 1ab
388 self._header.SetLabel(label)
391class ResourceFileEditor(_FileEditor): 1ab
392 _settings_open_id = 'resource file settings open' 1ab
394 def _create_header(self, text, readonly=False): 1ab
395 if readonly:
396 text += _(' (READ ONLY)')
398 def cb(event):
399 __ = event
400 ResourceFileUsages(self.controller, self._tree.highlight).show()
401 self._title_display = FindUsagesHeader(self, text, cb, color_foreground=self.color_secondary_foreground,
402 color_background=self.color_secondary_background)
403 return self._title_display
406class TestCaseFileEditor(_FileEditor): 1ab
407 __test__ = False 1ab
408 _settings_open_id = 'test case file settings open' 1ab
410 def _populate(self): 1ab
411 _FileEditor._populate(self)
412 self.sizer.Add((0, 10))
413 self._add_metadata()
415 def _add_metadata(self): 1ab
416 metadata_editor = MetadataListEditor(self, self._tree, self.controller.metadata)
417 self.sizer.Add(metadata_editor, 1, wx.EXPAND)
418 self._editors.append(metadata_editor)
421class InitFileEditor(TestCaseFileEditor): 1ab
422 _settings_open_id = 'init file settings open' 1ab
424 def _populate(self): 1ab
425 TestCaseFileEditor._populate(self)
426 self.plugin.subscribe(self._init_file_removed, RideInitFileRemoved)
428 def _init_file_removed(self, message): 1ab
429 _ = message
430 for setting, editor in zip(self.controller.settings, self._editors):
431 editor.refresh_items(setting)
433 def close(self): 1ab
434 self.plugin.unsubscribe(self._init_file_removed, RideInitFileRemoved)
435 super().close()