]> git.saurik.com Git - wxWidgets.git/blame - wxPython/wx/lib/sheet.py
in unescape add normal characters in addition to unescaped characters :)
[wxWidgets.git] / wxPython / wx / lib / sheet.py
CommitLineData
d14a1e28
RD
1# sheet.py
2# CSheet - A wxPython spreadsheet class.
3# This is free software. Feel free to adapt it as you like.
4# Author: Mark F. Russo (russomf@hotmail.com) 2002/01/31
b881fc78
RD
5#---------------------------------------------------------------------------
6# 12/11/2003 - Jeff Grimmett (grimmtooth@softhome.net)
7#
8# o 2.5 compatability update.
9# o Untested.
10#
1fded56b 11
b881fc78
RD
12import string
13import wx
14import wx.grid
1fded56b 15
d14a1e28 16#---------------------------------------------------------------------------
b881fc78 17class CTextCellEditor(wx.TextCtrl):
d14a1e28
RD
18 """ Custom text control for cell editing """
19 def __init__(self, parent, id, grid):
b881fc78 20 wx.TextCtrl.__init__(self, parent, id, "", style=wx.NO_BORDER)
d14a1e28 21 self._grid = grid # Save grid reference
b881fc78 22 self.Bind(wx.EVT_CHAR, self.OnChar)
d14a1e28
RD
23
24 def OnChar(self, evt): # Hook OnChar for custom behavior
25 """Customizes char events """
26 key = evt.GetKeyCode()
b881fc78 27 if key == wx.WXK_DOWN:
d14a1e28
RD
28 self._grid.DisableCellEditControl() # Commit the edit
29 self._grid.MoveCursorDown(False) # Change the current cell
b881fc78 30 elif key == wx.WXK_UP:
d14a1e28
RD
31 self._grid.DisableCellEditControl() # Commit the edit
32 self._grid.MoveCursorUp(False) # Change the current cell
b881fc78 33 elif key == wx.WXK_LEFT:
d14a1e28
RD
34 self._grid.DisableCellEditControl() # Commit the edit
35 self._grid.MoveCursorLeft(False) # Change the current cell
b881fc78 36 elif key == wx.WXK_RIGHT:
d14a1e28
RD
37 self._grid.DisableCellEditControl() # Commit the edit
38 self._grid.MoveCursorRight(False) # Change the current cell
39
40 evt.Skip() # Continue event
41
42#---------------------------------------------------------------------------
b881fc78 43class CCellEditor(wx.grid.PyGridCellEditor):
d14a1e28
RD
44 """ Custom cell editor """
45 def __init__(self, grid):
b881fc78 46 wx.grid.PyGridCellEditor.__init__(self)
d14a1e28
RD
47 self._grid = grid # Save a reference to the grid
48
49 def Create(self, parent, id, evtHandler):
50 """ Create the actual edit control. Must derive from wxControl.
51 Must Override
52 """
53 self._tc = CTextCellEditor(parent, id, self._grid)
54 self._tc.SetInsertionPoint(0)
55 self.SetControl(self._tc)
56 if evtHandler:
57 self._tc.PushEventHandler(evtHandler)
58
59 def SetSize(self, rect):
60 """ Position/size the edit control within the cell rectangle. """
61 # Size text control to exactly overlay in-cell editing
62 self._tc.SetDimensions(rect.x+3, rect.y+3, rect.width-2, rect.height-2)
63
64 def Show(self, show, attr):
65 """ Show or hide the edit control. Use the attr (if not None)
66 to set colors or fonts for the control.
ac1c82d7
RD
67 NOTE: There is no need to everride this if you don't need
68 to do something out of the ordingary.
d14a1e28
RD
69 """
70 self.base_Show(show, attr)
71
72 def PaintBackground(self, rect, attr):
73 """ Draws the part of the cell not occupied by the edit control. The
74 base class version just fills it with background colour from the
75 attribute.
ac1c82d7
RD
76 NOTE: There is no need to everride this if you don't need
77 to do something out of the ordingary.
d14a1e28
RD
78 """
79 # Call base class method.
80 self.base_PaintBackground(self, rect, attr)
81
82 def BeginEdit(self, row, col, grid):
83 """ Fetch the value from the table and prepare edit control to begin editing.
84 Set the focus to the edit control. Must Override.
85 """
86 self._startValue = grid.GetTable().GetValue(row, col)
87 self._tc.SetValue(self._startValue)
88 self._tc.SetFocus()
89
90 # Select the text when initiating an edit so that subsequent typing
91 # replaces the contents.
92 self._tc.SetSelection(0, self._tc.GetLastPosition())
93
94 def EndEdit(self, row, col, grid):
95 """ Commit editing the current cell. Returns True if the value has changed.
96 If necessary, the control may be destroyed. Must Override.
97 """
98 changed = False # Assume value not changed
99 val = self._tc.GetValue() # Get value in edit control
100 if val != self._startValue: # Compare
101 changed = True # If different then changed is True
102 grid.GetTable().SetValue(row, col, val) # Update the table
103 self._startValue = '' # Clear the class' start value
104 self._tc.SetValue('') # Clear contents of the edit control
105
106 return changed
107
108 def Reset(self):
109 """ Reset the value in the control back to its starting value. Must Override. """
110 self._tc.SetValue(self._startValue)
111 self._tc.SetInsertionPointEnd()
112
113 def IsAcceptedKey(self, evt):
114 """ Return True to allow the given key to start editing. The base class
115 version only checks that the event has no modifiers. F2 is special
116 and will always start the editor.
117 """
118 return (not (evt.ControlDown() or evt.AltDown())
b881fc78 119 and evt.GetKeyCode() != wx.WXK_SHIFT)
d14a1e28
RD
120
121 def StartingKey(self, evt):
122 """ If the editor is enabled by pressing keys on the grid, this will be
123 called to let the editor react to that first key.
124 """
125 key = evt.GetKeyCode() # Get the key code
126 ch = None # Handle num pad keys
b881fc78
RD
127 if key in [ wx.WXK_NUMPAD0, wx.WXK_NUMPAD1, wx.WXK_NUMPAD2, wx.WXK_NUMPAD3,
128 wx.WXK_NUMPAD4, wx.WXK_NUMPAD5, wx.WXK_NUMPAD6, wx.WXK_NUMPAD7,
129 wx.WXK_NUMPAD8, wx.WXK_NUMPAD9]:
130 ch = chr(ord('0') + key - wx.WXK_NUMPAD0)
d14a1e28 131
b881fc78 132 elif key == wx.WXK_BACK: # Empty text control when init w/ back key
d14a1e28
RD
133 ch = ""
134 # Handle normal keys
135 elif key < 256 and key >= 0 and chr(key) in string.printable:
136 ch = chr(key)
137 if not evt.ShiftDown():
138 ch = ch.lower()
139
140 if ch is not None: # If are at this point with a key,
141 self._tc.SetValue(ch) # replace the contents of the text control.
142 self._tc.SetInsertionPointEnd() # Move to the end so that subsequent keys are appended
143 else:
144 evt.Skip()
145
146 def StartingClick(self):
147 """ If the editor is enabled by clicking on the cell, this method will be
148 called to allow the editor to simulate the click on the control.
149 """
150 pass
151
152 def Destroy(self):
ac1c82d7
RD
153 """ Final cleanup
154 NOTE: There is no need to everride this if you don't need
155 to do something out of the ordingary.
156 """
d14a1e28
RD
157 self.base_Destroy()
158
159 def Clone(self):
160 """ Create a new object which is the copy of this one. Must Override. """
161 return CCellEditor()
162
163#---------------------------------------------------------------------------
b881fc78 164class CSheet(wx.grid.Grid):
d14a1e28 165 def __init__(self, parent):
b881fc78 166 wx.grid.Grid.__init__(self, parent, -1)
d14a1e28
RD
167
168 # Init variables
169 self._lastCol = -1 # Init last cell column clicked
170 self._lastRow = -1 # Init last cell row clicked
171 self._selected = None # Init range currently selected
172 # Map string datatype to default renderer/editor
b881fc78
RD
173 self.RegisterDataType(wx.grid.GRID_VALUE_STRING,
174 wx.grid.GridCellStringRenderer(),
d14a1e28
RD
175 CCellEditor(self))
176
177 self.CreateGrid(4, 3) # By default start with a 4 x 3 grid
178 self.SetColLabelSize(18) # Default sizes and alignment
179 self.SetRowLabelSize(50)
b881fc78 180 self.SetRowLabelAlignment(wx.ALIGN_RIGHT, wx.ALIGN_BOTTOM)
d14a1e28
RD
181 self.SetColSize(0, 75) # Default column sizes
182 self.SetColSize(1, 75)
183 self.SetColSize(2, 75)
184
185 # Sink events
b881fc78
RD
186 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnLeftClick)
187 self.Bind(wx.grid.EVT_GRID_CELL_RIGHT_CLICK, self.OnRightClick)
188 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.OnLeftDoubleClick)
189 self.Bind(wx.grid.EVT_GRID_RANGE_SELECT, self.OnRangeSelect)
190 self.Bind(wx.grid.EVT_GRID_ROW_SIZE, self.OnRowSize)
191 self.Bind(wx.grid.EVT_GRID_COL_SIZE, self.OnColSize)
192 self.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.OnCellChange)
193 self.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.OnGridSelectCell)
d14a1e28
RD
194
195 def OnGridSelectCell(self, event):
196 """ Track cell selections """
197 # Save the last cell coordinates
198 self._lastRow, self._lastCol = event.GetRow(), event.GetCol()
199 event.Skip()
200
201 def OnRowSize(self, event):
202 event.Skip()
203
204 def OnColSize(self, event):
205 event.Skip()
206
207 def OnCellChange(self, event):
208 event.Skip()
209
210 def OnLeftClick(self, event):
211 """ Override left-click behavior to prevent left-click edit initiation """
212 # Save the cell clicked
213 currCell = (event.GetRow(), event.GetCol())
214
215 # Suppress event if same cell clicked twice in a row.
216 # This prevents a single-click from initiating an edit.
217 if currCell != (self._lastRow, self._lastCol): event.Skip()
218
219 def OnRightClick(self, event):
220 """ Move grid cursor when a cell is right-clicked """
221 self.SetGridCursor( event.GetRow(), event.GetCol() )
222 event.Skip()
223
224 def OnLeftDoubleClick(self, event):
225 """ Initiate the cell editor on a double-click """
226 # Move grid cursor to double-clicked cell
227 if self.CanEnableCellControl():
228 self.SetGridCursor( event.GetRow(), event.GetCol() )
229 self.EnableCellEditControl(True) # Show the cell editor
230 event.Skip()
231
232 def OnRangeSelect(self, event):
233 """ Track which cells are selected so that copy/paste behavior can be implemented """
234 # If a single cell is selected, then Selecting() returns False (0)
235 # and range coords are entire grid. In this case cancel previous selection.
236 # If more than one cell is selected, then Selecting() is True (1)
237 # and range accurately reflects selected cells. Save them.
238 # If more cells are added to a selection, selecting remains True (1)
239 self._selected = None
240 if event.Selecting():
241 self._selected = ((event.GetTopRow(), event.GetLeftCol()),
242 (event.GetBottomRow(), event.GetRightCol()))
243 event.Skip()
244
245 def Copy(self):
246 """ Copy the currently selected cells to the clipboard """
247 # TODO: raise an error when there are no cells selected?
248 if self._selected == None: return
249 ((r1, c1), (r2, c2)) = self._selected
250
251 # Build a string to put on the clipboard
252 # (Is there a faster way to do this in Python?)
253 crlf = chr(13) + chr(10)
254 tab = chr(9)
255 s = ""
256 for row in range(r1, r2+1):
257 for col in range(c1, c2):
258 s += self.GetCellValue(row,col)
259 s += tab
260 s += self.GetCellValue(row, c2)
261 s += crlf
262
263 # Put the string on the clipboard
b881fc78
RD
264 if wx.TheClipboard.Open():
265 wx.TheClipboard.Clear()
266 wx.TheClipboard.SetData(wx.TextDataObject(s))
267 wx.TheClipboard.Close()
d14a1e28
RD
268
269 def Paste(self):
270 """ Paste the contents of the clipboard into the currently selected cells """
271 # (Is there a better way to do this?)
b881fc78
RD
272 if wx.TheClipboard.Open():
273 td = wx.TextDataObject()
274 success = wx.TheClipboard.GetData(td)
275 wx.TheClipboard.Close()
d14a1e28
RD
276 if not success: return # Exit on failure
277 s = td.GetText() # Get the text
278
279 crlf = chr(13) + chr(10) # CrLf characters
280 tab = chr(9) # Tab character
281
282 rows = s.split(crlf) # split into rows
283 rows = rows[0:-1] # leave out last element, which is always empty
284 for i in range(0, len(rows)): # split rows into elements
285 rows[i] = rows[i].split(tab)
286
287 # Get the starting and ending cell range to paste into
288 if self._selected == None: # If no cells selected...
289 r1 = self.GetGridCursorRow() # Start the paste at the current location
290 c1 = self.GetGridCursorCol()
291 r2 = self.GetNumberRows()-1 # Go to maximum row and col extents
292 c2 = self.GetNumberCols()-1
293 else: # If cells selected, only paste there
294 ((r1, c1), (r2, c2)) = self._selected
295
296 # Enter data into spreadsheet cells one at a time
297 r = r1 # Init row and column counters
298 c = c1
299 for row in rows: # Loop over all rows
300 for element in row: # Loop over all row elements
301 self.SetCellValue(r, c, str(element)) # Set cell value
302 c += 1 # Increment the column counter
303 if c > c2: break # Do not exceed maximum column
304 r += 1
305 if r > r2: break # Do not exceed maximum row
306 c = c1
307
308 def Clear(self):
309 """ Clear the currently selected cells """
310 if self._selected == None: # If no selection...
311 r = self.GetGridCursorRow() # clear only current cell
312 c = self.GetGridCursorCol()
313 self.SetCellValue(r, c, "")
314 else: # Otherwise clear selected cells
315 ((r1, c1), (r2, c2)) = self._selected
316 for r in range(r1, r2+1):
317 for c in range(c1, c2+1):
318 self.SetCellValue(r, c, "")
319
320 def SetNumberRows(self, numRows=1):
321 """ Set the number of rows in the sheet """
322 # Check for non-negative number
323 if numRows < 0: return False
324
325 # Adjust number of rows
326 curRows = self.GetNumberRows()
327 if curRows < numRows:
328 self.AppendRows(numRows - curRows)
329 elif curRows > numRows:
330 self.DeleteRows(numRows, curRows - numRows)
331
332 return True
333
334 def SetNumberCols(self, numCols=1):
335 """ Set the number of columns in the sheet """
336 # Check for non-negative number
337 if numCols < 0: return False
338
339 # Adjust number of rows
340 curCols = self.GetNumberCols()
341 if curCols < numCols:
342 self.AppendCols(numCols - curCols)
343 elif curCols > numCols:
344 self.DeleteCols(numCols, curCols - numCols)
345
346 return True