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