Robot Framework Integrated Development Environment (RIDE)
gridbase.py
Go to the documentation of this file.
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 
16 import re
17 from os import linesep
18 
19 import wx
20 from wx import grid, Colour
21 
22 from .clipboard import ClipboardHandler
23 from ..context import IS_WINDOWS
24 from ..widgets import PopupCreator, PopupMenuItems
25 
26 
27 class GridEditor(grid.Grid):
28 
31  _col_add_threshold = 6
32 
35  _popup_items = [
36  'Insert Cells\tCtrl-Shift-I', 'Delete Cells\tCtrl-Shift-D',
37  'Insert Rows\tCtrl-I', 'Delete Rows\tCtrl-D', '---',
38  'Select All\tCtrl-A', '---', 'Cut\tCtrl-X', 'Copy\tCtrl-C',
39  'Paste\tCtrl-V', 'Insert\tCtrl-Shift-V', '---', 'Delete\tDel']
40 
43  _regexps = (re.compile(r'(\\+)r\\n'),
44  re.compile(r'(\\+)n'),
45  re.compile(r'(\\+)r'),
46  re.compile(r'(\\+) '))
47 
48  def __init__(self, parent, num_rows, num_cols, popup_creator=None):
49  grid.Grid.__init__(self, parent)
50  try:
51  self.settingssettings = parent.plugin.global_settings['Grid']
52  self.general_settingsgeneral_settings = parent.plugin.global_settings['General']
53  except AttributeError:
54  from ..preferences import RideSettings
55 
58  _settings = RideSettings()
59  self.general_settingsgeneral_settings = _settings['General']
60  self.settingssettings = _settings['Grid']
61  self.color_backgroundcolor_background = self.settingssettings['background unknown']
62  self.color_foregroundcolor_foreground = self.settingssettings['text empty']
63  self.color_background_helpcolor_background_help = self.general_settingsgeneral_settings['background help']
64  self.color_foreground_textcolor_foreground_text = self.general_settingsgeneral_settings['foreground text']
65  self.color_secondary_backgroundcolor_secondary_background = self.general_settingsgeneral_settings['secondary background']
66  self.color_secondary_foregroundcolor_secondary_foreground = self.general_settingsgeneral_settings['secondary foreground']
67 
68  self._bind_to_events_bind_to_events()
69  self.selectionselection = _GridSelection(self)
70  self._clipboard_handler_clipboard_handler = ClipboardHandler(self)
71  self._history_history = _GridState()
72  self.CreateGrid(int(num_rows), int(num_cols))
73  self.SetDefaultCellBackgroundColour(Colour(self.color_backgroundcolor_background))
74  self.SetDefaultCellTextColour(Colour(self.color_foregroundcolor_foreground))
75  self.GetGridColLabelWindow().SetBackgroundColour(Colour(self.color_secondary_backgroundcolor_secondary_background))
76  self.GetGridColLabelWindow().SetForegroundColour(Colour(self.color_secondary_foregroundcolor_secondary_foreground))
77  self.GetGridRowLabelWindow().SetBackgroundColour(Colour(self.color_secondary_backgroundcolor_secondary_background))
78  self.GetGridRowLabelWindow().SetForegroundColour(Colour(self.color_secondary_foregroundcolor_secondary_foreground))
79  self._popup_creator_popup_creator = popup_creator or PopupCreator()
80 
81  def _bind_to_events(self):
82  self.Bind(grid.EVT_GRID_SELECT_CELL, self.OnSelectCellOnSelectCell)
83  self.Bind(grid.EVT_GRID_RANGE_SELECT, self.OnRangeSelectOnRangeSelect)
84  self.Bind(grid.EVT_GRID_CELL_RIGHT_CLICK, self.OnCellRightClickOnCellRightClick)
85 
86  def register_context_menu_hook(self, callable):
87  self._popup_creator_popup_creator.add_hook(callable)
88 
89  def unregister_context_menu_hook(self, callable):
90  self._popup_creator_popup_creator.remove_hook(callable)
91 
92  def write_cell(self, row, col, value, update_history=True):
93  if update_history:
94  self._update_history_update_history()
95  self._expand_if_necessary_expand_if_necessary(row, col)
96  # TODO: Make below action configurable
97  # unescape \n to support multi lines display in grid cells
98  value = self._unescape_newlines_and_whitespaces_unescape_newlines_and_whitespaces(value)
99  self.SetCellValue(row, col, value)
100 
102  for regexp in self._regexps_regexps:
103  if regexp.pattern.endswith(' '):
104  item = regexp.sub(self._whitespace_replacer_whitespace_replacer, item)
105  else:
106  item = regexp.sub(self._newline_replacer_newline_replacer, item)
107  return item
108 
109  def _whitespace_replacer(self, match):
110  return self._replacer_replacer(' ', match)
111 
112  def _newline_replacer(self, match):
113  return self._replacer_replacer(linesep, match)
114 
115  def _replacer(self, char, match):
116  slashes = len(match.group(1))
117  if slashes % 2 == 1:
118  return '\\' * (slashes - 1) + char
119  return match.group()
120 
121  def _expand_if_necessary(self, row, col):
122  # Changed col and row fill because of blank spacing not changing color
123  # print(f"DEBUG: GridEditor ENTER_expand_if_necessary row={row}, col={col}")
124  while self.NumberRows <= max(1, row+1, 10-row): # DEBUG 25 makes slower rendering
125  self.AppendRows(1)
126  while self.NumberCols <= max(1, col+1, 10-col): # DEBUG 40 makes slower rendering
127  self.AppendCols(max(1, self._col_add_threshold_col_add_threshold)) # DEBUG: was infinite when value was 0
128 
129  def has_focus(self):
130  return self.FindFocus() == self
131 
132  def _update_history(self):
133  self._history_history.change(self._get_all_content_get_all_content())
134 
135  def _get_all_content(self):
136  return self._get_block_content_get_block_content(range(self.NumberRows),
137  range(self.NumberCols))
138 
139  @property
140  cell_under_cursor = property
141 
142  def cell_under_cursor(self):
143  x, y = self.ScreenToClient(wx.GetMousePosition())
144  x -= self.RowLabelSize
145  return self.XYToCell(*self.CalcUnscrolledPosition(x, y))
146 
147  def select(self, row, column):
148  self.SelectBlock(row, column, row, column)
149  self.SetGridCursor(row, column)
150  self.MakeCellVisible(row, column)
151 
152  def copy(self):
153  # print("DEBUG: GridBase copy() called\n")
154  self._clipboard_handler_clipboard_handler.copy()
155 
156  def cut(self):
157  self._update_history_update_history()
158  self._clipboard_handler_clipboard_handler.cut()
159  self._clear_selected_cells_clear_selected_cells()
160 
162  for row, col in self.selectionselection.cells():
163  self.write_cellwrite_cell(row, col, '', update_history=False)
164 
165  def paste(self):
166  self._update_history_update_history()
167  self._clipboard_handler_clipboard_handler.paste()
168 
169  def delete(self):
170  self._update_history_update_history()
171 
174  _iscelleditcontrolshown = self.IsCellEditControlShown()
175  if _iscelleditcontrolshown:
176  if IS_WINDOWS:
177  self._delete_from_cell_editor_delete_from_cell_editor()
178  else:
179  self._clear_selected_cells_clear_selected_cells()
180 
182  editor = self.get_cell_edit_controlget_cell_edit_control()
183  start, end = editor.Selection
184  if start == end:
185  end += 1
186  editor.Remove(start, end)
187 
189  return self.SelectedRows
190 
192  return self.GetCellEditor(*self.selectionselection.cell).GetControl()
193 
195  return self._get_block_content_get_block_content(self.selectionselection.rows(),
196  self.selectionselection.cols())
197 
199  cells = self.get_selected_contentget_selected_content()
200  if len(cells) != 1 or len(cells[0]) != 1:
201  return None
202  return cells[0][0]
203 
205  return self.GetCellValue(*self.selectionselection.cell)
206 
207  def _get_block_content(self, row_range, col_range):
208  return [[self.GetCellValue(row, col) for col in col_range]
209  for row in row_range]
210 
211  def _strip_trailing_empty_cells(self, rowdata):
212  while rowdata and not rowdata[-1]:
213  rowdata.pop()
214  return rowdata
215 
216  def undo(self):
217  prev_data = self._history_history.back()
218  if prev_data:
219  self.ClearGrid()
220  self._write_data_write_data(prev_data, update_history=False)
221 
222  def _write_data(self, data, update_history=True):
223  self.BeginBatch()
224  for row_index, row_data in enumerate(data):
225  for col_index, cell_value in enumerate(row_data):
226  self.write_cellwrite_cell(row_index, col_index, cell_value, update_history)
227  self.EndBatch()
228 
229  def OnSelectCell(self, event):
230  if self._is_whole_row_selection_is_whole_row_selection():
231  self.SelectBlock(self.selectionselection.topleft.row, self.selectionselection.topleft.col,
232  self.selectionselection.bottomright.row, self.selectionselection.bottomright.col,
233  addToSelected=True)
234  else:
235  self.selectionselection.set_from_single_selection(event)
236  event.Skip()
237 
238  def OnRangeSelect(self, event):
239  if not event.Selecting():
240  self.selectionselection.clear()
241  return
242  if event.ControlDown():
243  self.SetGridCursor(event.TopRow, event.LeftCol)
244  self.SelectBlock(event.TopRow, event.LeftCol,
245  event.BottomRow, event.RightCol, addToSelected=False)
246  else:
247  self.selectionselection.set_from_range_selection(self, event)
248  self._ensure_selected_row_is_visible_ensure_selected_row_is_visible(event.BottomRow)
249 
250  def _ensure_selected_row_is_visible(self, bottom_row):
251  if not self.IsVisible(bottom_row , 0) and bottom_row < self.NumberRows and \
252  self._is_whole_row_selection_is_whole_row_selection():
253  self.MakeCellVisible(bottom_row, 0)
254 
255  def OnCellRightClick(self, event):
256  if hasattr(event, 'Row') and hasattr(event, 'Col'):
257  if not (event.Row, event.Col) in self.selectionselection.cells():
258  self.selectselect(event.Row, event.Col)
259  self.selectionselection.set_from_single_selection(event)
260  self._popup_creator_popup_creator.show(self, PopupMenuItems(self, self._popup_items_popup_items),
261  self.get_selected_contentget_selected_content())
262 
263  # TODO This code is overriden at fieldeditors
264  def OnInsertCells(self, event):
265  self._insert_or_delete_cells_insert_or_delete_cells(self._insert_cells_insert_cells, event)
266 
267  # TODO This code is overriden at fieldeditors
268  def OnDeleteCells(self, event):
269  # print("DEBUG delete cells %s" % event)
270  self._insert_or_delete_cells_insert_or_delete_cells(self._delete_cells_delete_cells, event)
271 
272  def _insert_or_delete_cells(self, action, event):
273  self._update_history_update_history()
274  # print("DEBUG insert or delete selected %s" % self.selection.rows())
275  for index in self.selectionselection.rows():
276  data = action(self._row_data_row_data(index))
277  self._write_row_write_row(index, data)
278  self._refresh_layout_refresh_layout()
279  event.Skip()
280 
281  def _insert_cells(self, data):
282  cols = self.selectionselection.cols()
283  left = right = cols[0]
284  data[left:right] = [''] * len(cols)
285  return self._strip_trailing_empty_cells_strip_trailing_empty_cells(data)
286 
287  def _delete_cells(self, data):
288  cols = self.selectionselection.cols()
289  # print("DEBUG delete cols selected %s" % cols)
290  left, right = cols[0], cols[-1] # + 1 # DEBUG removed extra cell
291  # print("DEBUG delete left, right (%d,%d) values %s" % (left, right, data[left:right]))
292  data[left:right] = []
293  return data + [''] * len(cols)
294 
295  def _row_data(self, row):
296  return [self.GetCellValue(row, col) for col in range(self.NumberCols)]
297 
298  def _write_row(self, row, data):
299  for col, value in enumerate(data):
300  self.write_cellwrite_cell(row, col, value, update_history=False)
301 
302  def _refresh_layout(self):
303  self.SetFocus()
304  self.SetGridCursor(*self.selectionselection.cell)
305  self.GetParent().Sizer.Layout()
306 
307 
308 # TODO: refactor this internal state away if possible
310  cell = property(lambda self: (self.toplefttopleft.row, self.toplefttopleft.col))
311 
312  def __init__(self, grid):
313  self._set_set((0, 0))
314  self._grid_grid = grid
315 
316  def _set(self, topleft, bottomright=None):
317  self.toplefttopleft = _Cell(topleft[0], topleft[1])
318  self.bottomrightbottomright = self._count_bottomright_count_bottomright(topleft, bottomright)
319 
320  def _count_bottomright(self, topleft, bottomright):
321  if not bottomright:
322  return _Cell(topleft[0], topleft[1])
323  return _Cell(min(self._grid_grid.NumberRows - 1, bottomright[0]),
324  min(self._grid_grid.NumberCols - 1, bottomright[1]))
325 
326  def set_from_single_selection(self, event):
327  self._set_set((event.Row, event.Col))
328 
329  def set_from_range_selection(self, grid, event):
330  self._set_set(*self._get_bounding_coordinates_get_bounding_coordinates(grid, event))
331 
332  def clear(self):
333  selection = (self._grid_grid.GetGridCursorRow(), self._grid_grid.GetGridCursorCol())
334  self._set_set(selection)
335 
336  def _get_bounding_coordinates(self, grid, event):
337  whole_row_selection = sorted(grid.SelectedRows)
338  if whole_row_selection:
339  return (whole_row_selection[0], 0), \
340  (whole_row_selection[-1], grid.NumberCols - 1)
341  return (event.TopLeftCoords.Row, event.TopLeftCoords.Col), \
342  (event.BottomRightCoords.Row, event.BottomRightCoords.Col)
343 
344 
345  def rows(self):
346  return range(self.toplefttopleft.row, self.bottomrightbottomright.row + 1)
347 
348 
349  def cols(self):
350  return range(self.toplefttopleft.col, self.bottomrightbottomright.col + 1)
351 
352 
353  def cells(self):
354  return [(row, col) for col in self.colscols()
355  for row in self.rowsrows()]
356 
357 
358 class _Cell():
359 
360  def __init__(self, row, col):
361  self.rowrow = row
362  self.colcol = col
363 
364  def __iter__(self):
365  for item in self.rowrow, self.colcol:
366  yield item
367 
368 
369 class _GridState():
370 
371  def __init__(self):
372  self._back_back = []
373  self._forward_forward = []
374 
375  def change(self, state):
376  if not self._back_back or state != self._back_back[-1]:
377  self._back_back.append(state)
378  self._forward_forward = []
379 
380  def back(self):
381  if not self._back_back:
382  return None
383  self._forward_forward.append(self._back_back.pop())
384  return self._forward_forward[-1]
385 
386  def forward(self):
387  if not self._forward_forward:
388  return None
389  state = self._forward_forward.pop()
390  self._back_back.append(state)
391  return state
def _ensure_selected_row_is_visible(self, bottom_row)
Definition: gridbase.py:250
def _write_row(self, row, data)
Definition: gridbase.py:298
def __init__(self, parent, num_rows, num_cols, popup_creator=None)
Definition: gridbase.py:48
def write_cell(self, row, col, value, update_history=True)
Definition: gridbase.py:92
def register_context_menu_hook(self, callable)
Definition: gridbase.py:86
def _whitespace_replacer(self, match)
Definition: gridbase.py:109
def select(self, row, column)
Definition: gridbase.py:147
def _write_data(self, data, update_history=True)
Definition: gridbase.py:222
def _expand_if_necessary(self, row, col)
Definition: gridbase.py:121
def _insert_or_delete_cells(self, action, event)
Definition: gridbase.py:272
def _newline_replacer(self, match)
Definition: gridbase.py:112
def _replacer(self, char, match)
Definition: gridbase.py:115
def OnCellRightClick(self, event)
Definition: gridbase.py:255
def _get_block_content(self, row_range, col_range)
Definition: gridbase.py:207
def unregister_context_menu_hook(self, callable)
Definition: gridbase.py:89
def _unescape_newlines_and_whitespaces(self, item)
Definition: gridbase.py:101
def _strip_trailing_empty_cells(self, rowdata)
Definition: gridbase.py:211
def __init__(self, row, col)
Definition: gridbase.py:360
def cols(self)
Returns a list containing indices of columns currently selected.
Definition: gridbase.py:349
def rows(self)
Returns a list containing indices of rows currently selected.
Definition: gridbase.py:345
def _get_bounding_coordinates(self, grid, event)
Definition: gridbase.py:336
def set_from_range_selection(self, grid, event)
Definition: gridbase.py:329
def _count_bottomright(self, topleft, bottomright)
Definition: gridbase.py:320
def cells(self)
Return selected cells as a list of tuples (row, column).
Definition: gridbase.py:353
def set_from_single_selection(self, event)
Definition: gridbase.py:326
def _set(self, topleft, bottomright=None)
Definition: gridbase.py:316