Changeset 1326

Show
Ignore:
Timestamp:
09/15/05 22:55:29 (3 years ago)
Author:
paul
Message:

This merges my temporary branch back into the trunk. All the changes are logged
as per my commits over the past few days.

I was going to hold off on doing this until most of my work on dGrid was done,
but I left trunk's dGrid in a semi-unusable state and this new one is looking
very good, so what the hey.

To summarize, dGrid is now better performing, is more flicker-free, has a
better API (more properties, better encapsulation of responsibilities,
better control over minute details while still being really easy to use),
and it fits in better with overall Dabo (props for "DataSource?" at the Grid
level and "DataField?" at the column level, which can be links to the bizobj
or to a "dataset", such as what you get from bizobj.getDataSet() or really
any tuple of dicts).

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/dabo/biz/dBizobj.py

    r1288 r1326  
    10311031 
    10321032 
    1033     def getFieldVal(self, fld): 
    1034         """ Return the value of the specified field in the current row.  
     1033    def getFieldVal(self, fld, row=None): 
     1034        """ Return the value of the specified field in the current or specified row.  
    10351035        """ 
    10361036        cursor = self._CurrentCursor 
    10371037        if cursor is not None: 
    1038             return cursor.getFieldVal(fld
     1038            return cursor.getFieldVal(fld, row
    10391039        else: 
    10401040            return None 
     
    10551055 
    10561056 
    1057     def getDataSet(self, flds=None): 
     1057    def getDataSet(self, flds=(), rowStart=0, rows=None): 
    10581058        """ Return the full data set from the cursor.  
    10591059         
  • trunk/dabo/dEvents.py

    r1304 r1326  
    381381    pass 
    382382 
     383class GridHeaderIdle(GridEvent): 
     384    """Occurs when an idle cycle happens in the grid header.""" 
     385    pass 
     386 
    383387class GridHeaderMouseEnter(GridEvent, MouseEvent): 
    384388    """Occurs when the mouse pointer enters the grid's header region.""" 
  • trunk/dabo/db/dCursorMixin.py

    r1267 r1326  
    520520 
    521521 
    522     def getFieldVal(self, fld): 
    523         """ Return the value of the specified field.""" 
     522    def getFieldVal(self, fld, row=None): 
     523        """ Return the value of the specified field in the current or specified row.""" 
    524524        ret = None 
    525525        if self.RowCount <= 0: 
    526526            raise dException.NoRecordsException, _("No records in the data set.") 
    527527 
    528         rec = self._records[self.RowNumber] 
     528        if row is None: 
     529            row = self.RowNumber 
     530 
     531        rec = self._records[row] 
    529532        if rec.has_key(fld): 
    530533            ret = rec[fld] 
     
    628631 
    629632 
    630     def getDataSet(self, flds=None): 
    631         """ Get the entire data set encapsulated in a tuple. If the optional 
     633    def getDataSet(self, flds=(), rowStart=0, rows=None): 
     634        """ Get the entire data set encapsulated in a list. If the optional 
    632635        'flds' parameter is given, the result set will be filtered to only  
    633636        include the specified fields. 
    634637        """ 
    635638        try: 
    636             ret = self._records 
     639            if rows is not None: 
     640                ret = self._records[rowStart:rows] 
     641            else: 
     642                ret = self._records[rowStart:] 
    637643            if flds: 
    638644                retlist = [] 
  • trunk/dabo/lib/datanav/Grid.py

    r1307 r1326  
    1818        self.bindEvent(dEvents.GridMouseLeftDoubleClick, self.onGridLeftDClick) 
    1919 
     20 
    2021    def _afterInit(self): 
    2122        super(Grid, self)._afterInit() 
    22         self.bizobj = None 
     23        ##pkm self.bizobj = None 
    2324        self._fldSpecs = None 
    2425        self.skipFields = [] 
     
    2829        self.customSort = True 
    2930 
    30  
    31     def getDataSet(self): 
     31     
     32    def getDataSet_old_pkm(self, requery=False): 
     33        # Normally, getDataSet() just returns the object reference to the list 
     34        # previously generated, but if we just requeried, we need to ask the  
     35        # bizobj for the new dataSet. 
     36        if requery: 
     37            ret = self.dataSet = None 
    3238        ret = self.dataSet 
    33         if not self.inAutoSizeCalc: 
    34             if self.bizobj: 
    35                 ret = self.dataSet = self.bizobj.getDataSet() 
     39        if not ret: 
     40            if not self.inAutoSizeCalc: 
     41                if self.bizobj: 
     42                    ret = self.dataSet = self.bizobj.getDataSet() 
    3643        return ret 
    3744     
    3845 
    3946    def populate(self): 
    40         ds = self.getDataSet() 
     47        ##pkm ds = self.getDataSet(requery=True) 
     48        ds = self.DataSource 
    4149        if not self.built and ds: 
    4250            self.buildFromDataSet(ds,  
     
    5058            ## pkm: this call appears to be redundant, as the grid as already been  
    5159            ##      filled in dGrid: 
    52             #self.fillGrid(True) 
     60            self.fillGrid(True) 
    5361            pass 
    5462        self.Form.refresh() 
     
    5866        # The superclass will have already set the sort properties. 
    5967        # We want to send those to the bizobj for sorting. 
    60         ds1 = self.dataSet 
    61         self.bizobj.sort(self.sortedColumn, self.sortOrder
     68        bizobj = self.Form.getBizobj(self.DataSource) 
     69        bizobj.sort(self.sortedColumn, self.sortOrder, self.caseSensitiveSorting
    6270         
    6371     
    6472    def setBizobj(self, biz): 
    65         self.bizobj = biz 
     73        self.DataSource = biz.DataSource 
     74 
    6675 
    6776    def onGridLeftDClick(self, evt):  
     
    106115    def newRecord(self, evt=None): 
    107116        """ Request that a new row be added.""" 
    108         self.Parent.newRecord(self.bizobj.DataSource) 
     117        self.Parent.newRecord(self.DataSource) 
    109118 
    110119 
    111120    def editRecord(self, evt=None): 
    112121        """ Request that the current row be edited.""" 
    113         self.Parent.editRecord(self.bizobj.DataSource) 
     122        self.Parent.editRecord(self.DataSource) 
    114123 
    115124 
    116125    def deleteRecord(self, evt=None): 
    117126        """ Request that the current row be deleted.""" 
    118         self.Parent.deleteRecord(self.bizobj.DataSource) 
     127        self.Parent.deleteRecord(self.DataSource) 
    119128        self.setFocus()  ## required or assertion happens on Gtk 
    120129 
  • trunk/dabo/lib/datanav/Page.py

    r1237 r1326  
    588588        # If we aren't the active page, strange things can happen if we 
    589589        # don't explicitly setFocus back to the active page.  
    590         self.updateGrid() 
     590        #self.updateGrid() 
     591        pass 
    591592#       if self.Parent.SelectedPage != self: 
    592593#           self.Parent.SelectedPage.setFocus() 
     
    604605                if self.itemsCreated: 
    605606                    self.fillGrid(False) 
    606         self.BrowseGrid.CurrentRow = bizobj.RowNumber 
     607        ## dGrid handles this now: 
     608        #self.BrowseGrid.CurrentRow = bizobj.RowNumber 
    607609 
    608610         
     
    618620    def createItems(self): 
    619621        bizobj = self.Form.getBizobj() 
    620         grid = Grid.Grid(self, Name="BrowseGrid") 
     622        grid = Grid.Grid(self, NameBase="BrowseGrid") 
    621623        grid.FieldSpecs = self.Form.FieldSpecs 
    622624        if not self.Form.preview: 
  • trunk/dabo/ui/uiwx/__init__.py

    r1254 r1326  
    533533 
    534534 
     535def fontMetricFromFont(txt, font): 
     536    wind = wx.Frame(None) 
     537    dc = wx.ClientDC(wind) 
     538    dc.SetFont(font) 
     539    ret = dc.GetTextExtent(txt) 
     540    wind.Destroy() 
     541    return ret 
     542 
     543 
    535544def fontMetric(txt=None, wind=None, face=None, size=None, bold=None, 
    536545        italic=None): 
  • trunk/dabo/ui/uiwx/dGrid.py

    r1307 r1326  
    1717import dKeys 
    1818import dUICursors 
     19import dabo.biz 
     20import dabo.common.dColors as dColors 
    1921 
    2022# See if the new decimal module is present. This is necessary  
     
    4042 
    4143        self.grid = parent 
    42         self.bizobj = None      #self.grid.Form.getBizobj(parent.DataSource)  
    43         # Holds a copy of the current data to prevent unnecessary re-drawing 
    44         self.__currData = [] 
    4544        self._initTable() 
    4645 
     
    4847    def _initTable(self): 
    4948        self.relativeColumns = [] 
    50         self.colLabels = [] 
    51         self.colNames = [] 
    5249        self.colDefs = [] 
    53         self.dataTypes = [] 
    5450        self.imageBaseThumbnails = [] 
    5551        self.imageLists = {} 
    56         self.data = [] 
    5752        self.rowLabels = [] 
     53        self._oldRowCount = None 
    5854        # Call the hook 
    5955        self.initTable() 
     
    6763        self.rowLabels = rowLbls 
    6864         
     65 
    6966    def GetAttr(self, row, col, kind=0): 
    7067        ## dColumn maintains one attribute object that applies to every row  
     
    8582        ## The column attr object is maintained in dColumn: 
    8683        try: 
    87             return self.grid.Columns[col]._gridColAttr.Clone() 
     84            dcol = self.grid.Columns[col] 
     85            attr = dcol._gridColAttr.Clone() 
    8886        except IndexError: 
    8987            # Something is out of order in the setting up of the grid: the grid table 
     
    9492            #  just empty - no columns or rows added yet) 
    9593 
     94        ## Now, override with a custom renderer/editor for this row/col if applicable. 
     95        customEditor = dcol.CustomEditors.get(row) 
     96        customRenderer = dcol.CustomRenderers.get(row) 
     97        if customEditor is not None: 
     98            attr.SetEditor(customEditor()) 
     99        if customRenderer is not None: 
     100            attr.SetRenderer(customRenderer()) 
     101 
     102        return attr 
     103 
     104 
    96105    def GetRowLabelValue(self, row): 
    97106        try: 
     
    100109            return "" 
    101110     
     111 
    102112    def GetColLabelValue(self, col): 
    103         try: 
    104             return self.colDefs[col].Caption 
    105         except: 
    106             return "" 
     113        # The column headers are painted when the wxGrid queries this method, as 
     114        # this is the most appropriate time to do so. We return "" so that wx  
     115        # doesn't draw the string itself. 
     116        self.grid._paintHeader(col) 
     117        return "" 
    107118         
    108119             
     
    119130            self.setColumnInfo() 
    120131            return 
    121         else: 
    122             self.__currData = [] 
     132 
    123133        for col in colDefs: 
    124             nm = col.Field 
     134            nm = col.DataField 
    125135            while not nm: 
    126136                nm = str(idx) 
     
    175185    def setColumnInfo(self): 
    176186        self.colDefs.sort(self.orderSort)        
    177         self.colLabels = [col.Caption for col in self.colDefs] 
    178         self.dataTypes = [self.convertType(col.DataType)  
    179                 for col in self.colDefs] 
    180         self.colNames = [col.Field for col in self.colDefs] 
    181187 
    182188 
     
    212218            return self.grid.customCanGetValueAs(row, col, typ) 
    213219        else: 
    214             return typ == self.dataTypes[col] 
     220            dcol = self.grid.Columns[col] 
     221            return typ == self.convertType(dcol.DataType) 
    215222 
    216223    def CanSetValueAs(self, row, col, typ): 
     
    218225            return self.grid.customCanSetValueAs(row, col, typ) 
    219226        else: 
    220             return typ == self.dataTypes[col] 
     227            dcol = self.grid.Columns[col] 
     228            return typ == self.convertType(dcol.DataType) 
    221229 
    222230         
    223231    def fillTable(self, force=False): 
    224232        """ Fill the grid's data table to match the data set.""" 
    225         rows = self.GetNumberRows() 
     233 
     234        _oldRowCount = self._oldRowCount 
     235 
     236        # Get the data from the grid. 
     237        dataSet = self.grid.DataSource 
     238        if dataSet is None: 
     239            return 
     240        bizobj = self.grid.getBizobj() 
     241 
     242        if bizobj: 
     243            dataSet = bizobj 
     244            _newRowCount = dataSet.RowCount 
     245        else: 
     246            _newRowCount = len(dataSet) 
     247            if _oldRowCount is None: 
     248                ## still haven't tracked down why, but bizobj grids needed _oldRowCount 
     249                ## to be initialized to None, or extra rows would be added. Since we 
     250                ## aren't a bizobj grid, we need to change that None to 0 here so that 
     251                ## the rows can get appended below. 
     252                _oldRowCount = 0 
     253 
     254        if _oldRowCount == _newRowCount and not force: 
     255            return 
     256             
    226257        oldRow = self.grid.CurrentColumn  # current row per the grid 
    227258        oldCol = self.grid.CurrentColumn  # current column per the grid 
    228259        if not oldCol: 
    229260            oldCol = 0 
    230         # Get the data from the grid. 
    231         dataSet = self.grid.getDataSet() 
    232         if not force: 
    233             if self.__currData == dataSet: 
    234                 # Nothing's changed; no need to re-fill the table 
    235                 return 
    236         else: 
    237             self.__currData = dataSet 
    238          
    239         ## pkm: Discovered this call isn't needed. Not sure if it improves anything 
    240         ##      without it, however: 
    241         #self.Clear() 
    242  
    243         self.data = [] 
     261 
    244262        encod = self.grid.Encoding 
    245         for record in dataSet: 
    246             recordFmt = self.formatRowForData(record) 
    247             self.data.append(recordFmt) 
    248263        self.grid.BeginBatch() 
    249264        # The data table is now current, but the grid needs to be 
    250265        # notified. 
    251         if len(self.data) > rows: 
     266 
     267        if _oldRowCount is not None and _newRowCount > _oldRowCount: 
    252268            # tell the grid we've added row(s) 
    253             num = len(self.data) - rows 
     269            num = _newRowCount - _oldRowCount 
    254270            msg = wx.grid.GridTableMessage(self,       # The table 
    255271                wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED,  # what we did to it 
    256272                num)                                     # how many 
    257273             
    258         elif rows > len(self.data)
     274        elif _oldRowCount is not None and _oldRowCount > _newRowCount
    259275            # tell the grid we've deleted row(s) 
    260             num = rows - len(self.data)  
     276            num = _oldRowCount - _newRowCount 
    261277            msg = wx.grid.GridTableMessage(self,      # The table 
    262278                wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED,  # what we did to it 
     
    265281        else: 
    266282            msg = None 
     283 
    267284        if msg:         
    268285            self.grid.ProcessTableMessage(msg) 
     
    271288        #   2) the fieldSpecs 
    272289        #   3) have the grid autosize 
     290 
    273291        for idx, col in enumerate(self.colDefs): 
    274             fld = col.Field 
     292            fld = col.DataField 
    275293            colName = "Column_%s" % fld 
    276294            gridCol = idx 
     
    301319        self.grid.EndBatch() 
    302320 
    303      
     321        self._oldRowCount = _newRowCount 
     322 
     323 
     324    ##pkm: The following function doesn't seem to be necessary, at least when  
     325    ##     connected directly to a bizobj. The appRecipes demo properly displays 
     326    ##     the unicode chars, etc. But if this is necessary, because of the  
     327    ##     reworking of things, we should probably make it formatFieldForData(fld,row) 
     328    ##     and call it from self.GetValue(). Ed, thoughts? 
    304329    def formatRowForData(self, rec): 
    305330        """Takes a row from a record set, and contructs a list 
     
    309334        returnFmt = [] 
    310335        for col in self.colDefs: 
    311             fld = col.Field 
     336            fld = col.DataField 
    312337            if rec.has_key(fld): 
    313338                recVal = rec[fld] 
     
    351376     
    352377     
    353     def addTempRow(self, row): 
    354         """Used by the autosize routine to add an individual row  
    355         containing the captions for the columns so that the autosize 
    356         function takes them into account. It is then followed by a 
    357         call to self.removeTempRow() to restore the data back to its 
    358         original state. 
    359         """ 
    360         rowFmt = self.formatRowForData(row) 
    361         self.data.append(rowFmt) 
    362         self.grid.BeginBatch() 
    363         msg = wx.grid.GridTableMessage(self, 
    364                 wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED, 1) 
    365         self.grid.ProcessTableMessage(msg) 
    366         self.grid.EndBatch() 
    367  
    368  
    369     def removeTempRow(self): 
    370         """Removes the temp row that was added in a prior call to  
    371         addTempRow(). This method assumes that the last row 
    372         in the data set is the row to remove. 
    373         """ 
    374         tmp = self.data.pop() 
    375         self.grid.BeginBatch() 
    376         msg = wx.grid.GridTableMessage(self, 
    377                 wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED, 
    378                 len(self.data), 1) 
    379         self.grid.ProcessTableMessage(msg) 
    380         self.grid.EndBatch() 
    381      
    382  
    383378    # The following methods are required by the grid, to find out certain 
    384379    # important details about the underlying table.                 
    385380    def GetNumberRows(self): 
    386         try: 
    387             num = len(self.data) 
     381        bizobj = self.grid.getBizobj() 
     382        if bizobj: 
     383            return bizobj.RowCount 
     384        try: 
     385            num = len(self.grid.DataSource) 
    388386        except: 
    389387            num = 0 
    390388        return num 
    391389 
     390 
    392391    def GetNumberCols(self): 
    393         try: 
    394             num = len(self.colLabels) 
    395         except: 
    396             num = 0 
    397         return num 
     392        return self.grid.ColumnCount 
    398393 
    399394 
    400395    def IsEmptyCell(self, row, col): 
    401         try: 
    402             return not self.data[row][col] 
    403         except IndexError: 
    404             return True 
     396        bizobj = self.grid.getBizobj() 
     397        field = self.grid.Columns[col].DataField 
     398        if bizobj: 
     399            if field: 
     400                return not bizobj.getFieldVal(field, row) 
     401            else: 
     402                return True 
     403        if field: 
     404            try: 
     405                return not self.grid.DataSource[row][field] 
     406            except IndexError, KeyError: 
     407                return True 
     408        return True 
     409 
    405410 
    406411    def GetValue(self, row, col): 
    407         try: 
    408             ret = self.data[row][col] 
     412        bizobj = self.grid.getBizobj() 
     413        field = self.grid.Columns[col].DataField 
     414        if bizobj: 
     415            if field: 
     416                return bizobj.getFieldVal(field, row) 
     417            else: 
     418                return "" 
     419        try: 
     420            ret = self.grid.DataSource[row][field] 
    409421        except: 
    410422            ret = "" 
    411423        return ret 
    412424 
     425 
    413426    def SetValue(self, row, col, value): 
    414         self.data[row][col] = value 
     427        #self.data[row][col] = value 
     428        field = self.grid.Columns[col].DataField 
     429        bizobj = self.grid.getBizobj() 
     430        if bizobj: 
     431            # make sure we are on the correct row already (we should be already): 
     432            bizobj.RowNumber = row 
     433            bizobj.setFieldVal(field, value) 
     434        else: 
     435            self.grid.DataSource[row][field] = value 
    415436 
    416437 
     
    436457        super(dColumn, self)._beforeInit() 
    437458        # Define the cell renderer and editor classes 
     459        import gridRenderers 
    438460        self.stringRendererClass = wx.grid.GridCellStringRenderer 
    439         self.boolRendererClass = wx.grid.GridCellBoolRenderer 
     461        self.boolRendererClass = gridRenderers.BoolRenderer 
    440462        self.intRendererClass = wx.grid.GridCellNumberRenderer 
    441463        self.longRendererClass = wx.grid.GridCellNumberRenderer 
     
    471493         
    472494 
     495    def getRendererForRow(self, row): 
     496        """Return the cell renderer for the passed row.""" 
     497        d = self.CustomRenderers 
     498        r = d.get(row) 
     499        if r is None: 
     500            r = self.Renderer 
     501        return r 
     502 
     503 
    473504    def _afterInit(self): 
    474505        self._isConstructed = True 
     
    480511 
    481512 
     513    def _getHeaderRect(self): 
     514        """Return the rect of this header in the header window.""" 
     515        grid = self.Parent 
     516        height = self.Parent.HeaderHeight 
     517        width = self.Width 
     518        top = 0 
     519 
     520        # Thanks Roger Binns: 
     521        left = -grid.GetViewStart()[0] * grid.GetScrollPixelsPerUnit()[0] 
     522 
     523        for col in range(self.Parent.ColumnCount): 
     524            colObj = self.Parent.Columns[col] 
     525            if colObj == self: 
     526                break 
     527            left += colObj.Width 
     528 
     529        return (left, top, width, height) 
     530 
     531 
     532    def _refreshHeader(self): 
     533        """Refresh just this column's header.""" 
     534        if self.Parent: 
     535            # This will trigger wx to query GetColLabelValue(), which will in turn 
     536            # call paintHeader() on just this column. It's roundabout, but gives the 
     537            # best overall results, but risks relying on wx implementation details.  
     538            # Other options, in case this starts to fail, are:  
     539            #       self.Parent.Header.Refresh() 
     540            #       self.Parent._paintHeader(self._GridColumnIndex) 
     541            self.Parent.SetColLabelValue(self._GridColumnIndex, "") 
     542 
     543 
     544    def _refreshGrid(self): 
     545        """Refresh the grid region, not the header region.""" 
     546        if self.Parent: 
     547            gw = self.Parent.GetGridWindow() 
     548            gw.Refresh() 
     549 
     550 
    482551    def _persist(self, prop): 
    483552        """Persist the current prop setting to the user settings table.""" 
     
    485554        app = self.Application 
    486555        grid = self.Parent 
    487         colName = "column_%s" % self.Field 
     556        colName = "column_%s" % self.DataField 
    488557        val = getattr(self, prop) 
    489558        settingName = "%s.%s.%s.%s" % (grid.Form.Name, grid.Name, colName, prop) 
     
    492561 
    493562 
    494     def changeMsg(self, prop): 
    495         if self.Parent: 
    496             self.Parent.onColumnChange(self, prop) 
    497      
    498      
    499563    def _getGridColumnIndex(self): 
    500564        """Return our column index in the grid, or -1.""" 
     
    534598            v = self._caption = "Column" 
    535599        return v 
     600 
    536601    def _setCaption(self, val): 
    537602        if self._constructed(): 
    538603            self._caption = val 
    539             if self.Parent: 
    540                 ## note: may want to use RefreshRect just on the column header region 
    541                 self.Parent.refresh() 
     604            self._refreshHeader() 
    542605        else: 
    543606            self._properties["Caption"] = val 
     607 
    544608 
    545609    def _getCustomEditor(self): 
     
    549613            v = self._customEditor = None 
    550614        return v 
     615 
    551616    def _setCustomEditor(self, val): 
    552617        if self._constructed(): 
     
    555620        else: 
    556621            self._properties["CustomEditor"] = val 
     622 
     623 
     624    def _getCustomEditors(self): 
     625        try: 
     626            v = self._customEditors 
     627        except AttributeError: 
     628            v = self._customEditors = {} 
     629        return v 
     630 
     631    def _setCustomEditors(self, val): 
     632        self._customEditors = val 
     633 
    557634 
    558635    def _getCustomRenderer(self): 
     
    562639            v = self._customRenderer = None 
    563640        return v 
     641 
    564642    def _setCustomRenderer(self, val): 
    565643        if self._constructed(): 
     
    568646        else: 
    569647            self._properties["CustomRenderer"] = val 
     648 
     649 
     650    def _getCustomRenderers(self): 
     651        try: 
     652            v = self._customRenderers 
     653        except AttributeError: 
     654            v = self._customRenderers = {} 
     655        return v 
     656 
     657    def _setCustomRenderers(self, val): 
     658        self._customRenderers = val 
     659 
    570660 
    571661    def _getDataType(self): 
     
    575665            v = self._dataType = "" 
    576666        return v 
     667 
    577668    def _setDataType(self, val): 
    578669        if self._constructed(): 
    579670            self._dataType = val 
    580             if "Automatic" in self.HorizontalCellAlignment: 
    581                 self._setAutoHorizontalCellAlignment() 
     671            if "Automatic" in self.HorizontalAlignment: 
     672                self._setAutoHorizontalAlignment() 
    582673            self._updateRenderer() 
    583674            self._updateEditor() 
    584             #self.changeMsg("DataType")  ## don't think this is necessary, now that the attr handles. 
    585675        else: 
    586676            self._properties["DataType"] = val 
     677 
    587678 
    588679    def _getEditable(self): 
    589680        return not self._gridColAttr.IsReadOnly() 
     681 
    590682    def _setEditable(self, val): 
    591683        if self._constructed(): 
     
    595687        else: 
    596688            self._properties["Editable"] = val           
     689 
    597690 
    598691    def _getEditor(self): 
     
    602695        return v 
    603696 
    604     def _getField(self): 
    605         try: 
    606             v = self._field 
     697 
     698    def _getDataField(self): 
     699        try: 
     700            v = self._dataField 
    607701        except AttributeError: 
    608             v = self._field = "" 
     702            v = self._dataField = "" 
    609703        return v 
    610     def _setField(self, val): 
    611         if self._constructed(): 
    612             self._field = val 
     704 
     705    def _setDataField(self, val): 
     706        if self._constructed(): 
     707            self._dataField = val 
    613708            self._updateRenderer() 
    614709            self._updateEditor() 
    615             self.changeMsg("Field") 
    616         else: 
    617             self._properties["Field"] = val 
     710        else: 
     711            self._properties["DataField"] = val 
     712 
     713 
     714    def _getHeaderFont(self): 
     715        try: 
     716            v = self._headerFont 
     717        except AttributeError: 
     718            if self.Parent: 
     719                v = self.Parent.GetLabelFont() 
     720            else: 
     721                v = wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.LIGHT) 
     722        return v 
     723     
     724    def _setHeaderFont(self, val): 
     725        if self._constructed(): 
     726            self._headerFont = val 
     727            self._refreshHeader() 
     728        else: 
     729            self._properties["HeaderFont"] = val 
     730 
     731     
     732    def _getHeaderFontBold(self): 
     733        return self.HeaderFont.GetWeight() == wx.BOLD 
     734     
     735    def _setHeaderFontBold(self, val): 
     736        if self._constructed(): 
     737            font = self.HeaderFont 
     738            if val: 
     739                font.SetWeight(wx.BOLD) 
     740            else: 
     741                font.SetWeight(wx.LIGHT)    # wx.NORMAL doesn't seem to work... 
     742            self.HeaderFont = font 
     743        else: 
     744            self._properties["HeaderFontBold"] = val 
     745 
     746    def _getHeaderFontDescription(self): 
     747        f = self.HeaderFont 
     748        ret = f.GetFaceName() + " " + str(f.GetPointSize()) 
     749        if f.GetWeight() == wx.BOLD: 
     750            ret += " B" 
     751        if f.GetStyle() == wx.ITALIC: 
     752            ret += " I" 
     753        return ret 
     754     
     755    def _getHeaderFontInfo(self): 
     756        return self.HeaderFont.GetNativeFontInfoDesc() 
     757 
     758         
     759    def _getHeaderFontItalic(self): 
     760        return self.HeaderFont.GetStyle() == wx.ITALIC 
     761     
     762    def _setHeaderFontItalic(self, val): 
     763        if self._constructed(): 
     764            font = self.HeaderFont 
     765            if val: 
     766                font.SetStyle(wx.ITALIC) 
     767            else: 
     768                font.SetStyle(wx.NORMAL) 
     769            self.HeaderFont = font 
     770        else: 
     771            self._properties["HeaderFontItalic"] = val 
     772 
     773     
     774    def _getHeaderFontFace(self): 
     775        return self.HeaderFont.GetFaceName() 
     776 
     777    def _setHeaderFontFace(self, val): 
     778        if self._constructed(): 
     779            f = self.HeaderFont 
     780            f.SetFaceName(val) 
     781            self.HeaderFont = f 
     782        else: 
     783            self._properties["HeaderFontFace"] = val 
     784 
     785     
     786    def _getHeaderFontSize(self): 
     787        return self.HeaderFont.GetPointSize() 
     788     
     789    def _setHeaderFontSize(self, val): 
     790        if self._constructed(): 
     791            font = self.HeaderFont 
     792            font.SetPointSize(int(val)) 
     793            self.HeaderFont = font 
     794        else: 
     795            self._properties["HeaderFontSize"] = val 
     796     
     797    def _getHeaderFontUnderline(self): 
     798        return self.HeaderFont.GetUnderlined() 
     799     
     800    def _setHeaderFontUnderline(self, val): 
     801        if self._constructed(): 
     802            # underlining doesn't seem to be working... 
     803            font = self.HeaderFont 
     804            font.SetUnderlined(bool(val)) 
     805            self.HeaderFont = font 
     806        else: 
     807            self._properties["HeaderFontUnderline"] = val 
     808 
    618809 
    619810    def _getHeaderBackgroundColor(self): 
     
    623814            v = self._headerBackgroundColor = None 
    624815        return v 
     816 
    625817    def _setHeaderBackgroundColor(self, val): 
    626818        if self._constructed(): 
     
    631823                    pass 
    632824            self._headerBackgroundColor = val 
    633             if self.Parent: 
    634                 self.Parent.refresh() 
     825            self._refreshHeader() 
    635826        else: 
    636827            self._properties["HeaderBackgroundColor"] = val 
    637      
    638     def _getHorizontalCellAlignment(self): 
    639         try: 
    640             auto = self._autoHorizontalCellAlignment 
     828 
     829     
     830    def _getHeaderForegroundColor(self): 
     831        try: 
     832            v = self._headerForegroundColor 
    641833        except AttributeError: 
    642             auto = self._autoHorizontalCellAlignment = True 
     834            v = self._headerForegroundColor = dColors.colorTupleFromName("Black") 
     835        return v 
     836 
     837    def _setHeaderForegroundColor(self, val): 
     838        if self._constructed(): 
     839            if isinstance(val, basestring): 
     840                try: 
     841                    val = dColors.colorTupleFromName(val) 
     842                except:  
     843                    pass 
     844            self._headerForegroundColor = val 
     845            self._refreshHeader() 
     846        else: 
     847            self._properties["HeaderForegroundColor"] = val 
     848 
     849     
     850    def _getHeaderHorizontalAlignment(self): 
     851        try: 
     852            val = self._headerHorizontalAlignment 
     853        except AttributeError: 
     854            val = self._headerHorizontalAlignment = "Center" 
     855        return val 
     856 
     857    def _setHeaderHorizontalAlignment(self, val): 
     858        if self._constructed(): 
     859            v = self._expandPropStringValue(val, ("Left", "Right", "Center")) 
     860            self._headerHorizontalAlignment = v 
     861            self._refreshHeader() 
     862        else: 
     863            self._properties["HeaderHorizontalAlignment"] = val 
     864 
     865 
     866    def _getHeaderVerticalAlignment(self): 
     867        try: 
     868            val = self._headerVerticalAlignment 
     869        except AttributeError: 
     870            val = self._headerVerticalAlignment = "Center" 
     871        return val 
     872 
     873    def _setHeaderVerticalAlignment(self, val): 
     874        if self._constructed(): 
     875            v = self._expandPropStringValue(val, ("Top", "Bottom", "Center")) 
     876            self._headerVerticalAlignment = v 
     877            self._refreshHeader() 
     878        else: 
     879            self._properties["HeaderVerticalAlignment"] = val 
     880 
     881 
     882    def _getHorizontalAlignment(self): 
     883        try: 
     884            auto = self._autoHorizontalAlignment 
     885        except AttributeError: 
     886            auto = self._autoHorizontalAlignment = True 
    643887        mapping = {wx.ALIGN_LEFT: "Left", wx.ALIGN_RIGHT: "Right", 
    644888                 wx.ALIGN_CENTRE: "Center"} 
     
    651895            val = "%s (Automatic)" % val 
    652896        return val 
    653     def _setAutoHorizontalCellAlignment(self): 
     897 
     898    def _setAutoHorizontalAlignment(self): 
    654899        dt = self.DataType 
    655900        if isinstance(dt, basestring): 
    656901            if dt in ("decimal", "float", "long", "integer"): 
    657                 self._setHorizontalCellAlignment("Right", _autoAlign=True) 
    658          
    659     def _setHorizontalCellAlignment(self, val, _autoAlign=False): 
     902                self._setHorizontalAlignment("Right", _autoAlign=True) 
     903         
     904    def _setHorizontalAlignment(self, val, _autoAlign=False): 
    660905        if self._constructed(): 
    661906            val = self._expandPropStringValue(val, ("Automatic", "Left", "Right", "Center")) 
    662907            if val == "Automatic" and not _autoAlign: 
    663                 self._autoHorizontalCellAlignment = True 
    664                 self._setAutoHorizontalCellAlignment() 
     908                self._autoHorizontalAlignment = True 
     909                self._setAutoHorizontalAlignment() 
    665910                return 
    666911            if val != "Automatic" and not _autoAlign: 
    667                 self._autoHorizontalCellAlignment = False 
     912                self._autoHorizontalAlignment = False 
    668913            mapping = {"Left": wx.ALIGN_LEFT, "Right": wx.ALIGN_RIGHT, 
    669914                     "Center": wx.ALIGN_CENTRE} 
     
    675920            wxVertAlign = self._gridColAttr.GetAlignment()[1] 
    676921            self._gridColAttr.SetAlignment(wxHorAlign, wxVertAlign) 
    677             if self.Parent: 
    678                 self.Parent.refresh() 
    679         else: 
    680             self._properties["HorizontalCellAlignment"] = val 
     922            self._refreshGrid() 
     923        else: 
     924            self._properties["HorizontalAlignment"] = val 
    681925 
    682926     
     
    687931            v = [] 
    688932        return v 
     933 
    689934    def _setListEditorChoices(self, val): 
    690935        if self._constructed(): 
     
    700945        return v 
    701946 
     947 
    702948    def _getOrder(self): 
    703949        try: 
     
    706952            v = self._order = -1 
    707953        return v