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

1# Copyright 2008-2015 Nokia Networks 

2# Copyright 2016- Robot Framework Foundation 

3# 

4# Licensed under the Apache License, Version 2.0 (the "License"); 

5# you may not use this file except in compliance with the License. 

6# You may obtain a copy of the License at 

7# 

8# http://www.apache.org/licenses/LICENSE-2.0 

9# 

10# Unless required by applicable law or agreed to in writing, software 

11# distributed under the License is distributed on an "AS IS" BASIS, 

12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

13# See the License for the specific language governing permissions and 

14# limitations under the License. 

15 

16import builtins 1ab

17import json 1ab

18from json.decoder import JSONDecodeError 1ab

19from multiprocessing import shared_memory 1ab

20 

21import wx 1ab

22from wx import grid 1ab

23from wx.grid import GridCellEditor 1ab

24 

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

46 

47_ = wx.GetTranslation # To keep linter/code analyser happy 1ab

48builtins.__dict__['_'] = wx.GetTranslation 1ab

49 

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

65 

66def requires_focus(function): 1ab

67 def _row_header_selected_on_linux(self): 1ab

68 return self.FindFocus() is None 

69 

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) 

75 

76 return decorated_function 1ab

77 

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

82 

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) 

204 

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) 

212 

213 def update_value(self): 1ab

214 # will be called in _RobotTableEditor._settings_changed 

215 pass 

216 

217 def _update_based_on_namespace_change(self): 1ab

218 try: 

219 self._colorize_grid() 

220 finally: 

221 self._updating_namespace = False 

222 

223 def _ps_on_resize_grid(self, message): 1ab

224 _ = message 

225 self._resize_grid() 

226 

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

234 

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) 

240 

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) 

245 

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) 

251 

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) 

256 

257 if word_wrap: 

258 self.SetDefaultRowSize(wx.grid.GRID_AUTOSIZE) 

259 self.SetDefaultCellOverflow(False) # DEBUG 

260 self.autosize() 

261 self._colorize_grid() 

262 

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 

270 

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

292 

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) 

305 

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) 

312 

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

329 

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

340 

341 def on_kill_focus(self, event): 1ab

342 self._tooltips.hide() 

343 self._hide_link_if_necessary() 

344 event.Skip() 

345 

346 def _execute(self, command): 1ab

347 return self._controller.execute(command) 

348 

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

356 

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) 

363 

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

396 

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) 

421 

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

440 

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) 

447 

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) 

460 

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) 

472 

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) 

478 

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

484 

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 

495 

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) 

502 

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 

513 

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) 

518 

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) 

524 

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) 

530 

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) 

535 

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) 

540 

541 def on_move_rows_up(self, event=None): 1ab

542 __ = event 

543 self._row_move(MoveRowsUp, -1) 

544 

545 def on_move_rows_down(self, event=None): 1ab

546 __ = event 

547 self._row_move(MoveRowsDown, 1) 

548 

549 def on_swap_row_up(self, event=None): 1ab

550 __ = event 

551 self._row_move(MoveRowsUp, 1, True) 

552 

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

572 

573 def _select_rows(self, rows): 1ab

574 self.ClearSelection() 

575 for r in rows: 

576 self.SelectRow(r, True) 

577 

578 def on_motion(self, event): 1ab

579 if IS_MAC and self.IsCellEditControlShown(): 

580 return 

581 event.Skip() 

582 

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

591 

592 def _data_changed(self, message): 1ab

593 if self._controller == message.item: 

594 self._write_steps(message.item) 

595 

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

604 

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, '') 

618 

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) 

626 

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) 

631 

632 def autosize(self): 1ab

633 wx.CallAfter(self.AutoSizeColumns, False) 

634 wx.CallAfter(self.AutoSizeRows, False) 

635 

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

639 

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 

652 

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) 

657 

658 def get_selected_datafile_controller(self): 1ab

659 return self._controller.datafile_controller 

660 

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

666 

667 # DEBUG @requires_focus 

668 def on_cut(self, event=None): 1ab

669 self.cut() 

670 self.on_delete(event) 

671 

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

678 

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

687 

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

697 

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 

707 

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 

712 

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

719 

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) 

725 

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

734 

735 # DEBUG @requires_focus 

736 def on_redo(self, event=None): 1ab

737 __ = event 

738 self._execute(Redo()) 

739 self._resize_grid() 

740 

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 

749 

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) 

755 

756 def show_content_assist(self): 1ab

757 if self.IsCellEditControlShown(): 

758 self.GetCellEditor(*self.selection.cell).show_content_assist(self.selection.cell) 

759 

760 def refresh_datafile(self, item, event): 1ab

761 self._tree.refresh_datafile(item, event) 

762 

763 @staticmethod 1ab

764 def _calculate_position(): 1ab

