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