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) 
   8 # o 2.5 compatability update. 
  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
) 
  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 
  40         evt
.Skip()                                  # Continue event 
  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 
  49     def Create(self
, parent
, id, evtHandler
): 
  50         """ Create the actual edit control.  Must derive from wxControl. 
  53         self
._tc 
= CTextCellEditor(parent
, id, self
._grid
) 
  54         self
._tc
.SetInsertionPoint(0) 
  55         self
.SetControl(self
._tc
) 
  57             self
._tc
.PushEventHandler(evtHandler
) 
  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) 
  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. 
  68         self
.base_Show(show
, attr
) 
  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 
  75         # Call base class method. 
  76         self
.base_PaintBackground(self
, rect
, attr
) 
  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. 
  82         self
._startValue 
= grid
.GetTable().GetValue(row
, col
) 
  83         self
._tc
.SetValue(self
._startValue
) 
  86         # Select the text when initiating an edit so that subsequent typing 
  87         # replaces the contents. 
  88         self
._tc
.SetSelection(0, self
._tc
.GetLastPosition()) 
  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. 
  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 
 105         """ Reset the value in the control back to its starting value. Must Override. """ 
 106         self
._tc
.SetValue(self
._startValue
) 
 107         self
._tc
.SetInsertionPointEnd() 
 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. 
 114         return (not (evt
.ControlDown() or evt
.AltDown()) 
 115                 and  evt
.GetKeyCode() != wx
.WXK_SHIFT
) 
 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. 
 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
) 
 128         elif key 
== wx
.WXK_BACK
:               # Empty text control when init w/ back key 
 131         elif key 
< 256 and key 
>= 0 and chr(key
) in string
.printable
: 
 133             if not evt
.ShiftDown(): 
 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 
 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. 
 149         """ Final cleanup """ 
 153         """ Create a new object which is the copy of this one. Must Override. """ 
 156 #--------------------------------------------------------------------------- 
 157 class CSheet(wx
.grid
.Grid
): 
 158     def __init__(self
, parent
): 
 159         wx
.grid
.Grid
.__init
__(self
, parent
, -1) 
 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(), 
 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) 
 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
) 
 188     def OnGridSelectCell(self
, event
): 
 189         """ Track cell selections """ 
 190         # Save the last cell coordinates 
 191         self
._lastRow
, self
._lastCol 
= event
.GetRow(), event
.GetCol() 
 194     def OnRowSize(self
, event
): 
 197     def OnColSize(self
, event
): 
 200     def OnCellChange(self
, event
): 
 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()) 
 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() 
 212     def OnRightClick(self
, event
): 
 213         """ Move grid cursor when a cell is right-clicked """ 
 214         self
.SetGridCursor( event
.GetRow(), event
.GetCol() ) 
 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 
 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())) 
 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
 
 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) 
 249         for row 
in range(r1
, r2
+1): 
 250             for col 
in range(c1
, c2
): 
 251                 s 
+= self
.GetCellValue(row
,col
) 
 253             s 
+= self
.GetCellValue(row
, c2
) 
 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() 
 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 
 272             crlf 
= chr(13) + chr(10)            # CrLf characters 
 273             tab 
= chr(9)                        # Tab character 
 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
) 
 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
 
 289             # Enter data into spreadsheet cells one at a time 
 290             r 
= r1                              
# Init row and column counters 
 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 
 298                 if r 
> r2
: break                # Do not exceed maximum row 
 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
, "") 
 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 
 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
) 
 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 
 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
)