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