]> git.saurik.com Git - wxWidgets.git/blobdiff - wxPython/wx/lib/sheet.py
"wxWindows" --> "wxWidgets"
[wxWidgets.git] / wxPython / wx / lib / sheet.py
index 8e05440f449970a2ac0cb55d6292c25bf6a57dd2..43a2e61f08d5062725bff1782ad100dfb740c7d0 100644 (file)
@@ -1,8 +1,339 @@
+# sheet.py
+# CSheet - A wxPython spreadsheet class.
+# This is free software.  Feel free to adapt it as you like.
+# Author: Mark F. Russo (russomf@hotmail.com) 2002/01/31
+#---------------------------------------------------------------------------
+# 12/11/2003 - Jeff Grimmett (grimmtooth@softhome.net)
+#
+# o 2.5 compatability update.
+# o Untested.
+#
 
 
-"""Renamer stub: provides a way to drop the wx prefix from wxPython objects."""
+import  string
+import  wx
+import  wx.grid
 
 
-from wx import _rename
-from wxPython.lib import sheet
-_rename(globals(), sheet.__dict__, modulename='lib.sheet')
-del sheet
-del _rename
+#---------------------------------------------------------------------------
+class CTextCellEditor(wx.TextCtrl):
+    """ Custom text control for cell editing """
+    def __init__(self, parent, id, grid):
+        wx.TextCtrl.__init__(self, parent, id, "", style=wx.NO_BORDER)
+        self._grid = grid                           # Save grid reference
+        self.Bind(wx.EVT_CHAR, self.OnChar)
+
+    def OnChar(self, evt):                          # Hook OnChar for custom behavior
+        """Customizes char events """
+        key = evt.GetKeyCode()
+        if   key == wx.WXK_DOWN:
+            self._grid.DisableCellEditControl()     # Commit the edit
+            self._grid.MoveCursorDown(False)        # Change the current cell
+        elif key == wx.WXK_UP:
+            self._grid.DisableCellEditControl()     # Commit the edit
+            self._grid.MoveCursorUp(False)          # Change the current cell
+        elif key == wx.WXK_LEFT:
+            self._grid.DisableCellEditControl()     # Commit the edit
+            self._grid.MoveCursorLeft(False)        # Change the current cell
+        elif key == wx.WXK_RIGHT:
+            self._grid.DisableCellEditControl()     # Commit the edit
+            self._grid.MoveCursorRight(False)       # Change the current cell
+
+        evt.Skip()                                  # Continue event
+
+#---------------------------------------------------------------------------
+class CCellEditor(wx.grid.PyGridCellEditor):
+    """ Custom cell editor """
+    def __init__(self, grid):
+        wx.grid.PyGridCellEditor.__init__(self)
+        self._grid = grid                           # Save a reference to the grid
+
+    def Create(self, parent, id, evtHandler):
+        """ Create the actual edit control.  Must derive from wxControl.
+            Must Override
+        """
+        self._tc = CTextCellEditor(parent, id, self._grid)
+        self._tc.SetInsertionPoint(0)
+        self.SetControl(self._tc)
+        if evtHandler:
+            self._tc.PushEventHandler(evtHandler)
+
+    def SetSize(self, rect):
+        """ Position/size the edit control within the cell rectangle. """
+        # Size text control to exactly overlay in-cell editing
+        self._tc.SetDimensions(rect.x+3, rect.y+3, rect.width-2, rect.height-2)
+
+    def Show(self, show, attr):
+        """ Show or hide the edit control.  Use the attr (if not None)
+            to set colors or fonts for the control.
+        """
+        self.base_Show(show, attr)
+
+    def PaintBackground(self, rect, attr):
+        """ Draws the part of the cell not occupied by the edit control.  The
+            base class version just fills it with background colour from the
+            attribute.
+        """
+        # Call base class method.
+        self.base_PaintBackground(self, rect, attr)
+
+    def BeginEdit(self, row, col, grid):
+        """ Fetch the value from the table and prepare edit control to begin editing.
+            Set the focus to the edit control.  Must Override.
+        """
+        self._startValue = grid.GetTable().GetValue(row, col)
+        self._tc.SetValue(self._startValue)
+        self._tc.SetFocus()
+
+        # Select the text when initiating an edit so that subsequent typing
+        # replaces the contents.
+        self._tc.SetSelection(0, self._tc.GetLastPosition())
+
+    def EndEdit(self, row, col, grid):
+        """ Commit editing the current cell. Returns True if the value has changed.
+            If necessary, the control may be destroyed. Must Override.
+        """
+        changed = False                             # Assume value not changed
+        val = self._tc.GetValue()                   # Get value in edit control
+        if val != self._startValue:                 # Compare
+            changed = True                          # If different then changed is True
+            grid.GetTable().SetValue(row, col, val) # Update the table
+        self._startValue = ''                       # Clear the class' start value
+        self._tc.SetValue('')                       # Clear contents of the edit control
+
+        return changed
+
+    def Reset(self):
+        """ Reset the value in the control back to its starting value. Must Override. """
+        self._tc.SetValue(self._startValue)
+        self._tc.SetInsertionPointEnd()
+
+    def IsAcceptedKey(self, evt):
+        """ Return True to allow the given key to start editing.  The base class
+            version only checks that the event has no modifiers.  F2 is special
+            and will always start the editor.
+        """
+        return (not (evt.ControlDown() or evt.AltDown())
+                and  evt.GetKeyCode() != wx.WXK_SHIFT)
+
+    def StartingKey(self, evt):
+        """ If the editor is enabled by pressing keys on the grid, this will be
+            called to let the editor react to that first key.
+        """
+        key = evt.GetKeyCode()              # Get the key code
+        ch = None                           # Handle num pad keys
+        if key in [ wx.WXK_NUMPAD0, wx.WXK_NUMPAD1, wx.WXK_NUMPAD2, wx.WXK_NUMPAD3, 
+                    wx.WXK_NUMPAD4, wx.WXK_NUMPAD5, wx.WXK_NUMPAD6, wx.WXK_NUMPAD7, 
+                    wx.WXK_NUMPAD8, wx.WXK_NUMPAD9]:
+            ch = chr(ord('0') + key - wx.WXK_NUMPAD0)
+
+        elif key == wx.WXK_BACK:               # Empty text control when init w/ back key
+            ch = ""
+                                            # Handle normal keys
+        elif key < 256 and key >= 0 and chr(key) in string.printable:
+            ch = chr(key)
+            if not evt.ShiftDown():
+                ch = ch.lower()
+
+        if ch is not None:                  # If are at this point with a key,
+            self._tc.SetValue(ch)           # replace the contents of the text control.
+            self._tc.SetInsertionPointEnd() # Move to the end so that subsequent keys are appended
+        else:
+            evt.Skip()
+
+    def StartingClick(self):
+        """ If the editor is enabled by clicking on the cell, this method will be
+            called to allow the editor to simulate the click on the control.
+        """
+        pass
+
+    def Destroy(self):
+        """ Final cleanup """
+        self.base_Destroy()
+
+    def Clone(self):
+        """ Create a new object which is the copy of this one. Must Override. """
+        return CCellEditor()
+
+#---------------------------------------------------------------------------
+class CSheet(wx.grid.Grid):
+    def __init__(self, parent):
+        wx.grid.Grid.__init__(self, parent, -1)
+
+        # Init variables
+        self._lastCol = -1              # Init last cell column clicked
+        self._lastRow = -1              # Init last cell row clicked
+        self._selected = None           # Init range currently selected
+                                        # Map string datatype to default renderer/editor
+        self.RegisterDataType(wx.grid.GRID_VALUE_STRING,
+                              wx.grid.GridCellStringRenderer(),
+                              CCellEditor(self))
+
+        self.CreateGrid(4, 3)           # By default start with a 4 x 3 grid
+        self.SetColLabelSize(18)        # Default sizes and alignment
+        self.SetRowLabelSize(50)
+        self.SetRowLabelAlignment(wx.ALIGN_RIGHT, wx.ALIGN_BOTTOM)
+        self.SetColSize(0, 75)          # Default column sizes
+        self.SetColSize(1, 75)
+        self.SetColSize(2, 75)
+
+        # Sink events
+        self.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnLeftClick)
+        self.Bind(wx.grid.EVT_GRID_CELL_RIGHT_CLICK, self.OnRightClick)
+        self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.OnLeftDoubleClick)
+        self.Bind(wx.grid.EVT_GRID_RANGE_SELECT, self.OnRangeSelect)
+        self.Bind(wx.grid.EVT_GRID_ROW_SIZE, self.OnRowSize)
+        self.Bind(wx.grid.EVT_GRID_COL_SIZE, self.OnColSize)
+        self.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.OnCellChange)
+        self.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.OnGridSelectCell)
+
+    def OnGridSelectCell(self, event):
+        """ Track cell selections """
+        # Save the last cell coordinates
+        self._lastRow, self._lastCol = event.GetRow(), event.GetCol()
+        event.Skip()
+
+    def OnRowSize(self, event):
+        event.Skip()
+
+    def OnColSize(self, event):
+        event.Skip()
+
+    def OnCellChange(self, event):
+        event.Skip()
+
+    def OnLeftClick(self, event):
+        """ Override left-click behavior to prevent left-click edit initiation """
+        # Save the cell clicked
+        currCell = (event.GetRow(), event.GetCol())
+
+        # Suppress event if same cell clicked twice in a row.
+        # This prevents a single-click from initiating an edit.
+        if currCell != (self._lastRow, self._lastCol): event.Skip()
+
+    def OnRightClick(self, event):
+        """ Move grid cursor when a cell is right-clicked """
+        self.SetGridCursor( event.GetRow(), event.GetCol() )
+        event.Skip()
+
+    def OnLeftDoubleClick(self, event):
+        """ Initiate the cell editor on a double-click """
+        # Move grid cursor to double-clicked cell
+        if self.CanEnableCellControl():
+            self.SetGridCursor( event.GetRow(), event.GetCol() )
+            self.EnableCellEditControl(True)    # Show the cell editor
+        event.Skip()
+
+    def OnRangeSelect(self, event):
+        """ Track which cells are selected so that copy/paste behavior can be implemented """
+        # If a single cell is selected, then Selecting() returns False (0)
+        # and range coords are entire grid.  In this case cancel previous selection.
+        # If more than one cell is selected, then Selecting() is True (1)
+        # and range accurately reflects selected cells.  Save them.
+        # If more cells are added to a selection, selecting remains True (1)
+        self._selected = None
+        if event.Selecting():
+            self._selected = ((event.GetTopRow(), event.GetLeftCol()),
+                              (event.GetBottomRow(), event.GetRightCol()))
+        event.Skip()
+
+    def Copy(self):
+        """ Copy the currently selected cells to the clipboard """
+        # TODO: raise an error when there are no cells selected?
+        if self._selected == None: return
+        ((r1, c1), (r2, c2)) = self._selected
+
+        # Build a string to put on the clipboard
+        # (Is there a faster way to do this in Python?)
+        crlf = chr(13) + chr(10)
+        tab = chr(9)
+        s = ""
+        for row in range(r1, r2+1):
+            for col in range(c1, c2):
+                s += self.GetCellValue(row,col)
+                s += tab
+            s += self.GetCellValue(row, c2)
+            s += crlf
+
+        # Put the string on the clipboard
+        if wx.TheClipboard.Open():
+            wx.TheClipboard.Clear()
+            wx.TheClipboard.SetData(wx.TextDataObject(s))
+            wx.TheClipboard.Close()
+
+    def Paste(self):
+        """ Paste the contents of the clipboard into the currently selected cells """
+        # (Is there a better way to do this?)
+        if wx.TheClipboard.Open():
+            td = wx.TextDataObject()
+            success = wx.TheClipboard.GetData(td)
+            wx.TheClipboard.Close()
+            if not success: return              # Exit on failure
+            s = td.GetText()                    # Get the text
+
+            crlf = chr(13) + chr(10)            # CrLf characters
+            tab = chr(9)                        # Tab character
+
+            rows = s.split(crlf)               # split into rows
+            rows = rows[0:-1]                   # leave out last element, which is always empty
+            for i in range(0, len(rows)):       # split rows into elements
+                rows[i] = rows[i].split(tab)
+
+            # Get the starting and ending cell range to paste into
+            if self._selected == None:          # If no cells selected...
+                r1 = self.GetGridCursorRow()    # Start the paste at the current location
+                c1 = self.GetGridCursorCol()
+                r2 = self.GetNumberRows()-1     # Go to maximum row and col extents
+                c2 = self.GetNumberCols()-1
+            else:                               # If cells selected, only paste there
+                ((r1, c1), (r2, c2)) = self._selected
+
+            # Enter data into spreadsheet cells one at a time
+            r = r1                              # Init row and column counters
+            c = c1
+            for row in rows:                    # Loop over all rows
+                for element in row:             # Loop over all row elements
+                    self.SetCellValue(r, c, str(element))   # Set cell value
+                    c += 1                      # Increment the column counter
+                    if c > c2: break            # Do not exceed maximum column
+                r += 1
+                if r > r2: break                # Do not exceed maximum row
+                c = c1
+
+    def Clear(self):
+        """ Clear the currently selected cells """
+        if self._selected == None:              # If no selection...
+            r = self.GetGridCursorRow()         # clear only current cell
+            c = self.GetGridCursorCol()
+            self.SetCellValue(r, c, "")
+        else:                                   # Otherwise clear selected cells
+            ((r1, c1), (r2, c2)) = self._selected
+            for r in range(r1, r2+1):
+                for c in range(c1, c2+1):
+                    self.SetCellValue(r, c, "")
+
+    def SetNumberRows(self, numRows=1):
+        """ Set the number of rows in the sheet """
+        # Check for non-negative number
+        if numRows < 0:  return False
+
+        # Adjust number of rows
+        curRows = self.GetNumberRows()
+        if curRows < numRows:
+            self.AppendRows(numRows - curRows)
+        elif curRows > numRows:
+            self.DeleteRows(numRows, curRows - numRows)
+
+        return True
+
+    def SetNumberCols(self, numCols=1):
+        """ Set the number of columns in the sheet """
+        # Check for non-negative number
+        if numCols < 0:  return False
+
+        # Adjust number of rows
+        curCols = self.GetNumberCols()
+        if curCols < numCols:
+            self.AppendCols(numCols - curCols)
+        elif curCols > numCols:
+            self.DeleteCols(numCols, curCols - numCols)
+
+        return True