From: Robin Dunn Date: Thu, 28 Feb 2002 19:01:19 +0000 (+0000) Subject: Added wxPython.lib.rcsizer which contains RowColSizer. This sizer is X-Git-Url: https://git.saurik.com/wxWidgets.git/commitdiff_plain/96de41c2c53c013293d452e3af55dd9c76e7030a 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. A few other minor changes too. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@14431 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- diff --git a/wxPython/CHANGES.txt b/wxPython/CHANGES.txt index 1097a4f938..bb655d30c2 100644 --- a/wxPython/CHANGES.txt +++ b/wxPython/CHANGES.txt @@ -22,6 +22,12 @@ Changed the img2py tool to use PNG instead of XPM for embedding image 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 diff --git a/wxPython/demo/Main.py b/wxPython/demo/Main.py index e46b1be9cf..14f9a919bb 100644 --- a/wxPython/demo/Main.py +++ b/wxPython/demo/Main.py @@ -23,6 +23,7 @@ import images _treeList = [ ('New since last release', ['wxGenericDirCtrl', 'wxImageFromStream', + 'RowColSizer', ]), ('Windows', ['wxFrame', 'wxDialog', 'wxMiniFrame', @@ -50,7 +51,9 @@ _treeList = [ 'wxEditableListBox', 'wxLEDNumberCtrl', ]), - ('Window Layout', ['wxLayoutConstraints', 'LayoutAnchors', 'Sizers', 'XML_Resource']), + ('Window Layout', ['wxLayoutConstraints', 'LayoutAnchors', 'Sizers', 'XML_Resource', + 'RowColSizer', + ]), ('Miscellaneous', [ 'DragAndDrop', 'CustomDragAndDrop', 'URLDragAndDrop', 'FontEnumerator', diff --git a/wxPython/demo/RowColSizer.py b/wxPython/demo/RowColSizer.py new file mode 100644 index 0000000000..433a7aac6e --- /dev/null +++ b/wxPython/demo/RowColSizer.py @@ -0,0 +1,67 @@ + +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__ + + diff --git a/wxPython/src/gdi.i b/wxPython/src/gdi.i index 29aca1dcac..7544659159 100644 --- a/wxPython/src/gdi.i +++ b/wxPython/src/gdi.i @@ -51,12 +51,12 @@ public: 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__ diff --git a/wxPython/wxPython/lib/grids.py b/wxPython/wxPython/lib/grids.py index 8387190002..9d517db3b0 100644 --- a/wxPython/wxPython/lib/grids.py +++ b/wxPython/wxPython/lib/grids.py @@ -12,7 +12,11 @@ #---------------------------------------------------------------------- """ -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 diff --git a/wxPython/wxPython/lib/rcsizer.py b/wxPython/wxPython/lib/rcsizer.py new file mode 100644 index 0000000000..af164089ea --- /dev/null +++ b/wxPython/wxPython/lib/rcsizer.py @@ -0,0 +1,317 @@ +#---------------------------------------------------------------------- +# 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) ) + diff --git a/wxPython/wxPython/lib/sheet.py b/wxPython/wxPython/lib/sheet.py new file mode 100644 index 0000000000..4b6612a072 --- /dev/null +++ b/wxPython/wxPython/lib/sheet.py @@ -0,0 +1,332 @@ +# 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