]>
git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/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
6 from wxPython
.wx
import *
7 from wxPython
.grid
import *
10 #---------------------------------------------------------------------------
11 class 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
)
18 def OnChar(self
, evt
): # Hook OnChar for custom behavior
19 """Customizes char events """
20 key
= evt
.GetKeyCode()
22 self
._grid
.DisableCellEditControl() # Commit the edit
23 self
._grid
.MoveCursorDown(False) # Change the current cell
25 self
._grid
.DisableCellEditControl() # Commit the edit
26 self
._grid
.MoveCursorUp(False) # Change the current cell
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
34 evt
.Skip() # Continue event
36 #---------------------------------------------------------------------------
37 class 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
43 def Create(self
, parent
, id, evtHandler
):
44 """ Create the actual edit control. Must derive from wxControl.
47 self
._tc
= CTextCellEditor(parent
, id, self
._grid
)
48 self
._tc
.SetInsertionPoint(0)
49 self
.SetControl(self
._tc
)
51 self
._tc
.PushEventHandler(evtHandler
)
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)
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.
62 self
.base_Show(show
, attr
)
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
69 # Call base class method.
70 self
.base_PaintBackground(self
, rect
, attr
)
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.
76 self
._startValue
= grid
.GetTable().GetValue(row
, col
)
77 self
._tc
.SetValue(self
._startValue
)
80 # Select the text when initiating an edit so that subsequent typing
81 # replaces the contents.
82 self
._tc
.SetSelection(0, self
._tc
.GetLastPosition())
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.
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
99 """ Reset the value in the control back to its starting value. Must Override. """
100 self
._tc
.SetValue(self
._startValue
)
101 self
._tc
.SetInsertionPointEnd()
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.
108 return (not (evt
.ControlDown() or evt
.AltDown())
109 and evt
.GetKeyCode() != WXK_SHIFT
)
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.
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
)
121 elif key
== WXK_BACK
: # Empty text control when init w/ back key
124 elif key
< 256 and key
>= 0 and chr(key
) in string
.printable
:
126 if not evt
.ShiftDown():
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
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.
142 """ Final cleanup """
146 """ Create a new object which is the copy of this one. Must Override. """
149 #---------------------------------------------------------------------------
150 class CSheet(wxGrid
):
151 def __init__(self
, parent
):
152 wxGrid
.__init
__(self
, parent
, -1)
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(),
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)
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
)
181 def OnGridSelectCell(self
, event
):
182 """ Track cell selections """
183 # Save the last cell coordinates
184 self
._lastRow
, self
._lastCol
= event
.GetRow(), event
.GetCol()
187 def OnRowSize(self
, event
):
190 def OnColSize(self
, event
):
193 def OnCellChange(self
, event
):
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())
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()
205 def OnRightClick(self
, event
):
206 """ Move grid cursor when a cell is right-clicked """
207 self
.SetGridCursor( event
.GetRow(), event
.GetCol() )
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
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()))
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
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)
242 for row
in range(r1
, r2
+1):
243 for col
in range(c1
, c2
):
244 s
+= self
.GetCellValue(row
,col
)
246 s
+= self
.GetCellValue(row
, c2
)
249 # Put the string on the clipboard
250 if wxTheClipboard
.Open():
251 wxTheClipboard
.Clear()
252 wxTheClipboard
.SetData(wxTextDataObject(s
))
253 wxTheClipboard
.Close()
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
265 crlf
= chr(13) + chr(10) # CrLf characters
266 tab
= chr(9) # Tab character
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
)
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
282 # Enter data into spreadsheet cells one at a time
283 r
= r1
# Init row and column counters
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
291 if r
> r2
: break # Do not exceed maximum row
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
, "")
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
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
)
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
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
)