data in Python source code, and the generated code now uses streams to
convert the image data to wxImage, wxBitmap, or wxIcon.
+Added wxPython.lib.rcsizer which contains RowColSizer. This sizer is
+based on code from Niki Spahiev and lets you specify a row and column
+for each item, as well as optional column or row spanning. Cells with
+not item assigned to it are just left blank. Stretchable rows or
+columns are specified and work the same as in wxFlexGridSizer.
+
2.3.2.1
_treeList = [
('New since last release', ['wxGenericDirCtrl',
'wxImageFromStream',
+ 'RowColSizer',
]),
('Windows', ['wxFrame', 'wxDialog', 'wxMiniFrame',
'wxEditableListBox', 'wxLEDNumberCtrl',
]),
- ('Window Layout', ['wxLayoutConstraints', 'LayoutAnchors', 'Sizers', 'XML_Resource']),
+ ('Window Layout', ['wxLayoutConstraints', 'LayoutAnchors', 'Sizers', 'XML_Resource',
+ 'RowColSizer',
+ ]),
('Miscellaneous', [ 'DragAndDrop', 'CustomDragAndDrop', 'URLDragAndDrop',
'FontEnumerator',
--- /dev/null
+
+from wxPython.wx import *
+from wxPython.lib.rcsizer import RowColSizer
+
+
+#----------------------------------------------------------------------
+
+class TestPanel(wxPanel):
+ def __init__(self, parent):
+ wxPanel.__init__(self, parent, -1)
+
+ sizer = RowColSizer()
+ text = "This sizer lays out it's items by row and column that are"\
+ "specified explicitly when the item is added to the sizer.\n"\
+ "Grid cells with nothing in them are supported and column-"\
+ "or row-spanning is handled as well. Growable rows and\n"\
+ "columns are specified just like the wxFlexGridSizer."
+ sizer.Add(wxStaticText(self, -1, text), row=1, col=1, colspan=5)
+
+ sizer.Add(wxTextCtrl(self, -1, "(3,1)"), flag=wxEXPAND, row=3, col=1)
+ sizer.Add(wxTextCtrl(self, -1, "(3,2)"), row=3, col=2)
+ sizer.Add(wxTextCtrl(self, -1, "(3,3)"), row=3, col=3)
+ sizer.Add(wxTextCtrl(self, -1, "(3,4)"), row=3, col=4)
+ sizer.Add(wxTextCtrl(self, -1, "(4,2) span:(2,2)"), flag=wxEXPAND,
+ row=4, col=2, rowspan=2, colspan=2)
+ sizer.Add(wxTextCtrl(self, -1, "(6,4)"), row=6, col=4)
+ sizer.Add(wxTextCtrl(self, -1, "(7,2)"), row=7, col=2)
+ sizer.Add(wxTextCtrl(self, -1, "(8,3)"), row=8, col=3)
+ sizer.Add(wxTextCtrl(self, -1, "(10,1) colspan: 4"), flag=wxEXPAND, pos=(10,1), colspan=4)
+ sizer.Add(wxTextCtrl(self, -1, "(3,5) rowspan: 8, growable col", style=wxTE_MULTILINE),
+ flag=wxEXPAND, pos=(3,5), size=(8,1))
+
+ box = wxBoxSizer(wxVERTICAL)
+ box.Add(wxButton(self, -1, "A vertical box"), flag=wxEXPAND)
+ box.Add(wxButton(self, -1, "sizer put in the"), flag=wxEXPAND)
+ box.Add(wxButton(self, -1, "RowColSizer at (12,1)"), flag=wxEXPAND)
+ sizer.Add(box, pos=(12,1))
+
+ sizer.Add(wxTextCtrl(self, -1, "(12,2) align bottom"), flag=wxALIGN_BOTTOM, pos=(12,2))
+ sizer.Add(wxTextCtrl(self, -1, "(12,3) align center"), flag=wxALIGN_CENTER_VERTICAL, pos=(12,3))
+ sizer.Add(wxTextCtrl(self, -1, "(12,4)"),pos=(12,4))
+ sizer.Add(wxTextCtrl(self, -1, "(12,5) full border"), flag=wxEXPAND|wxALL, border=15, pos=(12,5))
+
+ sizer.AddGrowableCol(5)
+ sizer.AddGrowableRow(9)
+
+ sizer.AddSpacer(10,10, pos=(1,6))
+ sizer.AddSpacer(10,10, pos=(13,1))
+
+ self.SetSizer(sizer)
+ self.SetAutoLayout(true)
+
+
+#----------------------------------------------------------------------
+
+def runTest(frame, nb, log):
+ win = TestPanel(nb)
+ return win
+
+
+#----------------------------------------------------------------------
+
+
+import wxPython.lib.rcsizer
+overview = wxPython.lib.rcsizer.__doc__
+
+
class wxBitmap : public wxGDIObject
{
public:
- wxBitmap(const wxString& name, wxBitmapType type=wxBITMAP_TYPE_BMP);
+ wxBitmap(const wxString& name, wxBitmapType type=wxBITMAP_TYPE_ANY);
~wxBitmap();
wxPalette* GetPalette();
wxMask* GetMask();
- bool LoadFile(const wxString& name, wxBitmapType type=wxBITMAP_TYPE_BMP);
+ bool LoadFile(const wxString& name, wxBitmapType type=wxBITMAP_TYPE_ANY);
bool SaveFile(const wxString& name, wxBitmapType type, wxPalette* palette = NULL);
void SetMask(wxMask* mask);
#ifdef __WXMSW__
#----------------------------------------------------------------------
"""
-In this module you will find wxGridSizer and wxFlexGridSizer.
+In this module you will find wxGridSizer and wxFlexGridSizer. Please
+note that these sizers have since been ported to C++ and those
+versions are now exposed in the regular wxPython wrappers. However I
+am also leaving them here in the library so they can serve as an
+example of how to implement sizers in Python.
wxGridSizer: Sizes and positions items such that all rows are the same
height and all columns are the same width. You can specify a gap in
--- /dev/null
+#----------------------------------------------------------------------
+# Name: wxPython.lib.rcsizer
+# Purpose: RowColSizer:
+#
+# Author: Robin Dunn, adapted from code by Niki Spahiev
+#
+# Created: 26-Feb-2002
+# RCS-ID: $Id$
+# Copyright: (c) 2002 by Total Control Software
+# Licence: wxWindows license
+#----------------------------------------------------------------------
+
+"""
+A pure-Python wxSizer that lays out items in a grid similar to
+wxFlexGridSizer but item position is not implicit but explicitly
+specified by row and col, and row/col spanning is supported.
+
+Adapted from code by Niki Spahiev.
+"""
+
+
+
+from wxPython.wx import *
+import operator
+
+#----------------------------------------------------------------------
+
+class RowColSizer(wxPySizer):
+
+ # default sizes for cells with no item
+ col_w = 10
+ row_h = 22
+
+ def __init__(self):
+ wxPySizer.__init__(self)
+ self.growableRows = []
+ self.growableCols = []
+
+
+ def AddGrowableRow(self, idx):
+ self.growableRows.append(idx)
+
+ def AddGrowableCol(self, idx):
+ self.growableCols.append(idx)
+
+
+
+ #--------------------------------------------------
+ def Add(self, item, option=0, flag=0, border=0,
+ row=-1, col=-1, # row, col and spanning can be specified individually...
+ rowspan=1, colspan=1,
+ pos=None, size=None, # or as tuples (row,col) and (rowspan,colspan)
+ ):
+
+ if pos is not None:
+ row, col = pos
+ if size is not None:
+ rowspan, colspan = size
+
+ assert row != -1, "Row must be specified"
+ assert col != -1, "Column must be specified"
+
+ # Do I really want to do this? Probably not...
+ #if rowspan > 1 or colspan > 1:
+ # flag = flag | wxEXPAND
+
+ wxPySizer.Add(self, item, option, flag, border,
+ userData=(row, col, row+rowspan, col+colspan))
+
+ #AddWindow = Add
+ #AddSizer = Add
+
+ def AddSpacer(self, width, height, option=0, flag=0, border=0,
+ row=-1, col=-1,
+ rowspan=1, colspan=1,
+ pos=None, size=None,
+ ):
+ if pos is not None:
+ row, col = pos
+ if size is not None:
+ rowspan, colspan = size
+
+ assert row != -1, "Row must be specified"
+ assert col != -1, "Column must be specified"
+
+ wxPySizer.AddSpacer(self, width, height, option, flag, border,
+ userData=(row, col, row+rowspan, col+colspan))
+
+ #--------------------------------------------------
+ def _add( self, size, dim ):
+ r, c, r2, c2 = dim # unpack coords and spanning
+
+ # are the widths and heights lists long enough?
+ if r2 > len(self.rowHeights):
+ x = [self.row_h] * (r2-len(self.rowHeights))
+ self.rowHeights.extend( x )
+ if c2 > len(self.colWidths):
+ x = [self.col_w] * (c2-len(self.colWidths))
+ self.colWidths.extend( x )
+
+ # set the widths and heights lists for this item
+ scale = (r2 - r)
+ for i in range(r, r2):
+ self.rowHeights[i] = max( self.rowHeights[i], size.height / scale )
+ scale = (c2 - c)
+ for i in range(c, c2):
+ self.colWidths[i] = max( self.colWidths[i], size.width / scale )
+
+
+ #--------------------------------------------------
+ def CalcMin( self ):
+ items = self.GetChildren()
+ if not items:
+ return wxSize(10, 10)
+
+ self.rowHeights = []
+ self.colWidths = []
+
+ for item in items:
+ self._add( item.CalcMin(), item.GetUserData() )
+
+ size = wxSize( reduce( operator.add, self.colWidths),
+ reduce( operator.add, self.rowHeights) )
+ return size
+
+
+ #--------------------------------------------------
+ def RecalcSizes( self ):
+ # save current dimensions, etc.
+ curWidth = self.GetSize().width
+ curHeight = self.GetSize().height
+ px = self.GetPosition().x
+ py = self.GetPosition().y
+ minWidth = self.CalcMin().width
+ minHeight = self.CalcMin().height
+
+ # Check for growables
+ if self.growableRows and curHeight > minHeight:
+ delta = (curHeight - minHeight) / len(self.growableRows)
+ extra = (curHeight - minHeight) % len(self.growableRows)
+ for idx in self.growableRows:
+ self.rowHeights[idx] += delta
+ self.rowHeights[self.growableRows[0]] += extra
+
+ if self.growableCols and curWidth > minWidth:
+ delta = (curWidth - minWidth) / len(self.growableCols)
+ extra = (curWidth - minWidth) % len(self.growableCols)
+ for idx in self.growableCols:
+ self.colWidths[idx] += delta
+ self.colWidths[self.growableCols[0]] += extra
+
+ rpos = [0] * len(self.rowHeights)
+ cpos = [0] * len(self.colWidths)
+
+ for i in range(len(self.rowHeights)):
+ height = self.rowHeights[i]
+ rpos[i] = px
+ px += height
+
+ for i in range(len(self.colWidths)):
+ width = self.colWidths[i]
+ cpos[i] = py
+ py += width
+
+ # iterate children and set dimensions...
+ for item in self.GetChildren():
+ r, c, r2, c2 = item.GetUserData()
+ width = reduce( operator.add, self.colWidths[c:c2] )
+ height = reduce( operator.add, self.rowHeights[r:r2] )
+ #item.SetDimension( (cpos[c], rpos[r]), (width, height))
+ self.SetItemBounds( item, cpos[c], rpos[r], width, height )
+
+
+ #--------------------------------------------------
+ def SetItemBounds(self, item, x, y, w, h):
+ # calculate the item's actual size and position within
+ # its grid cell
+ ipt = wxPoint(x, y)
+ isz = item.CalcMin()
+ flag = item.GetFlag()
+
+ if flag & wxEXPAND or flag & wxSHAPED:
+ isz = wxSize(w, h)
+ else:
+ if flag & wxALIGN_CENTER_HORIZONTAL:
+ ipt.x = x + (w - isz.width) / 2
+ elif flag & wxALIGN_RIGHT:
+ ipt.x = x + (w - isz.width)
+
+ if flag & wxALIGN_CENTER_VERTICAL:
+ ipt.y = y + (h - isz.height) / 2
+ elif flag & wxALIGN_BOTTOM:
+ ipt.y = y + (h - isz.height)
+
+ item.SetDimension(ipt, isz)
+
+
+#----------------------------------------------------------------------
+#----------------------------------------------------------------------
+
+
+
+
+## #--------------------------------------------------
+## def _add( self, size, opt, dim ):
+## r,c,r2,c2 = dim
+## if r2 > len(self.rows0):
+## x = [self.row_h] * (r2-len(self.rows0))
+## self.rows0.extend( x )
+## self.rows1.extend( x )
+## if c2 > len(self.cols0):
+## x = [self.col_w] * (c2-len(self.cols0))
+## self.cols0.extend( x )
+## self.cols1.extend( x )
+## if opt == 0: # fixed
+## scale = (r2-r)
+## for i in range(r,r2):
+## self.rows1[i] = self.rows0[i] = max( self.rows0[i], size.y/scale )
+## scale = (c2-c)
+## for i in range(c,c2):
+## self.cols1[i] = self.cols0[i] = max( self.cols0[i], size.x/scale )
+## else:
+## scale = (r2-r)
+## for i in range(r,r2):
+## self.rows0[i] = max( self.rows0[i], size.y/scale )
+## self.rows1[i] = self.rows0[i] * opt
+## scale = (c2-c)
+## for i in range(c,c2):
+## self.cols0[i] = max( self.cols0[i], size.x/scale )
+## self.cols1[i] = self.cols0[i] * opt
+
+
+## #--------------------------------------------------
+## def CalcMin( self ):
+## children = self.GetChildren()
+## if not children:
+## return wxSize(10, 10)
+
+## self.rows0 = []
+## self.cols0 = []
+## self.rows1 = []
+## self.cols1 = []
+
+## for cell in children:
+## self._add( cell.CalcMin(), cell.GetOption(), cell.GetUserData() )
+
+## self.minWidth = reduce( operator.add, self.cols1 )
+## self.minHeight = reduce( operator.add, self.rows1 )
+## self.fixedWidth = reduce( operator.add, self.cols0 ) # size without stretched widgets
+## self.fixedHeight = reduce( operator.add, self.rows0 )
+
+## return wxSize( self.minWidth, self.minHeight )
+
+
+## #--------------------------------------------------
+## def RecalcSizes( self ):
+## # get current dimensions, save for performance
+## myWidth = self.GetSize().x
+## myHeight = self.GetSize().y
+
+## # relative recent positions
+## px = self.GetPosition().x
+## py = self.GetPosition().y
+
+## # calculate space for one stretched item
+## stretchC = 0
+## for i in range(len(self.cols0)):
+## if self.cols1[i] <> self.cols0[i]:
+## stretchC = stretchC + self.cols1[i] / self.cols0[i]
+## if myWidth > self.fixedWidth and stretchC:
+## deltaw = (myWidth - self.fixedWidth) / stretchC
+## extraw = (myWidth - self.fixedWidth) % stretchC
+## else:
+## deltaw = extraw = 0
+
+## stretchR = 0
+## for i in range(len(self.rows0)):
+## if self.rows1[i] <> self.rows0[i]:
+## stretchR = stretchR + self.rows1[i] / self.rows0[i]
+## if myHeight > self.fixedHeight and stretchR:
+## deltah = (myHeight - self.fixedHeight) / stretchR
+## extrah = (myHeight - self.fixedHeight) % stretchR
+## else:
+## deltah = extrah = 0
+
+## self.rpos = [0] * len( self.rows0 )
+## self.cpos = [0] * len( self.cols0 )
+
+## for i in range(len(self.rows0)):
+## newHeight = self.rows0[i]
+## if self.rows1[i] <> self.rows0[i]:
+## weight = self.rows1[i] / self.rows0[i]
+## # first stretchable gets extra pixels
+## newHeight = newHeight + (deltah * weight) + extrah
+## extrah = 0
+## self.rpos[i] = py
+## self.rows1[i] = newHeight
+## py = py + newHeight
+
+## for i in range(len(self.cols0)):
+## newWidth = self.cols0[i]
+## if self.cols1[i] <> self.cols0[i]:
+## weight = self.cols1[i] / self.cols0[i]
+## # first stretchable gets extra pixels
+## newWidth = newWidth + (deltaw * weight) + extraw
+## extraw = 0
+## self.cpos[i] = px
+## self.cols1[i] = newWidth
+## px = px + newWidth
+
+## # iterate children ...
+## for cell in self.GetChildren():
+## r,c,r2,c2 = cell.GetUserData()
+## newWidth = reduce( operator.add, self.cols1[c:c2] )
+## newHeight = reduce( operator.add, self.rows1[r:r2] )
+## cell.SetDimension( (self.cpos[c], self.rpos[r]), (newWidth, newHeight) )
+
--- /dev/null
+# 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
+
+from wxPython.wx import *
+from wxPython.grid import *
+from string import *
+
+#---------------------------------------------------------------------------
+class CTextCellEditor(wxTextCtrl):
+ """ Custom text control for cell editing """
+ def __init__(self, parent, id, grid):
+ wxTextCtrl.__init__(self, parent, id, "", style=wxNO_BORDER)
+ self._grid = grid # Save grid reference
+ EVT_CHAR(self, self.OnChar)
+
+ def OnChar(self, evt): # Hook OnChar for custom behavior
+ """Customizes char events """
+ key = evt.GetKeyCode()
+ if key == WXK_DOWN:
+ self._grid.DisableCellEditControl() # Commit the edit
+ self._grid.MoveCursorDown(false) # Change the current cell
+ elif key == WXK_UP:
+ self._grid.DisableCellEditControl() # Commit the edit
+ self._grid.MoveCursorUp(false) # Change the current cell
+ elif key == WXK_LEFT:
+ self._grid.DisableCellEditControl() # Commit the edit
+ self._grid.MoveCursorLeft(false) # Change the current cell
+ elif key == WXK_RIGHT:
+ self._grid.DisableCellEditControl() # Commit the edit
+ self._grid.MoveCursorRight(false) # Change the current cell
+
+ evt.Skip() # Continue event
+
+#---------------------------------------------------------------------------
+class CCellEditor(wxPyGridCellEditor):
+ """ Custom cell editor """
+ def __init__(self, grid):
+ wxPyGridCellEditor.__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() != 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 [WXK_NUMPAD0, WXK_NUMPAD1, WXK_NUMPAD2, WXK_NUMPAD3, WXK_NUMPAD4,
+ WXK_NUMPAD5, WXK_NUMPAD6, WXK_NUMPAD7, WXK_NUMPAD8, WXK_NUMPAD9]:
+ ch = chr(ord('0') + key - WXK_NUMPAD0)
+
+ elif key == 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 = string.lower(ch)
+
+ 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(wxGrid):
+ def __init__(self, parent):
+ wxGrid.__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(wxGRID_VALUE_STRING,
+ wxGridCellStringRenderer(),
+ 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(wxALIGN_RIGHT, wxALIGN_BOTTOM)
+ self.SetColSize(0, 75) # Default column sizes
+ self.SetColSize(1, 75)
+ self.SetColSize(2, 75)
+
+ # Sink events
+ EVT_GRID_CELL_LEFT_CLICK( self, self.OnLeftClick)
+ EVT_GRID_CELL_RIGHT_CLICK( self, self.OnRightClick)
+ EVT_GRID_CELL_LEFT_DCLICK( self, self.OnLeftDoubleClick)
+ EVT_GRID_RANGE_SELECT( self, self.OnRangeSelect)
+ EVT_GRID_ROW_SIZE( self, self.OnRowSize)
+ EVT_GRID_COL_SIZE( self, self.OnColSize)
+ EVT_GRID_CELL_CHANGE( self, self.OnCellChange)
+ EVT_GRID_SELECT_CELL( self, 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 wxTheClipboard.Open():
+ wxTheClipboard.Clear()
+ wxTheClipboard.SetData(wxTextDataObject(s))
+ wxTheClipboard.Close()
+
+ def Paste(self):
+ """ Paste the contents of the clipboard into the currently selected cells """
+ # (Is there a better way to do this?)
+ if wxTheClipboard.Open():
+ td = wxTextDataObject()
+ success = wxTheClipboard.GetData(td)
+ wxTheClipboard.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 = split(s, 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] = split(rows[i], 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