Coverage for src/robotide/editor/contentassist.py: 39%
491 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 wx 1bc
17from wx import Colour 1bc
18from wx.lib.expando import ExpandoTextCtrl 1bc
19from wx.lib.filebrowsebutton import FileBrowseButton 1bc
20from os.path import relpath, dirname, isdir 1bc
22from .gridbase import GridEditor 1bc
23from .. import context, utils 1bc
24from ..context import IS_MAC, IS_WINDOWS, IS_WX_410_OR_HIGHER 1bc
25from ..namespace.suggesters import SuggestionSource 1bc
26from ..spec.iteminfo import VariableInfo 1bc
27from .popupwindow import RidePopupWindow, HtmlPopupWindow 1bc
28from ..publish import PUBLISHER 1bc
29from ..publish.messages import RideSettingsChanged 1bc
32def obtain_bdd_prefixes(language): 1bc
33 from robotide.lib.compat.parsing.language import Language 1klm
34 lang = Language.from_name(language[0] if isinstance(language, list) else language) 1klm
35 bdd_prefixes = lang.bdd_prefixes 1klm
36 return list(bdd_prefixes) 1klm
39_PREFERRED_POPUP_SIZE = (200, 400) 1bc
40_AUTO_SUGGESTION_CFG_KEY = "enable auto suggestions" 1bc
43class _ContentAssistTextCtrlBase(wx.TextCtrl): 1bc
45 def __init__(self, suggestion_source, language='En', **kw): 1bc
46 super().__init__(**kw) 1daef
47 from ..preferences import RideSettings 1daef
48 _settings = RideSettings() 1daef
49 self.general_settings = _settings['General'] 1daef
50 self.color_background = self.general_settings['background'] 1daef
51 self.color_foreground = self.general_settings['foreground'] 1daef
52 self.color_secondary_background = self.general_settings['secondary background'] 1daef
53 self.color_secondary_foreground = self.general_settings['secondary foreground'] 1daef
54 self.color_background_help = self.general_settings['background help'] 1daef
55 self.color_foreground_text = self.general_settings['foreground text'] 1daef
56 self.language = language 1daef
57 self._popup = ContentAssistPopup(self, suggestion_source) 1daef
58 self.Bind(wx.EVT_KEY_DOWN, self.on_key_down) 1daef
59 self.Bind(wx.EVT_CHAR, self.on_char) 1daef
60 self.Bind(wx.EVT_KILL_FOCUS, self.on_focus_lost) 1daef
61 self.Bind(wx.EVT_MOVE, self.on_focus_lost) 1daef
62 # self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
63 self._showing_content_assist = False 1daef
64 self.Bind(wx.EVT_WINDOW_DESTROY, self.pop_event_handlers) 1daef
65 self._row = None 1daef
66 self._selection = None 1daef
67 self.gherkin_prefix = '' 1daef
68 # Store gherkin prefix from input to add \
69 # later after search is performed
70 if IS_MAC and IS_WX_410_OR_HIGHER: 70 ↛ 72line 70 didn't jump to line 72 because the condition on line 70 was always true1daef
71 self.OSXDisableAllSmartSubstitutions() 1daef
72 self._is_auto_suggestion_enabled = self._get_auto_suggestion_config() 1daef
73 PUBLISHER.subscribe(self.on_settings_changed, RideSettingsChanged) 1daef
75 @staticmethod 1bc
76 def _get_auto_suggestion_config(): 1bc
77 from robotide.context import APP 1daef
78 if not APP: 78 ↛ 80line 78 didn't jump to line 80 because the condition on line 78 was always true1daef
79 return True 1daef
80 settings = APP.settings['Grid']
81 return settings.get(_AUTO_SUGGESTION_CFG_KEY, False)
83 def on_settings_changed(self, message): 1bc
84 """Update auto suggestion settings from PUBLISHER message"""
85 section, setting = message.keys
86 if section == 'Grid' and _AUTO_SUGGESTION_CFG_KEY in setting:
87 self._is_auto_suggestion_enabled = message.new
89 def set_row(self, row): 1bc
90 self._row = row
92 def is_shown(self): 1bc
93 return self._popup.is_shown() 1daef
95 def on_key_down(self, event): 1bc
96 key_code, alt_down = event.GetKeyCode(), event.AltDown()
97 control_down = event.CmdDown() or event.ControlDown()
98 key_char = event.GetUnicodeKey()
99 if key_char not in [ord('['), ord('{'), ord('('), ord("'"), ord('\"'), ord('`')]:
100 self._selection = self.GetStringSelection()
101 # Ctrl-Space handling needed for dialogs # DEBUG add Ctrl-m
102 if (control_down or alt_down) and key_code in [wx.WXK_SPACE, ord('m')]:
103 self.show_content_assist()
104 elif key_code in (wx.WXK_RIGHT, wx.WXK_LEFT): # To skip list and continue editing
105 if self._popup.is_shown():
106 value = self.GetValue()
107 if value:
108 self.SetValue(value)
109 self.SetInsertionPoint(len(value))
110 self._popup.hide()
111 self.reset()
112 else:
113 event.Skip()
114 elif key_code == wx.WXK_RETURN and self._popup.is_shown():
115 self.on_focus_lost(event)
116 elif key_code == wx.WXK_TAB:
117 self.on_focus_lost(event, False)
118 elif key_code == wx.WXK_ESCAPE and self._popup.is_shown():
119 self._popup.hide()
120 elif key_code in [wx.WXK_UP, wx.WXK_DOWN, wx.WXK_PAGEUP, wx.WXK_PAGEDOWN] and self._popup.is_shown():
121 self._popup.select_and_scroll(key_code)
122 elif key_code in (ord('1'), ord('2'), ord('5')) and control_down and not alt_down:
123 self.execute_variable_creator(list_variable=(key_code == ord('2')),
124 dict_variable=(key_code == ord('5')))
125 elif key_code == ord('3') and control_down and event.ShiftDown() and not alt_down:
126 self.execute_sharp_comment()
127 elif key_code == ord('4') and control_down and event.ShiftDown() and not alt_down:
128 self.execute_sharp_uncomment()
129 elif self._popup.is_shown() and key_code < 256:
130 wx.CallAfter(self._populate_content_assist)
131 event.Skip()
132 wx.CallAfter(self._show_auto_suggestions_when_enabled)
133 # Can not catch the following keyEvent from grid cell
134 elif key_code == wx.WXK_RETURN:
135 # fill suggestion in dialogs when pressing enter
136 self.fill_suggestion()
137 event.Skip()
138 # Can not catch the following keyEvent at all
139 # elif key_code == wx.WXK_TAB:
140 # self.fill_suggestion()
141 # elif key_code == wx.WXK_ESCAPE and self.is_shown():
142 # self._popup.hide()
143 else:
144 event.Skip()
146 def _show_auto_suggestions_when_enabled(self): 1bc
147 if self._is_auto_suggestion_enabled or self.is_shown():
148 self.show_content_assist()
150 def on_char(self, event): 1bc
151 key_char = event.GetUnicodeKey()
152 if key_char != wx.WXK_RETURN:
153 self._show_auto_suggestions_when_enabled()
154 if key_char == wx.WXK_NONE:
155 event.Skip()
156 return
157 if key_char in [ord('['), ord('{'), ord('('), ord("'"), ord('\"'), ord('`')]:
158 wx.CallAfter(self.execute_enclose_text, chr(key_char))
159 else:
160 event.Skip()
162 def execute_variable_creator(self, list_variable=False, dict_variable=False): 1bc
163 from_, to_ = self.GetSelection()
164 if list_variable:
165 symbol = '@'
166 elif dict_variable:
167 symbol = '&'
168 else:
169 symbol = '$'
170 self.SetValue(self._variable_creator_value(self.Value, symbol, from_, to_))
171 if from_ == to_:
172 self.SetInsertionPoint(from_ + 2)
173 else:
174 self.SetInsertionPoint(to_ + 3)
175 self.SetSelection(from_ + 2, to_ + 2)
177 @staticmethod 1bc
178 def _variable_creator_value(value, symbol, from_, to_): 1bc
179 return value[:from_] + symbol + '{' + value[from_:to_] + '}' + value[to_:]
181 def execute_enclose_text(self, key_code): 1bc
182 # DEBUG: move this code to kweditor
183 from_, to_ = self.GetSelection()
184 if not self._selection or IS_WINDOWS: # On windows selection is not deleted
185 content = self._enclose_text(self.Value, key_code, from_, to_)
186 else:
187 enclosed = self._enclose_text(self._selection, key_code, 0, len(self._selection))
188 value = self.Value
189 if len(value) <= from_:
190 content = value + enclosed
191 else:
192 content = value[:from_] + enclosed + value[from_:]
193 self.SetValue(content)
194 self._selection = None
195 elem = self
196 if from_ == to_:
197 elem.SetInsertionPoint(from_ + 1)
198 else:
199 elem.SetInsertionPoint(to_ + 2)
200 elem.SetSelection(from_ + 1, to_ + 1)
202 @staticmethod 1bc
203 def _enclose_text(value, open_symbol, from_, to_): 1bc
204 if open_symbol == '[':
205 close_symbol = ']'
206 elif open_symbol == '{':
207 close_symbol = '}'
208 elif open_symbol == '(':
209 close_symbol = ')'
210 else:
211 close_symbol = open_symbol
212 return value[:from_] + open_symbol + value[from_:to_] + close_symbol + value[to_:]
214 def execute_sharp_comment(self): 1bc
215 # DEBUG: Will only comment the left top cell for a multi cell select block!
216 from_, to_ = self.GetSelection()
217 add_text = '# '
218 self.SetValue(self._add_text(self.Value, add_text, True, False, from_, to_))
219 lenadd = len(add_text)
220 elem = self
221 elem.SetInsertionPoint(from_ + lenadd)
222 if from_ != to_:
223 elem.SetInsertionPoint(to_ + lenadd)
224 elem.SetSelection(from_ + lenadd, to_ + lenadd)
226 @staticmethod 1bc
227 def _add_text(value, add_text, on_the_left, on_the_right, from_, to_): 1bc
228 if on_the_left and on_the_right:
229 return value[:from_]+add_text+value[from_:to_]+add_text+value[to_:]
230 if on_the_left:
231 return value[:from_]+add_text+value[from_:to_]+value[to_:]
232 if on_the_right:
233 return value[:from_]+value[from_:to_]+add_text+value[to_:]
234 return value
236 def execute_sharp_uncomment(self): 1bc
237 # DEBUG: Will only uncomment the left top cell for a multi cell select block!
238 from_, to_ = self.GetSelection()
239 lenold = len(self.Value)
240 self.SetValue(self._remove_text(self.Value, '# ', True, False, from_, to_))
241 lenone = len(self.Value)
242 diffone = lenold - lenone
243 elem = self
244 if from_ == to_:
245 elem.SetInsertionPoint(from_ - diffone)
246 else:
247 elem.SetInsertionPoint(to_ - diffone)
248 elem.SetSelection(from_ - diffone, to_ - diffone)
250 @staticmethod 1bc
251 def _remove_text(value, remove_text, on_the_left, on_the_right, from_, to_): 1bc
252 if on_the_left and on_the_right:
253 value = value[:from_]+value[from_:to_].strip(remove_text) + remove_text+value[to_:]
254 elif on_the_left:
255 value = value[:from_]+value[from_:to_].lstrip(remove_text) + value[to_:]
256 elif on_the_right:
257 value = value[:from_]+value[from_:to_].rstrip(remove_text) + value[to_:]
258 value = value.replace('\\ ', ' ')
259 return value
261 def on_focus_lost(self, event, set_value=True): 1bc
262 event.Skip() 1a
263 if not self._popup.is_shown(): 263 ↛ 265line 263 didn't jump to line 265 because the condition on line 263 was always true1a
264 return 1a
265 if self.gherkin_prefix:
266 value = self.gherkin_prefix + self._popup.get_value() or self.GetValue()
267 else:
268 value = self._popup.get_value() or self.GetValue()
269 if set_value and value:
270 self.SetValue(value)
271 self.SetInsertionPoint(len(value)) # DEBUG was self.Value
272 else:
273 self.Clear()
274 self.hide()
276 def fill_suggestion(self): 1bc
277 if self.gherkin_prefix:
278 value = self.gherkin_prefix + self._popup.get_value() or self.GetValue()
279 else:
280 value = self._popup.get_value() or self.GetValue()
281 if value:
282 wrapper_view = self.GetParent().GetParent()
283 if hasattr(wrapper_view, 'open_cell_editor'):
284 # in grid cell, need to make sure cell editor is open
285 wrapper_view.open_cell_editor()
286 self.SetValue(value)
287 self.SetInsertionPoint(len(value))
288 self.hide()
290 def pop_event_handlers(self, event): 1bc
291 __ = event
292 # all pushed eventHandlers need to be popped before close
293 # the last event handler is window object itself - do not pop itself
294 if self:
295 while self.GetEventHandler() is not self:
296 self.PopEventHandler()
298 def on_destroy(self, event): 1bc
299 __ = event
300 # all pushed eventHandlers need to be popped before close
301 # the last event handler is window object itself - do not pop itself
302 while self.GetEventHandler() is not self:
303 self.PopEventHandler()
305 def reset(self): 1bc
306 self._popup.reset()
307 self._showing_content_assist = False
309 def show_content_assist(self): 1bc
310 if self._showing_content_assist:
311 return
312 if self._populate_content_assist():
313 self._showing_content_assist = True
314 self._show_content_assist()
316 def _populate_content_assist(self): 1bc
317 # DEBUG: Get partial content if not found in full
318 value = self.GetValue()
319 (self.gherkin_prefix, value) = self._remove_bdd_prefix(value)
320 return self._popup.content_assist_for(value, row=self._row)
322 def _remove_bdd_prefix(self, name): 1bc
323 bdd_prefix = []
324 if self.language.lower() not in ['en', 'english']:
325 bdd_prefix = [f"{x.lower()} " for x in obtain_bdd_prefixes(self.language)]
326 bdd_prefix += ['given ', 'when ', 'then ', 'and ', 'but ']
327 # print(f"DEBUG: contentassist.py ContentAssistTextCtrlBase _remove_bdd_prefix bdd_prefix={bdd_prefix}")
328 for match in bdd_prefix:
329 if name.lower().startswith(match):
330 return name[:len(match)], name[len(match):]
331 return '', name
333 def _show_content_assist(self): 1bc
334 _, height = self.GetSize()
335 x, y = self.ClientToScreen((0, 0))
336 self._popup.show(x, y, height)
338 def content_assist_value(self): 1bc
339 suggestion = self._popup.content_assist_value(self.Value)
340 if suggestion is None:
341 return suggestion
342 else:
343 return self.gherkin_prefix + suggestion
345 def hide(self): 1bc
346 if not self.is_shown(): 346 ↛ 347line 346 didn't jump to line 347 because the condition on line 346 was never true1daef
347 return
348 self._popup.hide() 1daef
349 self._showing_content_assist = False 1daef
351 def dismiss(self): 1bc
352 if not self.is_shown():
353 return
354 self._popup.dismiss()
357class ExpandingContentAssistTextCtrl(_ContentAssistTextCtrlBase, ExpandoTextCtrl): 1bc
359 def __init__(self, parent, plugin, controller, language='En'): 1bc
360 """ According to class MRO, super().__init__ in _ContentAssistTextCtrlBase will init ExpandoTextCtrl
361 instance """
363 self.suggestion_source = SuggestionSource(plugin, controller) 1d
364 _ContentAssistTextCtrlBase.__init__(self, self.suggestion_source, language=language, 1d
365 parent=parent, size=wx.DefaultSize,
366 style=wx.WANTS_CHARS | wx.TE_NOHIDESEL)
367 self.SetBackgroundColour(context.POPUP_BACKGROUND) 1d
368 # self.SetOwnBackgroundColour(Colour(200, 222, 40))
369 self.SetForegroundColour(context.POPUP_FOREGROUND) 1d
370 # self.SetOwnForegroundColour(Colour(7, 0, 70))
373class ContentAssistTextCtrl(_ContentAssistTextCtrlBase): 1bc
375 def __init__(self, parent, suggestion_source, language='En', size=wx.DefaultSize): 1bc
376 super().__init__(suggestion_source, language=language, parent=parent, 1e
377 size=size, style=wx.WANTS_CHARS | wx.TE_NOHIDESEL)
378 self.SetBackgroundColour(Colour(self.color_background_help)) 1e
379 # self.SetOwnBackgroundColour(Colour(self.color_background_help))
380 self.SetForegroundColour(Colour(self.color_foreground_text)) 1e
381 # self.SetOwnForegroundColour(Colour(self.color_foreground_text))
384class ContentAssistTextEditor(_ContentAssistTextCtrlBase): 1bc
386 def __init__(self, parent, suggestion_source, pos, language='En', size=wx.DefaultSize): 1bc
387 super().__init__(suggestion_source, language=language, 1f
388 parent=parent, id=-1, value="", pos=pos, size=size,
389 style=wx.WANTS_CHARS | wx.BORDER_NONE | wx.WS_EX_TRANSIENT | wx.TE_PROCESS_ENTER |
390 wx.TE_NOHIDESEL)
391 self.SetBackgroundColour(Colour(self.color_background_help)) 1f
392 # self.SetOwnBackgroundColour(Colour(self.color_background_help))
393 self.SetForegroundColour(Colour(self.color_foreground_text)) 1f
394 # self.SetOwnForegroundColour(Colour(self.color_foreground_text))
397class ContentAssistFileButton(FileBrowseButton): 1bc
398 def __init__(self, parent, suggestion_source, label, controller, size=wx.DefaultSize): 1bc
399 self.suggestion_source = suggestion_source 1a
400 FileBrowseButton.__init__(self, parent, labelText=label, 1a
401 size=size, fileMask="*",
402 changeCallback=self.on_file_changed)
403 self._parent = parent 1a
404 self._controller = controller 1a
405 self._browsed = False 1a
407 self.SetBackgroundColour(Colour(context.POPUP_BACKGROUND)) 1a
408 # self.SetOwnBackgroundColour(Colour(context.POPUP_BACKGROUND))
409 self.SetForegroundColour(Colour(context.POPUP_FOREGROUND)) 1a
410 # self.SetOwnForegroundColour(Colour(context.POPUP_FOREGROUND))
412 def Bind(self, *args): 1bc
413 self.textControl.Bind(*args)
415 def createTextControl(self): 1bc
416 """Create the text control"""
417 text_control = _ContentAssistTextCtrlBase(parent=self, id=-1, suggestion_source=self.suggestion_source) 1a
418 text_control.SetToolTip(self.toolTip) 1a
419 if self.changeCallback: 419 ↛ 422line 419 didn't jump to line 422 because the condition on line 419 was always true1a
420 text_control.Bind(wx.EVT_TEXT, self.OnChanged) 1a
421 text_control.Bind(wx.EVT_COMBOBOX, self.OnChanged) 1a
422 return text_control 1a
424 def __getattr__(self, item): 1bc
425 return getattr(self.textControl, item) 1a
427 def OnBrowse(self, evt=None): # Overrides wx method 1bc
428 self._browsed = True
429 FileBrowseButton.OnBrowse(self, evt)
430 self._browsed = False
432 def on_destroy(self, event): 1bc
433 __ = event
434 # all pushed eventHandlers need to be popped before close
435 # the last event handler is window object itself - do not pop itself
436 try:
437 while self.GetEventHandler() is not self:
438 self.PopEventHandler()
439 except RuntimeError:
440 pass
442 def on_file_changed(self, evt): 1bc
443 _ = evt
444 if self._browsed:
445 self._browsed = False
446 self.SetValue(self._relative_path(self.GetValue()))
447 self._parent.setFocusToOK()
449 def _relative_path(self, value): 1bc
450 src = self._controller.datafile.source
451 if utils.is_same_drive(src, value):
452 path = relpath(value, src if isdir(src) else dirname(src))
453 else:
454 path = value
455 return path.replace('\\', '/') if context.IS_WINDOWS else \
456 path.replace('\\', '\\\\')
459class Suggestions(object): 1bc
461 def __init__(self, suggestion_source): 1bc
462 self._suggestion_source = suggestion_source 1ihjgdaef
463 self._previous_value = None 1ihjgdaef
464 self._previous_choices = [] 1ihjgdaef
466 def get_for(self, value, row=None): 1bc
467 self._previous_choices = self._get_choices(value, row) 1ihjgdaef
468 self._previous_value = value 1ihjgdaef
469 return [k for k, _ in self._previous_choices] 1ihjgdaef
471 def get_item(self, name): 1bc
472 for k, v in self._previous_choices:
473 if k == name:
474 return v
475 raise AttributeError('Item not in choices "%s"' % name)
477 def _get_choices(self, value, row): 1bc
478 if self._previous_value and value.startswith(self._previous_value): 1ihjgdaef
479 return [(key, val) for key, val in self._previous_choices 1h
480 if utils.normalize(key).startswith(utils.normalize(value))]
481 choices = self._suggestion_source.get_suggestions(value, row) 1ihjgdaef
482 duplicate_names = self._get_duplicate_names(choices) 1ihjgdaef
483 return self._format_choices(choices, value, duplicate_names) 1ihjgdaef
485 @staticmethod 1bc
486 def _get_duplicate_names(choices): 1bc
487 results = set() 1ihjgdaef
488 normalized_names = [utils.normalize(ch.name if hasattr(ch, 'name') else ch) for ch in choices] 1ihjgdaef
489 for choice in choices: 1ihjgdaef
490 normalized = utils.normalize(choice.name if hasattr(choice, 'name') else choice) 1ihj
491 if normalized_names.count(normalized) > 1: 1ihj
492 results.add(normalized) 1ihj
493 return results 1ihjgdaef
495 def _format_choices(self, choices, prefix, duplicate_names): 1bc
496 return [(self._format(val, prefix, duplicate_names), val) for val in 1ihjgdaef
497 choices]
499 def _format(self, choice, prefix, duplicate_names): 1bc
500 if hasattr(choice, 'name'): 500 ↛ 503line 500 didn't jump to line 503 because the condition on line 500 was always true1ihj
501 return choice.name if self._matches_unique_shortname( 1ihj
502 choice, prefix, duplicate_names) else choice.longname
503 elif self._matches_unique_shortname(choice, prefix, duplicate_names):
504 return choice
506 @staticmethod 1bc
507 def _matches_unique_shortname(choice, prefix, duplicate_names): 1bc
508 if isinstance(choice, VariableInfo): 508 ↛ 509line 508 didn't jump to line 509 because the condition on line 508 was never true1ihj
509 return True
510 if hasattr(choice, 'name'): 510 ↛ 513line 510 didn't jump to line 513 because the condition on line 510 was always true1ihj
511 name = choice.name 1ihj
512 else:
513 name = choice
514 if not utils.normalize(name).startswith( 514 ↛ 516line 514 didn't jump to line 516 because the condition on line 514 was never true1ihj
515 utils.normalize(prefix)):
516 return False
517 if utils.normalize(name) in duplicate_names: 1ihj
518 return False 1ihj
519 return True 1ihj
522class ContentAssistPopup(object): 1bc
524 def __init__(self, parent, suggestion_source): 1bc
525 self._parent = parent 1gdaef
526 self._main_popup = RidePopupWindow(parent, _PREFERRED_POPUP_SIZE) 1gdaef
527 self._details_popup = HtmlPopupWindow(parent, _PREFERRED_POPUP_SIZE) 1gdaef
528 self._selection = -1 1gdaef
529 self._list: ContentAssistList = ContentAssistList(self._main_popup, 1gdaef
530 self.on_list_item_selected,
531 self.on_list_item_activated)
532 self._suggestions = Suggestions(suggestion_source) 1gdaef
533 self._choices = None 1gdaef
535 def reset(self): 1bc
536 self._selection = -1 1g
538 def get_value(self): 1bc
539 return self._selection != -1 and self._list.get_text(
540 self._selection) or None
542 def content_assist_for(self, value, row=None): 1bc
543 self._choices = self._suggestions.get_for(value, row=row) 1gdaef
544 if not self._choices: 544 ↛ 549line 544 didn't jump to line 549 because the condition on line 544 was always true1gdaef
545 self._list.ClearAll() 1gdaef
546 if not isinstance(self._parent, GridEditor): 1gdaef
547 self._parent.hide() 1daef
548 return False 1gdaef
549 self._choices = list(set([c for c in self._choices if c is not None]))
550 # print(f"DEBUG: contentassist.py ContentAssistPopup content_assist_for CALL POPULATE Choices={self._choices}")
551 self._list.populate(self._choices)
552 return True
554 @staticmethod 1bc
555 def _starts(val1, val2): 1bc
556 return val1.lower().startswith(val2.lower())
558 def content_assist_value(self, value): 1bc
559 _ = value # DEBUG: why we have this argument
560 if self._selection > -1:
561 return self._list.GetItem(self._selection).GetText()
562 return None
564 def show(self, xcoord, ycoord, cell_height): 1bc
565 self._main_popup.SetPosition((xcoord, 1gdaef
566 self._move_y_where_room(ycoord,
567 cell_height)))
568 self._details_popup.SetPosition((self._move_x_where_room(xcoord), 1gdaef
569 self._move_y_where_room(ycoord,
570 cell_height)))
571 self._main_popup.Show() 1gdaef
572 self._list.SetFocus() 1gdaef
574 @staticmethod 1bc
575 def _move_x_where_room(start_x): 1bc
576 width = _PREFERRED_POPUP_SIZE[0] 1gdaef
577 max_horizontal = wx.GetDisplaySize()[0] 1gdaef
578 free_right = max_horizontal - start_x - width 1gdaef
579 free_left = start_x - width 1gdaef
580 if max_horizontal - start_x < 2 * width and free_left > free_right: 580 ↛ 581line 580 didn't jump to line 581 because the condition on line 580 was never true1gdaef
581 return start_x - width
582 return start_x + width 1gdaef
584 @staticmethod 1bc
585 def _move_y_where_room(start_y, cell_height): 1bc
586 height = _PREFERRED_POPUP_SIZE[1] 1gdaef
587 max_vertical = wx.GetDisplaySize()[1] 1gdaef
588 if max_vertical - start_y - cell_height < height: 588 ↛ 589line 588 didn't jump to line 589 because the condition on line 588 was never true1gdaef
589 return start_y - height
590 return start_y + cell_height 1gdaef
592 def is_shown(self): 1bc
593 return self._main_popup.IsShown() 1gdaef
595 def select_and_scroll(self, key_code): 1bc
596 sel = self._list.GetFirstSelected()
597 count = self._list.GetItemCount()
598 pos = 0
599 if key_code == wx.WXK_DOWN:
600 pos = sel + 1 if sel < count - 1 else 0
601 elif key_code == wx.WXK_UP:
602 pos = sel - 1 if sel > 0 else count - 1
603 elif key_code == wx.WXK_PAGEDOWN:
604 pos = self._selection + 14 if count - self._selection > 14 else count - 1
605 elif key_code == wx.WXK_PAGEUP:
606 pos = self._selection - 14 if self._selection > 14 else 0
607 self._select_and_scroll(pos)
609 def _select_and_scroll(self, selection): 1bc
610 self._selection = selection
611 self._list.Select(self._selection)
612 self._list.EnsureVisible(self._selection)
613 value = self.get_value()
614 if value:
615 self._parent.SetValue(value)
617 def dismiss(self): 1bc
618 if not self._list.HasFocus():
619 self.hide()
621 def hide(self): 1bc
622 self._selection = -1 1daef
623 self._main_popup.Show(False) 1daef
624 self._details_popup.Show(False) 1daef
626 def on_list_item_activated(self, event): 1bc
627 __ = event
628 self._parent.fill_suggestion()
630 def on_list_item_selected(self, event): 1bc
631 self._selection = event.GetIndex()
632 item = self._suggestions.get_item(event.GetText())
633 if hasattr(item, 'details') and item.details:
634 self._details_popup.Show()
635 self._details_popup.set_content(item.details, item.name)
636 elif self._details_popup.IsShown():
637 self._details_popup.Show(False)
640class ContentAssistList(wx.ListCtrl): 1bc
642 def __init__(self, parent, selection_callback, activation_callback=None): 1bc
643 self.parent = parent 1gdaef
644 from ..preferences import RideSettings 1gdaef
645 _settings = RideSettings() 1gdaef
646 self.general_settings = _settings['General'] 1gdaef
647 self.color_background_help = self.general_settings['background help'] 1gdaef
648 self.color_foreground_text = self.general_settings['foreground text'] 1gdaef
649 style = wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_NO_HEADER 1gdaef
650 wx.ListCtrl.__init__(self, parent, style=style) 1gdaef
651 self._selection_callback = selection_callback 1gdaef
652 self._activation_callback = activation_callback 1gdaef
653 self.SetSize(parent.GetSize()) 1gdaef
654 self.SetBackgroundColour(self.color_background_help) 1gdaef
655 self.SetForegroundColour(self.color_foreground_text) 1gdaef
656 self.Bind(wx.EVT_LIST_ITEM_SELECTED, selection_callback) 1gdaef
657 self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, activation_callback) 1gdaef
659 def populate(self, data): 1bc
660 self.ClearAll()
661 self.InsertColumn(0, '', width=self.Size[0])
662 for row, item in enumerate(data):
663 self.InsertItem(row, item)
664 self.Select(0)
666 def get_text(self, index): 1bc
667 return self.GetItem(index).GetText()