765 x, y = wx.GetMousePosition() 

766 return x, y + 20 

767 

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

773 

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) 

784 

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 

799 

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 

830 

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 

857 

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 

868 

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

882 

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

891 

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) 

900 

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) 

908 

909 def on_go_to_definition(self, event): 1ab

910 __ = event 

911 self._navigate_to_matching_user_keyword( 

912 self.GetGridCursorRow(), self.GetGridCursorCol()) 

913 

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) 

920 

921 def _cell_value(self, cell): 1ab

922 if cell == self._no_cell: 

923 return None 

924 return self.GetCellValue(cell.Row, cell.Col) 

925 

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) 

929 

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

948 

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

953 

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

960 

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

967 

968 def move_grid_cursor_and_edit(self): 1ab

969 # self.MoveCursorRight(False) 

970 self.open_cell_editor() 

971 

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

977 

978 def _get_cell_editor(self): 1ab

979 row = self.GetGridCursorRow() 

980 return self.GetCellEditor(self.GetGridCursorCol(), row) 

981 

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 

988 

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) 

993 

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) 

997 

998 def on_make_variable(self, event): 1ab

999 __ = event 

1000 self._open_cell_editor_and_execute_variable_creator(list_variable=False) 

1001 

1002 def on_make_list_variable(self, event): 1ab

1003 __ = event 

1004 self._open_cell_editor_and_execute_variable_creator(list_variable=True) 

1005 

1006 def on_make_dict_variable(self, event): 1ab

1007 __ = event 

1008 self._open_cell_editor_and_execute_variable_creator(dict_variable=True) 

1009 

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) 

1013 

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) 

1017 

1018 def current_cell(self): 1ab

1019 curcell = [self.GetGridCursorRow(), self.GetGridCursorCol()] 

1020 return curcell 

1021 

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

1028 

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

1035 

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 

1041 

1042 def on_select_all(self, event): 1ab

1043 __ = event 

1044 self.SelectAll() 

1045 

1046 def on_cell_col_size_changed(self, event): 1ab

1047 wx.CallAfter(self.AutoSizeRows, False) 

1048 event.Skip() 

1049 

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

1060 

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 

1069 

1070 def _is_active_window(self): 1ab

1071 return self.IsShownOnScreen() and self.FindFocus() 

1072 

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) 

1077 

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

1088 

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

1093 

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 

1100 

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

1108 

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

1117 

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) 

1123 

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 

1134 

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

1140 

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 

1145 

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

1156 

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

1167 

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

1178 

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

1226 

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 

1235 

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) 

1243 

1244 @staticmethod 

1245 def collect_words(text: str): 

1246 if not text: 

1247 return [''] 

1248 

1249 def var_strip(txt:str): 

1250 return txt.strip('$&@%{[(') 

1251 

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) 

1258 

1259 print(f"DEBUG: texteditor.py SourceEditor collect_words returning {words=}") 

1260 return sorted(words) 

1261 """ 

1262 

1263class ContentAssistCellEditor(GridCellEditor): 1ab

1264 

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 

1281 

1282 def show_content_assist(self, args=None): 1ab

1283 _ = args 

1284 if self._tc: 

1285 self._tc.show_content_assist() 

1286 

1287 def update_from_suggestion_list(self): 1ab

1288 if self._tc and self._tc.is_shown(): 

1289 self._tc.fill_suggestion() 

1290 

1291 def execute_variable_creator(self, list_variable=False, 1ab

1292 dict_variable=False): 

1293 self._tc.execute_variable_creator(list_variable, dict_variable) 

1294 

1295 def execute_enclose_text(self, keycode): 1ab

1296 self._tc.execute_enclose_text(keycode) 

1297 

1298 def execute_sharp_comment(self): 1ab

1299 self._tc.execute_sharp_comment() 

1300 

1301 def execute_sharp_uncomment(self): 1ab

1302 self._tc.execute_sharp_uncomment() 

1303 

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) 

1310 

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) 

1313 

1314 def SetHeight(self, height): 1ab

1315 self._height = height 

1316 

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 

1333 

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

1346 

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) 

1355 

1356 def _get_value(self): 1ab

1357 suggestion = self._tc.content_assist_value() 

1358 return suggestion or self._tc.GetValue() 

1359 

1360 def Reset(self): 1ab

1361 self._tc.SetValue(self._original_value) 

1362 self._tc.reset() 

1363 

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

1376 

1377 def Clone(self): 1ab

1378 return ContentAssistCellEditor(self._plugin, self._controller, self._language) 

1379 

1380 

1381class ChooseUsageSearchStringDialog(wx.Dialog): 1ab

1382 

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

1397 

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

1413 

1414 def GetStringSelection(self): 1ab

1415 return self.choices[self.radiobox_choices.GetSelection()]