Coverage for src/robotide/editor/kweditor.py: 15%
986 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 json 1ab
18from json.decoder import JSONDecodeError 1ab
19from multiprocessing import shared_memory 1ab
21import wx 1ab
22from wx import grid 1ab
23from wx.grid import GridCellEditor 1ab
25from os import linesep 1ab
26from .contentassist import ExpandingContentAssistTextCtrl 1ab
27from .editordialogs import UserKeywordNameDialog, ScalarVariableDialog, ListVariableDialog 1ab
28from .gridbase import GridEditor 1ab
29from .gridcolorizer import Colorizer 1ab
30from .tooltips import GridToolTips 1ab
31from .. import robotapi 1ab
32from ..context import IS_MAC 1ab
33from ..controller.cellinfo import tip_message, ContentType, CellType 1ab
34from ..controller.ctrlcommands import ChangeCellValue, clear_area, \ 1ab
35 paste_area, delete_rows, add_rows, comment_rows, insert_cells, delete_cells, \
36 uncomment_rows, Undo, Redo, RenameKeywordOccurrences, ExtractKeyword, \
37 add_keyword_from_cells, MoveRowsUp, MoveRowsDown, extract_scalar, extract_list, \
38 insert_area, sharp_comment_rows, sharp_uncomment_rows
39from ..editor.cellrenderer import CellRenderer 1ab
40from ..pluginapi import Plugin 1ab
41from ..publish import RideItemStepsChanged, RideSaved, PUBLISHER, RideBeforeSaving, RideSettingsChanged 1ab
42from ..ui.progress import RenameProgressObserver 1ab
43from ..usages.UsageRunner import Usages, VariableUsages 1ab
44from ..utils import variablematcher 1ab
45from ..widgets import RIDEDialog, PopupMenu, PopupMenuItems 1ab
47_ = wx.GetTranslation # To keep linter/code analyser happy 1ab
48builtins.__dict__['_'] = wx.GetTranslation 1ab
50_DEFAULT_FONT_SIZE = 11 1ab
51COL_HEADER_EDITOR = wx.NewId() 1ab
52PLUGIN_NAME = 'Editor' 1ab
53ZOOM_FACTOR = 'zoom factor' 1ab
54INS_ROWS = 'Insert Rows\tCtrl-I' 1ab
55DEL_ROWS = 'Delete Rows\tCtrl-D' 1ab
56CMT_CELLS = 'Comment Cells\tCtrl-Shift-3' 1ab
57UCMT_CELLS = 'Uncomment Cells\tCtrl-Shift-4' 1ab
58MV_CUR_DWN = 'Move Cursor Down\tAlt-Enter' 1ab
59CMT_ROWS = 'Comment Rows\tCtrl-3' 1ab
60UCMT_ROWS = 'Uncomment Rows\tCtrl-4' 1ab
61MV_ROWS_UP = 'Move Rows Up\tAlt-Up' 1ab
62MV_ROWS_DWN = 'Move Rows Down\tAlt-Down' 1ab
63SWAP_ROWS_UP = 'Swap Row Up\tCtrl-T' 1ab
64REN_KW = 'Rename Keyword' 1ab
66def requires_focus(function): 1ab
67 def _row_header_selected_on_linux(self): 1ab
68 return self.FindFocus() is None
70 def decorated_function(self, *args): 1ab
71 if not self.has_focus():
72 return
73 if self.has_focus() or self.IsCellEditControlShown() or _row_header_selected_on_linux(self):
74 function(self, *args)
76 return decorated_function 1ab
78class KeywordEditor(GridEditor, Plugin): 1ab
79 _no_cell = (-1, -1) 1ab
80 _popup_menu_shown = False 1ab
81 dirty = property(lambda self: self.controller.dirty) 1ab
83 def __init__(self, parent, controller, tree): 1ab
84 self.settings = parent.plugin.global_settings['Grid']
85 self.zoom = self.settings.get(ZOOM_FACTOR, 0)
86 self.general_settings = parent.plugin.global_settings['General']
87 self.color_background = self.general_settings['background']
88 self.color_foreground = self.general_settings['foreground']
89 self.color_secondary_background = self.general_settings['secondary background']
90 self.color_secondary_foreground = self.general_settings['secondary foreground']
91 self.color_background_help = self.general_settings['background help']
92 self.color_foreground_text = self.general_settings['foreground text']
93 GridEditor.__init__(self, parent, len(controller.steps) + 5, max((controller.max_columns + 1), 5),
94 parent.plugin.grid_popup_creator)
95 self._popup_items = ([
96 _('Insert Cells\tCtrl-Shift-I'), _('Delete Cells\tCtrl-Shift-D'),
97 _(INS_ROWS), _(DEL_ROWS), '---',
98 _('Select All\tCtrl-A'), '---', _('Cut\tCtrl-X'), _('Copy\tCtrl-C'),
99 _('Paste\tCtrl-V'), _('Insert\tCtrl-Shift-V'), '---', _('Delete\tDel'),
100 '---'] +
101 [
102 _('Create Keyword'),
103 _('Extract Keyword'),
104 _('Extract Variable'),
105 _(REN_KW),
106 _('Find Where Used'),
107 _('JSON Editor\tCtrl-Shift-J'),
108 '---',
109 _('Go to Definition\tCtrl-B'),
110 '---',
111 _('Undo\tCtrl-Z'),
112 _('Redo\tCtrl-Y'),
113 '---',
114 _('Make Variable\tCtrl-1'),
115 _('Make List Variable\tCtrl-2'),
116 _('Make Dict Variable\tCtrl-5'),
117 '---',
118 _(CMT_CELLS),
119 _(UCMT_CELLS),
120 _(MV_CUR_DWN),
121 '---',
122 _(CMT_ROWS),
123 _(UCMT_ROWS),
124 _(MV_ROWS_UP),
125 _(MV_ROWS_DWN),
126 _(SWAP_ROWS_UP)
127 ])
128 self._popup_items_nt = ([
129 'Insert Cells\tCtrl-Shift-I', 'Delete Cells\tCtrl-Shift-D',
130 INS_ROWS, DEL_ROWS, '---',
131 'Select All\tCtrl-A', '---', 'Cut\tCtrl-X', 'Copy\tCtrl-C',
132 'Paste\tCtrl-V', 'Insert\tCtrl-Shift-V', '---', 'Delete\tDel',
133 '---'] +
134 [
135 'Create Keyword',
136 'Extract Keyword',
137 'Extract Variable',
138 REN_KW,
139 'Find Where Used',
140 'JSON Editor\tCtrl-Shift-J',
141 '---',
142 'Go to Definition\tCtrl-B',
143 '---',
144 'Undo\tCtrl-Z',
145 'Redo\tCtrl-Y',
146 '---',
147 'Make Variable\tCtrl-1',
148 'Make List Variable\tCtrl-2',
149 'Make Dict Variable\tCtrl-5',
150 '---',
151 CMT_CELLS,
152 UCMT_CELLS,
153 MV_CUR_DWN,
154 '---',
155 CMT_ROWS,
156 UCMT_ROWS,
157 MV_ROWS_UP,
158 MV_ROWS_DWN,
159 SWAP_ROWS_UP
160 ])
161 self._parent = parent
162 self._plugin = parent.plugin
163 self._cell_selected = False
164 self._colorizer = Colorizer(self, controller)
165 self._controller = controller
166 try:
167 set_lang = shared_memory.ShareableList(name="language")
168 self._language = [set_lang[0]]
169 # print(f"DEBUG: settings.py SettingEditor __init__ SHAREDMEM language={self._language}")
170 except AttributeError:
171 try:
172 self._language = self._controller.language
173 # print(f"DEBUG: settings.py SettingEditor __init__ CONTROLLER language={self._language}")
174 except AttributeError:
175 self._language = ['en']
176 self._language = self._language[0] if isinstance(self._language, list) else self._language
177 self._configure_grid()
178 self._updating_namespace = False
179 self._controller.datafile_controller.register_for_namespace_updates(
180 self._namespace_updated)
181 self._tooltips = GridToolTips(self)
182 self._marked_cell = (-1, -1)
183 self._make_bindings()
184 self._write_steps(self._controller)
185 self.autosize()
186 self._tree = tree
187 self._has_been_clicked = False
188 self._counter = 0 # Workaround for double delete actions
189 self._dcells = None # Workaround for double delete actions
190 self._icells = None # Workaround for double insert actions
191 self._spacing = self._plugin.global_settings['txt number of spaces']
192 self._namespace_updated = None
193 self.InheritAttributes()
194 self.col_label_element = None
195 if hasattr(self, 'SetupScrolling'):
196 self.SetupScrolling(scrollToTop=True, scrollIntoView=True)
197 self.ShowScrollbars(wx.SHOW_SB_ALWAYS, wx.SHOW_SB_ALWAYS)
198 print("DEBUG: GridBase init at SELF SetupScrolling\n")
199 # self.Refresh()
200 PUBLISHER.subscribe(self._before_saving, RideBeforeSaving)
201 PUBLISHER.subscribe(self._data_changed, RideItemStepsChanged)
202 PUBLISHER.subscribe(self.on_settings_changed, RideSettingsChanged)
203 PUBLISHER.subscribe(self._ps_on_resize_grid, RideSaved)
205 def _namespace_updated(self): 1ab
206 if not self._updating_namespace:
207 self._updating_namespace = True
208 # See following issue for history of the next line:
209 # http://code.google.com/p/robotframework-ride/issues/detail?id=1108
210 wx.CallAfter(
211 wx.CallLater, 200, self._update_based_on_namespace_change)
213 def update_value(self): 1ab
214 # will be called in _RobotTableEditor._settings_changed
215 pass
217 def _update_based_on_namespace_change(self): 1ab
218 try:
219 self._colorize_grid()
220 finally:
221 self._updating_namespace = False
223 def _ps_on_resize_grid(self, message): 1ab
224 _ = message
225 self._resize_grid()
227 @requires_focus 1ab
228 def _resize_grid(self): 1ab
229 if self.settings.get("auto size cols", False):
230 self.AutoSizeColumns(False)
231 if self.settings.get("word wrap", True):
232 self.AutoSizeRows(False)
233 self.SetFocus()
235 def _set_cells(self): 1ab
236 col_size = self.settings.get("col size", 150)
237 max_col_size = self.settings.get("max col size", 450)
238 auto_col_size = self.settings.get("auto size cols", False)
239 word_wrap = self.settings.get("word wrap", True)
241 self.SetDefaultRenderer(
242 CellRenderer(col_size, max_col_size, auto_col_size, word_wrap))
243 self.SetRowLabelSize(wx.grid.GRID_AUTOSIZE)
244 self.SetColLabelSize(0)
246 if auto_col_size:
247 self.SetDefaultColSize(wx.grid.GRID_AUTOSIZE, resizeExistingCols=True)
248 else:
249 self.SetDefaultColSize(col_size, resizeExistingCols=True)
250 self.SetColMinimalAcceptableWidth(col_size)
252 if auto_col_size:
253 self.Bind(grid.EVT_GRID_CMD_COL_SIZE, self.on_cell_col_size_changed)
254 else:
255 self.Unbind(grid.EVT_GRID_CMD_COL_SIZE)
257 if word_wrap:
258 self.SetDefaultRowSize(wx.grid.GRID_AUTOSIZE)
259 self.SetDefaultCellOverflow(False) # DEBUG
260 self.autosize()
261 self._colorize_grid()
263 def _configure_grid(self): 1ab
264 self._set_cells()
265 self.SetDefaultEditor(ContentAssistCellEditor(self._plugin, self._controller, self._language))
266 self._set_fonts()
267 wx.CallAfter(self.SetGridCursor, (0, 0)) # To make cells colorized as soon we select keywords or tests
268 wx.CallAfter(self.highlight, '')
269 # wx.CallAfter(self.GoToCell, (0, 0)) # To make cells colorized as soon we select keywords or tests
271 def _set_fonts(self, update_cells=False): 1ab
272 _ = update_cells
273 font_size = self.settings.get('font size', _DEFAULT_FONT_SIZE) + self.zoom
274 font_family = wx.FONTFAMILY_MODERN if self.settings['fixed font'] \
275 else wx.FONTFAMILY_DEFAULT
276 font_face = self.settings.get('font face', None)
277 if font_face is None:
278 font = wx.Font(font_size, font_family, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
279 self.settings.set('font face', font.GetFaceName())
280 else:
281 font = wx.Font(font_size, font_family, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, font_face)
282 self.SetDefaultCellFont(font)
283 self.SetLabelFont(font)
284 col_size = max(0, font_size)+self.zoom
285 row_size = max(20, font_size)+self.zoom
286 self.SetRowLabelSize(row_size)
287 self.SetColLabelSize(col_size)
288 for row in range(self.NumberRows):
289 for col in range(self.NumberCols):
290 self.SetCellFont(row, col, font)
291 self.ForceRefresh()
293 def _make_bindings(self): 1ab
294 self.Bind(grid.EVT_GRID_EDITOR_SHOWN, self.on_editor)
295 self.Bind(wx.EVT_KEY_DOWN, self.on_key_down)
296 self.Bind(wx.EVT_CHAR, self.on_char)
297 self.Bind(wx.EVT_KEY_UP, self.on_key_up)
298 self.GetGridWindow().Bind(wx.EVT_MOTION, self.on_motion)
299 self.Bind(grid.EVT_GRID_CELL_LEFT_CLICK, self.on_cell_left_click)
300 self.Bind(grid.EVT_GRID_LABEL_RIGHT_CLICK, self.on_label_right_click)
301 self.Bind(grid.EVT_GRID_LABEL_LEFT_DCLICK, self._col_label_right_click)
302 self.Bind(grid.EVT_GRID_LABEL_LEFT_CLICK, self.on_label_left_click)
303 self.Bind(wx.EVT_KILL_FOCUS, self.on_kill_focus)
304 self.Bind(wx.EVT_MOUSEWHEEL, self.on_zoom)
306 def get_tooltip_content(self): 1ab
307 if self.IsCellEditControlShown() or self._popup_menu_shown:
308 return ''
309 cell = self.cell_under_cursor
310 cell_info = self._controller.get_cell_info(cell.Row, cell.Col)
311 return tip_message(cell_info)
313 def on_settings_changed(self, message): 1ab
314 """Redraw the colors if the color settings are modified"""
315 section, setting = message.keys
316 if section == 'Grid':
317 if ZOOM_FACTOR in setting:
318 self.zoom = self.settings.get(ZOOM_FACTOR, 0)
319 if 'font' in setting or ZOOM_FACTOR in setting:
320 self._set_fonts(update_cells=True)
321 elif ('col size' in setting
322 or 'max col size' in setting
323 or 'auto size cols' in setting
324 or 'word wrap' in setting):
325 self._set_cells()
326 return
327 self.autosize()
328 self._colorize_grid()
330 def on_select_cell(self, event): 1ab
331 self._cell_selected = True
332 GridEditor.on_select_cell(self, event)
333 rows = self._is_whole_row_selection()
334 if rows:
335 self.ClearSelection()
336 self.GoToCell(rows[0], 0)
337 wx.CallAfter(self.SelectBlock, rows[0], 0, rows[-1], self.NumberCols-1)
338 self._colorize_grid()
339 event.Skip()
341 def on_kill_focus(self, event): 1ab
342 self._tooltips.hide()
343 self._hide_link_if_necessary()
344 event.Skip()
346 def _execute(self, command): 1ab
347 return self._controller.execute(command)
349 def _toggle_underlined(self, cell, clear=False): 1ab
350 font = self.GetCellFont(cell.Row, cell.Col)
351 toggle = not font.GetUnderlined() if not clear else False
352 self._marked_cell = cell if toggle else (-1, -1)
353 font.SetUnderlined(toggle)
354 self.SetCellFont(cell.Row, cell.Col, font)
355 self.Refresh()
357 def on_label_right_click(self, event): 1ab
358 if event.Col == -1:
359 if event.Row != -1:
360 self._row_label_right_click(event)
361 else:
362 self._col_label_right_click(event)
364 def _row_label_right_click(self, event): 1ab
365 selected_row = event.GetRow()
366 selected_rows = self.selection.rows()
367 if selected_row not in selected_rows:
368 self.SelectRow(selected_row, addToSelected=False)
369 self.SetGridCursor(event.Row, 0)
370 popupitems = [
371 _(INS_ROWS),
372 _(DEL_ROWS),
373 _(CMT_ROWS),
374 _(UCMT_ROWS),
375 _(MV_ROWS_UP),
376 _(MV_ROWS_DWN),
377 _(SWAP_ROWS_UP),
378 '---',
379 _(CMT_CELLS),
380 _(UCMT_CELLS),
381 ]
382 popupitems_nt = [
383 INS_ROWS,
384 DEL_ROWS,
385 CMT_ROWS,
386 UCMT_ROWS,
387 MV_ROWS_UP,
388 MV_ROWS_DWN,
389 SWAP_ROWS_UP,
390 '---',
391 CMT_CELLS,
392 UCMT_CELLS,
393 ]
394 PopupMenu(self, PopupMenuItems(self, popupitems, popupitems_nt))
395 event.Skip()
397 def _col_label_right_click(self, event): 1ab
398 if event.Col < 0:
399 return
400 headers = self._controller.data.parent.header[1:]
401 if (not headers and event.Col == 0) or (headers and event.Col == len(headers)):
402 self._controller.data.parent.header.append('')
403 if event.Col + 1 < len(self._controller.data.parent.header) + 1:
404 value = self._controller.data.parent.header[event.Col+1]
405 lpos = self.GetColLeft(event.Col)
406 whandle = self.GetGridColLabelWindow()
407 font_size = self.GetLabelFont().GetPixelSize().width + 4
408 col_size = max(max(4, len(value))*font_size, self.GetColSize(event.Col))
409 edit = wx.TextCtrl(whandle, COL_HEADER_EDITOR, value, size=(col_size, -1),
410 style=wx.TE_PROCESS_ENTER | wx.TE_NOHIDESEL)
411 epos = edit.GetPosition()
412 edit.SetPosition((lpos, epos[1]))
413 edit.Bind(wx.EVT_KEY_DOWN, self.on_col_label_edit)
414 edit.Bind(wx.EVT_KEY_UP, self.on_col_label_edit)
415 edit.SetInsertionPointEnd()
416 edit.SelectAll()
417 edit.SetFocus()
418 self.col_label_element = (edit, event.Col)
419 self._marked_cell = (-1, -1)
420 # edit.Bind(wx.EVT_KILL_FOCUS, self.on_kill_focus)
422 def on_col_label_edit(self, event: wx.KeyEvent): 1ab
423 keycode = event.GetKeyCode()
424 edit, col = self.col_label_element
425 if keycode == wx.WXK_ESCAPE:
426 wx.CallAfter(edit.Destroy)
427 if keycode == wx.WXK_RETURN:
428 value = edit.GetValue()
429 if value == '':
430 del self._controller.data.parent.header[col+1]
431 else:
432 self._controller.data.parent.header[col+1] = value
433 self.SetColLabelValue(col, value)
434 self.AutoSizeColumn(col)
435 self._controller.mark_dirty()
436 self._controller.notify_steps_changed()
437 wx.CallAfter(edit.Destroy)
438 return
439 event.Skip()
441 def on_label_left_click(self, event): 1ab
442 if event.Col == -1:
443 if event.Row != -1:
444 self._row_label_left_click(event)
445 else:
446 self._col_label_left_click(event)
448 def _row_label_left_click(self, event): 1ab
449 if event.ShiftDown() or event.ControlDown():
450 self.ClearSelection()
451 cursor_row = self.GetGridCursorRow()
452 event_row = event.Row
453 start, end = (cursor_row, event_row) \
454 if cursor_row < event_row else (event_row, cursor_row)
455 for row in range(start, end + 1):
456 self.SelectRow(row, addToSelected=True)
457 else:
458 self.SelectRow(event.Row, addToSelected=False)
459 self.SetGridCursor(event.Row, 0)
461 def _col_label_left_click(self, event): 1ab
462 if event.ShiftDown() or event.ControlDown():
463 self.ClearSelection()
464 cursor_col = self.GetGridCursorCol()
465 event_col = event.Col
466 start, end = (cursor_col, event_col) if cursor_col < event_col else (event_col, cursor_col)
467 for col in range(start, end + 1):
468 self.SelectCol(col, addToSelected=True)
469 else:
470 self.SelectCol(event.Col, addToSelected=False)
471 self.SetGridCursor(0, event.Col)
473 def on_insert_rows(self, event): 1ab
474 self._execute(add_rows(self.selection.rows()))
475 self.ClearSelection()
476 self._resize_grid()
477 self._skip_except_on_mac(event)
479 @staticmethod 1ab
480 def _skip_except_on_mac(event): # DEBUG Do we still need this? 1ab
481 if event is not None and not IS_MAC:
482 # print("DEBUG skip!")
483 event.Skip()
485 def on_insert_cells(self, event=None): 1ab
486 # DEBUG remove below workaround for double actions
487 if self._counter == 1:
488 if self._icells == (
489 self.selection.topleft, self.selection.bottomright):
490 self._counter = 0
491 self._icells = None
492 return
493 else:
494 self._counter = 1
496 self._icells = (self.selection.topleft,
497 self.selection.bottomright)
498 self._execute(insert_cells(self.selection.topleft,
499 self.selection.bottomright))
500 self._resize_grid()
501 self._skip_except_on_mac(event)
503 def on_delete_cells(self, event=None): 1ab
504 # DEBUG remove below workaround for double actions
505 if self._counter == 1:
506 if self._dcells == (self.selection.topleft,
507 self.selection.bottomright):
508 self._counter = 0
509 self._dcells = None
510 return
511 else:
512 self._counter = 1
514 self._dcells = (self.selection.topleft, self.selection.bottomright)
515 self._execute(delete_cells(self.selection.topleft, self.selection.bottomright))
516 self._resize_grid()
517 self._skip_except_on_mac(event)
519 # DEBUG @requires_focus
520 def on_comment_rows(self, event=None): 1ab
521 self._execute(comment_rows(self.selection.rows()))
522 self._resize_grid()
523 self._skip_except_on_mac(event)
525 # DEBUG @requires_focus
526 def on_uncomment_rows(self, event=None): 1ab
527 self._execute(uncomment_rows(self.selection.rows()))
528 self._resize_grid()
529 self._skip_except_on_mac(event)
531 def on_sharp_comment_rows(self, event=None): 1ab
532 self._execute(sharp_comment_rows(self.selection.rows()))
533 self._resize_grid()
534 self._skip_except_on_mac(event)
536 def on_sharp_uncomment_rows(self, event=None): 1ab
537 self._execute(sharp_uncomment_rows(self.selection.rows()))
538 self._resize_grid()
539 self._skip_except_on_mac(event)
541 def on_move_rows_up(self, event=None): 1ab
542 __ = event
543 self._row_move(MoveRowsUp, -1)
545 def on_move_rows_down(self, event=None): 1ab
546 __ = event
547 self._row_move(MoveRowsDown, 1)
549 def on_swap_row_up(self, event=None): 1ab
550 __ = event
551 self._row_move(MoveRowsUp, 1, True)
553 def _row_move(self, command, change, swap=False): 1ab
554 # Workaround for double actions, see issue #2048
555 if self._counter == 1:
556 if IS_MAC:
557 row = self.GetGridCursorRow() + change
558 col = self.GetGridCursorCol()
559 if row >= 0:
560 self.SetGridCursor(row, col)
561 self._counter = 0
562 return
563 else:
564 self._counter += 1
565 rows = self.selection.rows()
566 if self._execute(command(rows)):
567 if swap:
568 wx.CallAfter(self._select_rows, [r for r in rows])
569 else:
570 wx.CallAfter(self._select_rows, [r + change for r in rows])
571 self._resize_grid()
573 def _select_rows(self, rows): 1ab
574 self.ClearSelection()
575 for r in rows:
576 self.SelectRow(r, True)
578 def on_motion(self, event): 1ab
579 if IS_MAC and self.IsCellEditControlShown():
580 return
581 event.Skip()
583 def _before_saving(self, message): 1ab
584 _ = message
585 if self.IsCellEditControlShown():
586 # Fix: cannot save modifications in edit mode
587 # Exit edit mode before saving
588 self.HideCellEditControl()
589 self.SaveEditControlValue()
590 self.SetFocus()
592 def _data_changed(self, message): 1ab
593 if self._controller == message.item:
594 self._write_steps(message.item)
596 def _write_steps(self, controller): 1ab
597 data = []
598 self._write_headers(controller)
599 for step in controller.steps:
600 data.append(self._format_comments(step.as_list()))
601 self.ClearGrid()
602 self._write_data(data, update_history=False)
603 self._colorize_grid()
605 def _write_headers(self, controller): 1ab
606 headers = controller.data.parent.header[1:]
607 if not headers:
608 self.SetColLabelSize(20) # DEBUG We set a small size to activate right and left clicks
609 for empty_col in range(0, 26): # DEBUG to be sure all are empty, was: self.NumberCols + 1
610 self.SetColLabelValue(empty_col, '')
611 return
612 self.SetColLabelSize(wx.grid.GRID_AUTOSIZE) # DEBUG
613 col = 0
614 for col, header in enumerate(headers):
615 self.SetColLabelValue(col, header)
616 for empty_col in range(col + 1, 26): # DEBUG to be sure all are empty, was: self.NumberCols + 1
617 self.SetColLabelValue(empty_col, '')
619 def _colorize_grid(self): 1ab
620 selection_content = self._get_single_selection_content_or_none_on_first_call()
621 if selection_content is None:
622 self.highlight(None)
623 elif self._parent:
624 # print(f"DEBUG: kweditor.py _colorize_grid parent={self._parent} name={self._parent.name}")
625 self._parent.highlight(selection_content, expand=False)
627 def highlight(self, text, expand=True): 1ab
628 # Below CallAfter was causing C++ assertions(objects not found)
629 # When calling Preferences Grid Colors change
630 wx.CallLater(100, self._colorizer.colorize, text)
632 def autosize(self): 1ab
633 wx.CallAfter(self.AutoSizeColumns, False)
634 wx.CallAfter(self.AutoSizeRows, False)
636 def _get_single_selection_content_or_none_on_first_call(self): 1ab
637 if self._cell_selected:
638 return self.get_single_selection_content()
640 @staticmethod 1ab
641 def _format_comments(data): 1ab
642 # DEBUG: This should be moved to robot.model
643 in_comment = False
644 ret = []
645 for cell in data:
646 if cell.strip().startswith('#'):
647 in_comment = True
648 if in_comment:
649 cell = cell.replace(' |', '')
650 ret.append(cell)
651 return ret
653 def cell_value_edited(self, row, col, value): 1ab
654 self._execute(ChangeCellValue(row, col, value))
655 wx.CallAfter(self.AutoSizeColumn, col, False)
656 wx.CallAfter(self.AutoSizeRow, row, False)
658 def get_selected_datafile_controller(self): 1ab
659 return self._controller.datafile_controller
661 # DEBUG @requires_focus
662 def on_copy(self, event=None): 1ab
663 __ = event
664 # print("DEBUG: OnCopy called event %s\n" % str(event))
665 self.copy()
667 # DEBUG @requires_focus
668 def on_cut(self, event=None): 1ab
669 self.cut()
670 self.on_delete(event)
672 def on_delete(self, event=None): 1ab
673 __ = event
674 if not self.IsCellEditControlShown():
675 self._execute(clear_area(self.selection.topleft,
676 self.selection.bottomright))
677 self._resize_grid()
679 # DEBUG @requires_focus
680 def on_paste(self, event=None): 1ab
681 __ = event
682 if self.IsCellEditControlShown():
683 self.paste()
684 else:
685 self._execute_clipboard_command(paste_area)
686 self._resize_grid()
688 def _execute_clipboard_command(self, command_class): 1ab
689 if not self.IsCellEditControlShown():
690 data = self._clipboard_handler.clipboard_content()
691 if data:
692 if isinstance(data, str):
693 data = [[self._string_to_cell(data)]]
694 elif isinstance(data, list) and isinstance(data[0], list):
695 data = self._get_main_data(data)
696 self._execute(command_class(self.selection.topleft, data))
698 def _get_main_data(self, data: []) -> []: 1ab
699 main_data = []
700 for ldata in data:
701 new_data = []
702 for rdata in ldata:
703 sdata = self._string_to_cell(rdata)
704 new_data.append(sdata)
705 main_data.append(new_data)
706 return main_data
708 def _string_to_cell(self, content: str) -> str: 1ab
709 spaces = ' ' * self._spacing
710 cells = content.replace(' | ', spaces).replace(spaces, '\t').strip() # DEBUG: Make this cells
711 return cells
713 # DEBUG
714 @requires_focus 1ab
715 def on_insert(self, event=None): 1ab
716 __ = event
717 self._execute_clipboard_command(insert_area)
718 self._resize_grid()
720 def on_delete_rows(self, event): 1ab
721 self._execute(delete_rows(self.selection.rows()))
722 self.ClearSelection()
723 self._resize_grid()
724 self._skip_except_on_mac(event)
726 # DEBUG @requires_focus
727 def on_undo(self, event=None): 1ab
728 __ = event
729 if not self.IsCellEditControlShown():
730 self._execute(Undo())
731 else:
732 self.GetCellEditor(*self.selection.cell).Reset()
733 self._resize_grid()
735 # DEBUG @requires_focus
736 def on_redo(self, event=None): 1ab
737 __ = event
738 self._execute(Redo())
739 self._resize_grid()
741 def close(self): 1ab
742 self._colorizer.close()
743 self.save()
744 PUBLISHER.unsubscribe_all(self)
745 if self._namespace_updated:
746 # Prevent re-entry to unregister method
747 self._controller.datafile_controller.unregister_namespace_updates(self._namespace_updated)
748 self._namespace_updated = None
750 def save(self): 1ab
751 self._tooltips.hide()
752 if self.IsCellEditControlShown():
753 cell_editor = self.GetCellEditor(*self.selection.cell)
754 cell_editor.EndEdit(self.selection.topleft.row, self.selection.topleft.col, self)
756 def show_content_assist(self): 1ab
757 if self.IsCellEditControlShown():
758 self.GetCellEditor(*self.selection.cell).show_content_assist(self.selection.cell)
760 def refresh_datafile(self, item, event): 1ab
761 self._tree.refresh_datafile(item, event)
763 @staticmethod 1ab
764 def _calculate_position(): 1ab
765 x, y = wx.GetMousePosition()
766 return x, y + 20
768 def on_editor(self, event): 1ab
769 self._tooltips.hide()
770 row_height = self.GetRowSize(self.selection.topleft.row)
771 self.GetCellEditor(*self.selection.cell).SetHeight(row_height)
772 event.Skip()
774 def _move_cursor_down(self, event): 1ab
775 self.DisableCellEditControl()
776 if event:
777 try:
778 shiftdown = event.ShiftDown()
779 except AttributeError:
780 shiftdown = False
781 else:
782 shiftdown = False
783 self.MoveCursorDown(shiftdown)
785 def _call_ctrl_shift_function(self, event: object, keycode: int): 1ab
786 if keycode == ord('I'):
787 self.on_insert_cells()
788 elif keycode == ord('J'):
789 self.on_json_editor(event)
790 elif keycode == ord('D'):
791 self.on_delete_cells()
792 """
793 elif keycode == ord('3'):
794 self._open_cell_editor_and_execute_sharp_comment()
795 elif keycode == ord('4'):
796 self._open_cell_editor_and_execute_sharp_uncomment()
797 """
798 return True
800 def _call_ctrl_function(self, event: object, keycode: int): 1ab
801 if keycode == wx.WXK_SPACE:
802 self._open_cell_editor_with_content_assist()
803 return False # event must not be skipped in this case
804 elif keycode == ord('C'):
805 self.on_copy(event)
806 elif keycode == ord('X'):
807 return False
808 elif keycode == ord('V'):
809 self.on_paste(event)
810 elif keycode == ord('Z'):
811 # print("DEBUG: kweditor.py _call_ctrl_function Pressed CTRL-Z")
812 self.on_undo(event)
813 elif keycode == ord('A'):
814 self.on_select_all(event)
815 elif keycode == ord('B'):
816 self._navigate_to_matching_user_keyword(
817 self.GetGridCursorRow(), self.GetGridCursorCol())
818 elif keycode == ord('F'):
819 if not self.has_focus():
820 self.SetFocus() # Avoiding Search field on Text Edit
821 elif keycode in (ord('1'), ord('2'), ord('5')):
822 self._open_cell_editor_and_execute_variable_creator(
823 list_variable=(keycode == ord('2')),
824 dict_variable=(keycode == ord('5')))
825 elif keycode == ord('T'):
826 self._row_move(MoveRowsUp, 1, True)
827 else:
828 self.show_cell_information()
829 return True
831 def _call_direct_function(self, event: wx.KeyEvent, keycode: int): 1ab
832 if keycode == wx.WXK_WINDOWS_MENU:
833 self.on_cell_right_click(event)
834 elif keycode == wx.WXK_BACK:
835 self._move_grid_cursor(event, keycode)
836 elif keycode == wx.WXK_RETURN:
837 if self.IsCellEditControlShown():
838 # fill auto-suggestion into cell when pressing enter
839 self._get_cell_editor().update_from_suggestion_list()
840 self._move_grid_cursor(event, keycode)
841 else:
842 self.open_cell_editor()
843 return False # event must not be skipped in this case
844 elif keycode == wx.WXK_F2:
845 self.open_cell_editor()
846 elif keycode in [wx.WXK_DOWN, wx.WXK_UP]:
847 # This block exists to ty to make cells visible
848 # on arrow_down because the mouse scroll does not work
849 # unfortunatelly IsVisible it always True.
850 delta = 1 if keycode == wx.WXK_DOWN else -1
851 cursor = (self.GetGridCursorRow(), self.GetGridCursorCol())
852 cursor = (cursor[0] + delta if cursor[0] + delta >= 0 else 0, cursor[1])
853 # print(f"DEBUG: call MakeCellVisible cursor={cursor}")
854 self.MakeCellVisible(cursor)
855 # print(f"DEBUG: IsCellVisible cursor={self.IsVisible(cursor)}")
856 return True
858 def _call_alt_function(self, event, keycode: int): 1ab
859 if keycode == wx.WXK_SPACE:
860 self._open_cell_editor_with_content_assist() # Mac CMD
861 elif keycode == wx.WXK_RETURN:
862 if self.IsCellEditControlShown():
863 event.GetEventObject().WriteText(linesep)
864 else:
865 self._move_cursor_down(event)
866 return False # event must not be skipped in this case
867 return True
869 def on_key_down(self, event): 1ab
870 keycode = event.GetUnicodeKey() or event.GetKeyCode()
871 if event.ControlDown():
872 if event.ShiftDown():
873 skip = self._call_ctrl_shift_function(event, keycode)
874 else:
875 skip = self._call_ctrl_function(event, keycode)
876 elif event.AltDown():
877 skip = self._call_alt_function(event, keycode)
878 else:
879 skip = self._call_direct_function(event, keycode)
880 if skip:
881 event.Skip()
883 def on_char(self, event): 1ab
884 key_char = event.GetUnicodeKey()
885 if key_char < ord(' '):
886 return
887 if key_char in [ord('['), ord('{'), ord('('), ord("'"), ord('\"'), ord('`')]:
888 self.open_cell_editor().execute_enclose_text(chr(key_char))
889 else:
890 event.Skip()
892 def on_zoom(self, event): 1ab
893 rotation = event.GetWheelRotation()
894 ctrl_down = event.ControlDown()
895 if not ctrl_down:
896 event.Skip()
897 return
898 self._set_zoom(rotation)
899 self.zoom = self.settings.get(ZOOM_FACTOR, 0)
901 def _set_zoom(self, rotation): 1ab
902 if rotation == 0: # Special value to reset
903 self.settings.set(ZOOM_FACTOR, 0)
904 return
905 new = 1 if rotation > 0 else -1 # Rotate away from user, increase, to user, decrease
906 old = self.settings.get(ZOOM_FACTOR, 0) # DEBUG: Condition to zoom limits, [-10, 10]?
907 self.settings.set(ZOOM_FACTOR, old+new)
909 def on_go_to_definition(self, event): 1ab
910 __ = event
911 self._navigate_to_matching_user_keyword(
912 self.GetGridCursorRow(), self.GetGridCursorCol())
914 def show_cell_information(self): 1ab
915 cell = self.cell_under_cursor
916 value = self._cell_value(cell)
917 if value:
918 self._show_user_keyword_link(cell, value)
919 self._show_keyword_details(cell, value)
921 def _cell_value(self, cell): 1ab
922 if cell == self._no_cell:
923 return None
924 return self.GetCellValue(cell.Row, cell.Col)
926 def _show_user_keyword_link(self, cell, value): 1ab
927 if cell != self._marked_cell and self._plugin.get_user_keyword(value):
928 self._toggle_underlined(cell)
930 def _show_keyword_details(self, cell, value): 1ab
931 details = self._plugin.get_keyword_details(value)
932 if not details:
933 info = self._controller.get_cell_info(cell.Row, cell.Col)
934 if info.cell_type == CellType.KEYWORD and info.content_type == ContentType.STRING:
935 details = _("""<b>Keyword was not detected by RIDE</b>
936 <br>Possible corrections:<br>
937 <ul>
938 <li>Import library or resource file containing the keyword.</li>
939 <li>For library import errors: Consider importing library spec XML
940 (Tools / Import Library Spec XML or by adding the XML file with the
941 correct name to PYTHONPATH) to enable keyword completion
942 for example for Java libraries.
943 Library spec XML can be created using libdoc tool from Robot Framework.</li>
944 </ul>""")
945 if details:
946 self._tooltips.show_info_at(
947 details, value, self._cell_to_screen_coordinates(cell))
949 def _cell_to_screen_coordinates(self, cell): 1ab
950 point = self.CellToRect(cell.Row, cell.Col).GetTopRight()
951 point.x += self.GetRowLabelSize() + 5
952 return self.ClientToScreen(self.CalcScrolledPosition(point))
954 def _move_rows(self, keycode): 1ab
955 if keycode == wx.WXK_UP:
956 self.on_move_rows_up()
957 else:
958 self.on_move_rows_down()
959 self.SetFocus()
961 def _move_grid_cursor(self, event, keycode): 1ab
962 self.DisableCellEditControl()
963 if keycode == wx.WXK_RETURN:
964 self.MoveCursorRight(event.ShiftDown())
965 else:
966 self.MoveCursorLeft(event.ShiftDown())
968 def move_grid_cursor_and_edit(self): 1ab
969 # self.MoveCursorRight(False)
970 self.open_cell_editor()
972 def on_key_up(self, event): 1ab
973 event.Skip() # DEBUG seen this skip as soon as possible
974 self._tooltips.hide()
975 self._hide_link_if_necessary()
976 # event.Skip()
978 def _get_cell_editor(self): 1ab
979 row = self.GetGridCursorRow()
980 return self.GetCellEditor(self.GetGridCursorCol(), row)
982 def open_cell_editor(self): 1ab
983 if not self.IsCellEditControlEnabled():
984 self.EnableCellEditControl()
985 cell_editor = self._get_cell_editor()
986 cell_editor.Show(True)
987 return cell_editor
989 def _open_cell_editor_with_content_assist(self): 1ab
990 # print(f"DEBUG: kweditor call _open_cell_editor_with_content_assist")
991 wx.CallAfter(self.open_cell_editor().show_content_assist)
992 # wx.CallAfter(self._move_grid_cursor, wx.grid.GridEvent(), wx.WXK_RETURN)
994 def _open_cell_editor_and_execute_variable_creator(self, list_variable=False, dict_variable=False): 1ab
995 cell_editor = self.open_cell_editor()
996 wx.CallAfter(cell_editor.execute_variable_creator, list_variable, dict_variable)
998 def on_make_variable(self, event): 1ab
999 __ = event
1000 self._open_cell_editor_and_execute_variable_creator(list_variable=False)
1002 def on_make_list_variable(self, event): 1ab
1003 __ = event
1004 self._open_cell_editor_and_execute_variable_creator(list_variable=True)
1006 def on_make_dict_variable(self, event): 1ab
1007 __ = event
1008 self._open_cell_editor_and_execute_variable_creator(dict_variable=True)
1010 def _open_cell_editor_and_execute_sharp_comment(self): 1ab
1011 # Meant for a single cell selection!
1012 wx.CallAfter(self.open_cell_editor().execute_sharp_comment)
1014 def _open_cell_editor_and_execute_sharp_uncomment(self): 1ab
1015 # Meant for a single cell selection!
1016 wx.CallAfter(self.open_cell_editor().execute_sharp_uncomment)
1018 def current_cell(self): 1ab
1019 curcell = [self.GetGridCursorRow(), self.GetGridCursorCol()]
1020 return curcell
1022 def on_comment_cells(self, event): 1ab
1023 __ = event
1024 if self.GetSelectionBlockTopLeft():
1025 self.on_sharp_comment_rows(event)
1026 else:
1027 self._open_cell_editor_and_execute_sharp_comment()
1029 def on_uncomment_cells(self, event): 1ab
1030 __ = event
1031 if self.GetSelectionBlockTopLeft():
1032 self.on_sharp_uncomment_rows(event)
1033 else:
1034 self._open_cell_editor_and_execute_sharp_uncomment()
1036 def on_cell_right_click(self, event): 1ab
1037 self._tooltips.hide()
1038 self._popup_menu_shown = True
1039 GridEditor.on_cell_right_click(self, event)
1040 self._popup_menu_shown = False
1042 def on_select_all(self, event): 1ab
1043 __ = event
1044 self.SelectAll()
1046 def on_cell_col_size_changed(self, event): 1ab
1047 wx.CallAfter(self.AutoSizeRows, False)
1048 event.Skip()
1050 def on_cell_left_click(self, event): 1ab
1051 self._tooltips.hide()
1052 if event.ControlDown():
1053 if self._navigate_to_matching_user_keyword(event.Row, event.Col):
1054 return
1055 if not self._has_been_clicked:
1056 self.SetGridCursor(event.Row, event.Col)
1057 self._has_been_clicked = True
1058 else:
1059 event.Skip()
1061 def _navigate_to_matching_user_keyword(self, row, col): 1ab
1062 value = self.GetCellValue(row, col)
1063 uk = self._plugin.get_user_keyword(value)
1064 if uk:
1065 self._toggle_underlined((grid.GridCellCoords(row, col)), True)
1066 wx.CallAfter(self._tree.select_user_keyword_node, uk)
1067 return True
1068 return False
1070 def _is_active_window(self): 1ab
1071 return self.IsShownOnScreen() and self.FindFocus()
1073 def _hide_link_if_necessary(self): 1ab
1074 if self._marked_cell == (-1, -1):
1075 return
1076 self._toggle_underlined(self._marked_cell, True)
1078 def on_create_keyword(self, event): 1ab
1079 __ = event
1080 cells = self._data_cells_from_current_row()
1081 if not cells:
1082 return
1083 try:
1084 self._execute(add_keyword_from_cells(cells))
1085 except ValueError as err:
1086 message_box = RIDEDialog(title=_('Validation Error'), message=str(err), style=wx.ICON_ERROR|wx.OK)
1087 message_box.ShowModal()
1089 def _data_cells_from_current_row(self): 1ab
1090 currow, curcol = self.selection.cell
1091 rowdata = self._row_data(currow)[curcol:]
1092 return self._strip_trailing_empty_cells(self._remove_comments(rowdata))
1094 @staticmethod 1ab
1095 def _remove_comments(data): 1ab
1096 for index, cell in enumerate(data):
1097 if cell.strip().startswith('#'):
1098 return data[:index]
1099 return data
1101 def on_extract_keyword(self, event): 1ab
1102 __ = event
1103 dlg = UserKeywordNameDialog(self._controller)
1104 if dlg.ShowModal() == wx.ID_OK:
1105 name, args = dlg.get_value()
1106 rows = self.selection.topleft.row, self.selection.bottomright.row
1107 self._execute(ExtractKeyword(name, args, rows))
1109 def on_extract_variable(self, event): 1ab
1110 __ = event
1111 cells = self.selection.cells()
1112 if len(cells) == 1:
1113 self._extract_scalar(cells[0])
1114 elif min(row for row, _ in cells) == max(row for row, _ in cells):
1115 self._extract_list(cells)
1116 self._resize_grid()
1118 def on_find_where_used(self, event): 1ab
1119 __ = event
1120 is_variable, searchstring = self._get_is_variable_and_searchstring()
1121 if searchstring:
1122 self._execute_find_where_used(is_variable, searchstring)
1124 def _get_is_variable_and_searchstring(self): 1ab
1125 cellvalue = self.GetCellValue(*self.selection.cells()[0])
1126 if self._cell_value_contains_multiple_search_items(cellvalue):
1127 choice_dialog = ChooseUsageSearchStringDialog(cellvalue)
1128 choice_dialog.ShowModal()
1129 is_var, value = choice_dialog.GetStringSelection()
1130 choice_dialog.Destroy()
1131 return is_var, value
1132 else:
1133 return variablematcher.is_variable(cellvalue), cellvalue
1135 def _execute_find_where_used(self, is_variable, searchstring): 1ab
1136 usages_dialog_class = VariableUsages if is_variable else Usages
1137 usages_dialog_class(
1138 self._controller,
1139 self._tree.highlight, searchstring).show()
1141 @staticmethod 1ab
1142 def _cell_value_contains_multiple_search_items(value): 1ab
1143 variables = variablematcher.find_variable_basenames(value)
1144 return variables and variables[0] != value
1146 def _extract_scalar(self, cell): 1ab
1147 var = robotapi.Variable(
1148 self._controller.datafile.variable_table, '',
1149 self.GetCellValue(*cell), '')
1150 dlg = ScalarVariableDialog(
1151 self._controller.datafile_controller.variables, var)
1152 if dlg.ShowModal() == wx.ID_OK:
1153 name, value = dlg.get_value()
1154 comment = dlg.get_comment()
1155 self._execute(extract_scalar(name, value, comment, cell))
1157 def _extract_list(self, cells): 1ab
1158 var = robotapi.Variable(
1159 self._controller.datafile.variable_table,
1160 '', [self.GetCellValue(*cell) for cell in cells], '')
1161 dlg = ListVariableDialog(
1162 self._controller.datafile_controller.variables, var, self._plugin)
1163 if dlg.ShowModal() == wx.ID_OK:
1164 name, value = dlg.get_value()
1165 comment = dlg.get_comment()
1166 self._execute(extract_list(name, value, comment, cells))
1168 def on_rename_keyword(self, event): 1ab
1169 __ = event
1170 old_name = self._current_cell_value()
1171 if not old_name.strip() or variablematcher.is_variable(old_name):
1172 return
1173 new_name = wx.GetTextFromUser(_('New name'), _(REN_KW), default_value=old_name)
1174 if new_name:
1175 self._execute(RenameKeywordOccurrences(
1176 old_name, new_name, RenameProgressObserver(self.GetParent(), background=self.color_background,
1177 foreground=self.color_foreground), language=self._language))
1179 # Add one new Dialog to edit pretty json String TODO: use better editor with more functions
1180 def on_json_editor(self, event=None): 1ab
1181 if event:
1182 event.Skip()
1183 dialog = RIDEDialog()
1184 dialog.SetTitle('JSON Editor')
1185 dialog.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
1186 ok_btn = wx.Button(dialog, wx.ID_OK, _("Save"))
1187 ok_btn.SetBackgroundColour(self.color_secondary_background)
1188 ok_btn.SetForegroundColour(self.color_secondary_foreground)
1189 cnl_btn = wx.Button(dialog, wx.ID_CANCEL, _("Cancel"))
1190 cnl_btn.SetBackgroundColour(self.color_secondary_background)
1191 cnl_btn.SetForegroundColour(self.color_secondary_foreground)
1192 rich_text = wx.TextCtrl(dialog, wx.ID_ANY, "If supported by the native control, this is reversed, and this is"
1193 " a different font.", size=(400, 475),
1194 style=wx.HSCROLL | wx.TE_MULTILINE | wx.TE_NOHIDESEL)
1195 rich_text.SetBackgroundColour(self.settings['background unknown'])
1196 rich_text.SetForegroundColour(self.settings['text empty'])
1197 dialog.Sizer.Add(rich_text, flag=wx.GROW, proportion=1)
1198 dialog.Sizer.Add(ok_btn, flag=wx.ALL)
1199 dialog.Sizer.Add(cnl_btn, flag=wx.ALL)
1200 # Get cell value of parent grid
1201 if self.is_json(self._current_cell_value()):
1202 json_str = json.loads(self._current_cell_value())
1203 rich_text.SetValue(json.dumps(json_str, indent=4, ensure_ascii=False))
1204 else:
1205 rich_text.SetValue(self._current_cell_value())
1206 dialog.SetSize((650, 550))
1207 # If click Save, then save the value in richText into the original
1208 # grid cell, and clear all indent.
1209 if dialog.ShowModal() == wx.ID_OK:
1210 content = rich_text.GetValue()
1211 if self.is_json(content):
1212 str_json = json.loads(content)
1213 self.cell_value_edited(self.selection.cell[0], self.selection.cell[1],
1214 json.dumps(str_json, ensure_ascii=False))
1215 else:
1216 try:
1217 json.loads(content) # Yes, we need the error
1218 except JSONDecodeError as e:
1219 res = RIDEDialog(title=_('Validation Error!'),
1220 message=f"{_('Error in JSON:')} {e}\n\n{_('Save anyway?')}",
1221 style=wx.ICON_ERROR | wx.YES_NO)
1222 res.InheritAttributes()
1223 response = res.ShowModal()
1224 if response == wx.ID_YES:
1225 self.cell_value_edited(self.selection.cell[0], self.selection.cell[1], rich_text.GetValue())
1227 # If the json_str is json format, then return True
1228 @staticmethod 1ab
1229 def is_json(json_str): 1ab
1230 try:
1231 json.loads(json_str)
1232 except JSONDecodeError:
1233 return False
1234 return True
1236 """ 1ab
1237 def words_cache(self, doc_size: int):
1238 if doc_size != self.doc_size:
1239 words_list = self.collect_words(SOME_CONTENT)
1240 self._words_cache.update(words_list)
1241 self.doc_size = doc_size
1242 return sorted(self._words_cache)
1244 @staticmethod
1245 def collect_words(text: str):
1246 if not text:
1247 return ['']
1249 def var_strip(txt:str):
1250 return txt.strip('$&@%{[(')
1252 words = set()
1253 words_ = list(text.replace('\r\n', ' ').replace('\n', ' ').split(' '))
1254 for w in words_:
1255 wl = var_strip(w)
1256 if wl and wl[0].isalpha():
1257 words.add(w)
1259 print(f"DEBUG: texteditor.py SourceEditor collect_words returning {words=}")
1260 return sorted(words)
1261 """
1263class ContentAssistCellEditor(GridCellEditor): 1ab
1265 def __init__(self, plugin, controller, language='En'): 1ab
1266 self.settings = plugin.global_settings['Grid']
1267 self.general_settings = plugin.global_settings['General']
1268 self.filter_newlines = self.settings.get("filter newlines", True)
1269 self.color_background_help = self.general_settings['background help']
1270 self.color_foreground_text = self.general_settings['foreground text']
1271 GridCellEditor.__init__(self)
1272 self._plugin = plugin
1273 self._controller = controller
1274 self._language = language
1275 self._grid = None
1276 self._original_value = None
1277 self._value = None
1278 self._tc = None
1279 self._counter = 0
1280 self._height = 0
1282 def show_content_assist(self, args=None): 1ab
1283 _ = args
1284 if self._tc:
1285 self._tc.show_content_assist()
1287 def update_from_suggestion_list(self): 1ab
1288 if self._tc and self._tc.is_shown():
1289 self._tc.fill_suggestion()
1291 def execute_variable_creator(self, list_variable=False, 1ab
1292 dict_variable=False):
1293 self._tc.execute_variable_creator(list_variable, dict_variable)
1295 def execute_enclose_text(self, keycode): 1ab
1296 self._tc.execute_enclose_text(keycode)
1298 def execute_sharp_comment(self): 1ab
1299 self._tc.execute_sharp_comment()
1301 def execute_sharp_uncomment(self): 1ab
1302 self._tc.execute_sharp_uncomment()
1304 def Create(self, parent, idd, evthandler): 1ab
1305 self._tc = ExpandingContentAssistTextCtrl(parent, self._plugin, self._controller, self._language)
1306 # self._tc.suggestion_source.update_from_local(self._controller.datafile, self._language)
1307 self.SetControl(self._tc)
1308 if evthandler:
1309 self._tc.PushEventHandler(evthandler)
1311 def SetSize(self, rect): 1ab
1312 self._tc.SetSize(rect.x, rect.y, rect.width + 2, rect.height + 2, wx.SIZE_ALLOW_MINUS_ONE)
1314 def SetHeight(self, height): 1ab
1315 self._height = height
1317 def BeginEdit(self, row, col, gridd): 1ab
1318 self._counter = 0
1319 self._tc.SetSize((-1, self._height))
1320 self._tc.SetBackgroundColour(self.color_background_help) # DEBUG: We are now in Edit mode
1321 self._tc.SetForegroundColour(self.color_foreground_text)
1322 self._tc.set_row(row)
1323 self._original_value = gridd.GetCellValue(row, col)
1324 if self._original_value:
1325 if self.filter_newlines:
1326 temp_value = self._original_value.replace(r'\n', '\\n')
1327 self._tc.SetValue(temp_value)
1328 else:
1329 self._tc.SetValue(self._original_value)
1330 self._tc.SetSelection(0, self._tc.GetLastPosition())
1331 self._tc.SetFocus()
1332 self._grid = gridd
1334 def EndEdit(self, row, col, gridd, *ignored): 1ab
1335 value = self._get_value()
1336 if value and self.filter_newlines:
1337 temp_value = value.replace('\\n', r'\n')
1338 value = temp_value
1339 if value != self._original_value:
1340 self._value = value
1341 wx.CallAfter(self._grid.move_grid_cursor_and_edit)
1342 return value
1343 else:
1344 self._tc.hide()
1345 gridd.SetFocus()
1347 def ApplyEdit(self, row, col, gridd): 1ab
1348 val = self._tc.GetValue()
1349 gridd.GetTable().SetValue(row, col, val) # update the table
1350 self._original_value = ''
1351 self._tc.SetValue('')
1352 # if self._value and val != '': # DEBUG Fix #1967 crash when click other cell
1353 # this will cause deleting all text in edit mode not working
1354 self._grid.cell_value_edited(row, col, self._value)
1356 def _get_value(self): 1ab
1357 suggestion = self._tc.content_assist_value()
1358 return suggestion or self._tc.GetValue()
1360 def Reset(self): 1ab
1361 self._tc.SetValue(self._original_value)
1362 self._tc.reset()
1364 def StartingKey(self, event): 1ab
1365 key = event.GetKeyCode()
1366 event.Skip() # DEBUG seen this skip as soon as possible
1367 if key == wx.WXK_DELETE or key > 255:
1368 # print(f"DEBUG: Delete key at ContentAssist key {key}")
1369 self._grid.HideCellEditControl()
1370 elif key == wx.WXK_BACK:
1371 self._tc.SetValue(self._original_value)
1372 else:
1373 self._tc.SetValue(chr(key))
1374 self._tc.SetFocus()
1375 self._tc.SetInsertionPointEnd()
1377 def Clone(self): 1ab
1378 return ContentAssistCellEditor(self._plugin, self._controller, self._language)
1381class ChooseUsageSearchStringDialog(wx.Dialog): 1ab
1383 def __init__(self, cellvalue): 1ab
1384 wx.Dialog.__init__(self, None, wx.ID_ANY, "Find Where Used",
1385 style=wx.DEFAULT_DIALOG_STYLE)
1386 """
1387 self.SetBackgroundColour(Colour(200, 222, 40))
1388 self.SetForegroundColour(Colour(7, 0, 70))
1389 """
1390 self.caption = _("Please select what you want to check for usage")
1391 variables = set(variablematcher.find_variable_basenames(cellvalue))
1392 self.choices = [(False, cellvalue)] + [(True, v) for v in variables]
1393 self.choices_string = [_("Complete cell content")] + \
1394 [_("Variable ") + var.replace("&", "&&") for var
1395 in variables]
1396 self._build_ui()
1398 def _build_ui(self): 1ab
1399 self.radiobox_choices = wx.RadioBox(
1400 self, choices=self.choices_string, style=wx.RA_SPECIFY_COLS,
1401 majorDimension=1)
1402 sizer = wx.BoxSizer(wx.VERTICAL)
1403 sizer.Add(wx.StaticText(self, label=self.caption), 0, wx.ALL |
1404 wx.EXPAND, 5)
1405 sizer.Add(self.radiobox_choices, 0, wx.ALL | wx.EXPAND, 5)
1406 sizer.Add(wx.Button(self, wx.ID_OK, label=_("Search")),
1407 0, wx.ALL | wx.ALIGN_CENTER, 5)
1408 big_sizer = wx.BoxSizer(wx.VERTICAL)
1409 big_sizer.Add(sizer, 0, wx.ALL, 10)
1410 self.SetSizer(big_sizer)
1411 self.Fit()
1412 self.CenterOnParent()
1414 def GetStringSelection(self): 1ab
1415 return self.choices[self.radiobox_choices.GetSelection()]