/[thuban]/trunk/thuban/Thuban/UI/classifier.py
ViewVC logotype

Diff of /trunk/thuban/Thuban/UI/classifier.py

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 451 by jonathan, Tue Mar 4 10:33:56 2003 UTC revision 2817 by bernhard, Sun Jan 27 00:01:32 2008 UTC
# Line 1  Line 1 
1  # Copyright (c) 2001 by Intevation GmbH  # Copyright (c) 2003-2005 by Intevation GmbH
2  # Authors:  # Authors:
3  # Jonathan Coles <[email protected]>  # Jan-Oliver Wagner <[email protected]> (2003-2004)
4    # Martin Schulze <[email protected]> (2004)
5    # Frank Koormann <[email protected]> (2003, 2006)
6    # Bernhard Herzog <[email protected]> (2003)
7    # Jonathan Coles <[email protected]> (2003)
8  #  #
9  # This program is free software under the GPL (>=v2)  # This program is free software under the GPL (>=v2)
10  # Read the file COPYING coming with Thuban for details.  # Read the file COPYING coming with Thuban for details.
# Line 8  Line 12 
12  """Dialog for classifying how layers are displayed"""  """Dialog for classifying how layers are displayed"""
13    
14  __version__ = "$Revision$"  __version__ = "$Revision$"
15    # $Source$
16    # $Id$
17    
18  import copy  import copy
19    import re
20    
21  from wxPython.wx import *  from Thuban.Model.table import FIELDTYPE_INT, FIELDTYPE_DOUBLE, \
22  from wxPython.grid import *       FIELDTYPE_STRING
23    
24    import wx
25    from wx import grid
26    
27  from Thuban import _  from Thuban import _
28  from Thuban.common import *  from Thuban.UI import internal_from_wxstring
29  from Thuban.UI.common import *  from Thuban.UI.common import Color2wxColour, wxColour2Color
30    
31    from Thuban.Model.messages import MAP_LAYERS_REMOVED, LAYER_SHAPESTORE_REPLACED
32    from Thuban.Model.range import Range
33    from Thuban.Model.classification import \
34        Classification, ClassGroupDefault, \
35        ClassGroupSingleton, ClassGroupPattern, ClassGroupRange, ClassGroupMap, \
36        ClassGroupProperties
37    
38  from Thuban.Model.classification import *  from Thuban.Model.color import Transparent
39    
40  from Thuban.Model.color import Color  from Thuban.Model.layer import Layer
41    from Thuban.Model.data import SHAPETYPE_ARC, SHAPETYPE_POLYGON, SHAPETYPE_POINT
42    
43  from Thuban.Model.layer import SHAPETYPE_ARC, SHAPETYPE_POLYGON, SHAPETYPE_POINT  from Thuban.UI.classgen import ClassGenDialog
44    from Thuban.UI.colordialog import ColorDialog
45    
46  ID_PROPERTY_SELECT = 4010  from Thuban.UI.layerproperties import LayerProperties
47  ID_CLASS_TABLE = 40011  from messages import MAP_REPLACED
48    
 ID_CLASSIFY_OK = 4001  
 ID_CLASSIFY_CANCEL = 4002  
 ID_CLASSIFY_ADD = 4003  
 ID_CLASSIFY_GENRANGE = 4004  
 ID_CLASSIFY_REMOVE = 4005  
49    
50  COL_VISUAL = 0  # table columns
51  COL_VALUE  = 1  COL_VISIBLE = 0
52  COL_LABEL  = 2  COL_SYMBOL  = 1
53    COL_VALUE   = 2
54    COL_LABEL   = 3
55    NUM_COLS    = 4
56    
57    # indices into the client data lists in Classifier.fields
58  FIELD_CLASS = 0  FIELD_CLASS = 0
59  FIELD_TYPE = 1  FIELD_TYPE = 1
60  FIELD_NAME = 2  FIELD_NAME = 2
61    
 FIELD_TYPE_STRING = "string"  
 FIELD_TYPE_INT = "int"  
 FIELD_TYPE_DOUBLE = "double"  
   
62  #  #
63  # this is a silly work around to ensure that the table that is  # this is a silly work around to ensure that the table that is
64  # passed into SetTable is the same that is returned by GetTable  # passed into SetTable is the same that is returned by GetTable
65  #  #
66  import weakref  import weakref
67  class ClassGrid(wxGrid):  class ClassGrid(grid.Grid):
68    
69    
70      def __init__(self, parent, layer, fieldData):      def __init__(self, parent, classifier):
71          wxGrid.__init__(self, parent, ID_CLASS_TABLE, size = (340, 160))          """Constructor.
         self.SetSelectionMode(wxGrid.wxGridSelectRows)  
         self.SetTable(ClassTable(fieldData, layer.ShapeType(), self), true)  
         EVT_GRID_CELL_LEFT_DCLICK(self, self.OnCellDClick)  
72    
73          EVT_GRID_RANGE_SELECT(self, self._OnSelectedRange)          parent -- the parent window
74          EVT_GRID_SELECT_CELL(self, self._OnSelectedCell)  
75            clazz -- the working classification that this grid should
76                     use for display.
77            """
78            grid.Grid.__init__(self, parent, -1, style = 0)
79    
80            self.classifier = classifier
81    
         self.layer = layer  
82          self.currentSelection = []          self.currentSelection = []
83    
84            self.Bind(grid.EVT_GRID_CELL_LEFT_DCLICK, self._OnCellDClick)
85            self.Bind(grid.EVT_GRID_RANGE_SELECT, self._OnSelectedRange)
86            self.Bind(grid.EVT_GRID_SELECT_CELL, self._OnSelectedCell)
87            self.Bind(grid.EVT_GRID_COL_SIZE, self._OnCellResize)
88            self.Bind(grid.EVT_GRID_ROW_SIZE, self._OnCellResize)
89            self.Bind(grid.EVT_GRID_LABEL_RIGHT_CLICK, self._OnLabelRightClicked)
90    
91    
92        #def GetCellAttr(self, row, col):
93            #print "GetCellAttr ", row, col
94            #Grid.GetCellAttr(self, row, col)
95    
96        def CreateTable(self, clazz, fieldType, shapeType, group = None):
97    
98            assert isinstance(clazz, Classification)
99    
100            table = self.GetTable()
101            if table is None:
102                w = self.GetDefaultColSize() * NUM_COLS \
103                    + self.GetDefaultRowLabelSize()
104                h = self.GetDefaultRowSize() * 4 \
105                    + self.GetDefaultColLabelSize()
106    
107                self.SetDimensions(-1, -1, w, h)
108                self.SetSizeHints(w, h, -1, -1)
109                table = ClassTable(self)
110                self.SetTable(table, True)
111    
112    
113            self.SetSelectionMode(grid.Grid.wxGridSelectRows)
114            self.ClearSelection()
115    
116            table.Reset(clazz, fieldType, shapeType, group)
117    
118      def GetCurrentSelection(self):      def GetCurrentSelection(self):
119            """Return the currently highlighted rows as an increasing list
120               of row numbers."""
121          sel = copy.copy(self.currentSelection)          sel = copy.copy(self.currentSelection)
122          sel.sort()          sel.sort()
123          return sel          return sel
124    
125      def SetCellRenderer(self, row, col):      def GetSelectedRows(self):
126          raise ValueError(_("Must not allow setting of renderer in ClassGrid!"))          return self.GetCurrentSelection()
127    
128        #def SetCellRenderer(self, row, col, renderer):
129            #raise ValueError(_("Must not allow setting of renderer in ClassGrid!"))
130    
131        #
132        # [Set|Get]Table is taken from http://wiki.wxpython.org
133        # they are needed as a work around to ensure that the table
134        # that is passed to SetTable is the one that is returned
135        # by GetTable.
136        #
137      def SetTable(self, object, *attributes):      def SetTable(self, object, *attributes):
138          self.tableRef = weakref.ref(object)          self.tableRef = weakref.ref(object)
139          return wxGrid.SetTable(self, object, *attributes)          return grid.Grid.SetTable(self, object, *attributes)
140    
141      def GetTable(self):      def GetTable(self):
142          return self.tableRef()          try:
143                return self.tableRef()
144            except:
145                return None
146    
147      def DeleteSelectedRows(self):      def DeleteSelectedRows(self):
148            """Deletes all highlighted rows.
149    
150            If only one row is highlighted then after it is deleted the
151            row that was below the deleted row is highlighted."""
152    
153          sel = self.GetCurrentSelection()          sel = self.GetCurrentSelection()
154    
155            # nothing to do
156          if len(sel) == 0: return          if len(sel) == 0: return
157    
158            # if only one thing is selected check if it is the default
159            # data row, because we can't remove that
160          if len(sel) == 1:          if len(sel) == 1:
161              group = self.GetTable().GetValueAsCustom(sel[0], COL_VISUAL, None)              #group = self.GetTable().GetValueAsCustom(sel[0], COL_SYMBOL, None)
162                group = self.GetTable().GetClassGroup(sel[0])
163              if isinstance(group, ClassGroupDefault):              if isinstance(group, ClassGroupDefault):
164                  wxMessageDialog(self,                  wx.MessageDialog(self,
165                                  "The Default group cannot be removed.",                                  _("The Default group cannot be removed."),
166                                  style = wxOK | wxICON_EXCLAMATION).ShowModal()                                  style = wx.OK | wx.ICON_EXCLAMATION).ShowModal()
167                  return                  return
168            
169    
170          self.ClearSelection()          self.ClearSelection()
171    
172            # we need to remove things from the bottom up so we don't
173            # change the indexes of rows that will be deleted next
174          sel.reverse()          sel.reverse()
175    
176            #
177            # actually remove the rows
178            #
179          table = self.GetTable()          table = self.GetTable()
180          for row in sel:          for row in sel:
181              table.DeleteRows(row)              table.DeleteRows(row)
182    
183            #
184            # if there was only one row selected highlight the row
185            # that was directly below it, or move up one if the
186            # deleted row was the last row.
187            #
188          if len(sel) == 1:          if len(sel) == 1:
189              r = sel[0]              r = sel[0]
190              if r > self.GetNumberRows() - 1:              if r > self.GetNumberRows() - 1:
191                  r = self.GetNumberRows() - 1                  r = self.GetNumberRows() - 1
192              self.SelectRow(r)              self.SelectRow(r)
193            
194    
195        def SelectGroup(self, group, makeVisible = True):
196            if group is None: return
197    
198            assert isinstance(group, ClassGroup)
199    
200            table = self.GetTable()
201    
202            assert table is not None
203    
204            for i in range(table.GetNumberRows()):
205                g = table.GetClassGroup(i)
206                if g is group:
207                    self.SelectRow(i)
208                    if makeVisible:
209                        self.MakeCellVisible(i, 0)
210                    break
211    
212  #  #
213  # XXX: This isn't working, and there is no way to deselect rows wxPython!  # XXX: This isn't working, and there is no way to deselect rows wxPython!
214  #  #
215  #   def DeselectRow(self, row):  #   def DeselectRow(self, row):
216  #       self.ProcessEvent(  #       self.ProcessEvent(
217  #           wxGridRangeSelectEvent(-1,  #           GridRangeSelectEvent(-1,
218  #                                  wxEVT_GRID_RANGE_SELECT,  #                                  wxEVT_GRID_RANGE_SELECT,
219  #                                  self,  #                                  self,
220  #                                  (row, row), (row, row),  #                                  (row, row), (row, row),
221  #                                  sel = False))  #                                  sel = False))
222    
223      def OnCellDClick(self, event):      def _OnCellDClick(self, event):
224            """Handle a double click on a cell."""
225    
226          r = event.GetRow()          r = event.GetRow()
227          c = event.GetCol()          c = event.GetCol()
228          if c == COL_VISUAL:  
229              # XXX: getting the properties is only possible with non-Maps!!!          if c == COL_SYMBOL:
230              group = self.GetTable().GetValueAsCustom(r, c, None)              self.classifier.EditSymbol(r)
231              prop = group.GetProperties()          else:
232              propDlg = SelectPropertiesDialog(NULL, prop, self.layer.ShapeType())              event.Skip()
             if propDlg.ShowModal() == wxID_OK:  
                 new_prop = propDlg.GetClassGroupProperties()  
                 prop.SetStroke(new_prop.GetStroke())  
                 prop.SetStrokeWidth(new_prop.GetStrokeWidth())  
                 prop.SetFill(new_prop.GetFill())  
                 self.Refresh()  
             propDlg.Destroy()  
233    
234      #      #
235      # _OnSelectedRange() and _OnSelectedCell() were borrowed      # _OnSelectedRange() and _OnSelectedCell() were borrowed
236      # from http://wiki.wxpython.org      # from http://wiki.wxpython.org to keep track of which
237        # cells are currently highlighted
238      #      #
239      def _OnSelectedRange(self, event):      def _OnSelectedRange(self, event):
240          """Internal update to the selection tracking list"""          """Internal update to the selection tracking list"""
241          if event.Selecting():          if event.Selecting():
242              for index in range( event.GetTopRow(), event.GetBottomRow()+1):              for index in range( event.GetTopRow(), event.GetBottomRow()+1):
243                  if index not in self.currentSelection:                  if index not in self.currentSelection:
244                      self.currentSelection.append( index )                      self.currentSelection.append( index )
245          else:          else:
246              for index in range( event.GetTopRow(), event.GetBottomRow()+1):              for index in range( event.GetTopRow(), event.GetBottomRow()+1):
247                  while index in self.currentSelection:                  while index in self.currentSelection:
248                      self.currentSelection.remove( index )                      self.currentSelection.remove( index )
249          #self.ConfigureForSelection()          #self.ConfigureForSelection()
250    
251          event.Skip()          event.Skip()
252    
253      def _OnSelectedCell( self, event ):      def _OnSelectedCell( self, event ):
254          """Internal update to the selection tracking list"""          """Internal update to the selection tracking list"""
255          self.currentSelection = [ event.GetRow() ]          self.currentSelection = [ event.GetRow() ]
256          #self.ConfigureForSelection()          #self.ConfigureForSelection()
257          event.Skip()          event.Skip()
258    
259        def _OnCellResize(self, event):
260            self.FitInside()
261            event.Skip()
262    
263        def _OnLabelRightClicked(self, event):
264            """Process right click on label, raise popup for row labels."""
265            row, col = event.GetRow(), event.GetCol()
266            if col == -1:
267                self.labelPopup(event, row)
268    
269        def labelPopup(self, event, row):
270            """Raise grid label popup."""
271            # check if row label is Pattern or Singleton
272            label = self.GetRowLabelValue(row)
273            if (label == _("Pattern") or label == _("Singleton")):
274                xe,ye = event.GetPosition()
275                x=self.GetRowSize(row)/2
276                menu = wx.Menu()
277                patternID = wx.NewId()
278                singletonID = wx.NewId()
279    
280                def _SetSingleton(event, self=self, row=row):
281                    table = self.GetTable()
282                    group = table.clazz.GetGroup(row - 1)
283                    if not isinstance(group, ClassGroupSingleton):
284                        ngroup = ClassGroupSingleton(
285                                    group.GetPattern(),
286                                    group.GetProperties(),
287                                    group.GetLabel()
288                                    )
289                        table.SetClassGroup(row, ngroup)
290    
291                def _SetPattern(event, self=self, row=row):
292                    table = self.GetTable()
293                    group = table.clazz.GetGroup(row - 1)
294                    if not isinstance(group, ClassGroupPattern):
295                        try:
296                            re.compile(group.GetValue())
297                        except:
298                            pass
299                        else:
300                            ngroup = ClassGroupPattern(
301                                    group.GetValue(),
302                                    group.GetProperties(),
303                                    group.GetLabel()
304                                    )
305                            table.SetClassGroup(row, ngroup)
306    
307                menu.Append(singletonID, _("Singleton"))
308                self.Bind(wx.EVT_MENU, _SetSingleton, id=singletonID)
309                if self.GetTable().fieldType == FIELDTYPE_STRING:
310                    menu.Append(patternID, _("Pattern"))
311                    self.Bind(wx.EVT_MENU, _SetPattern, id=patternID)
312                self.PopupMenu(menu, wx.Point(x,ye))
313                menu.Destroy()
314    
315    class ClassTable(grid.PyGridTableBase):
316        """Represents the underlying data structure for the grid."""
317    
318        __col_labels = [_("Visible"), _("Symbol"), _("Value"), _("Label")]
319    
 class ClassTable(wxPyGridTableBase):  
320    
321      NUM_COLS = 3      def __init__(self, view = None):
322            """Constructor.
323    
324      __col_labels = [_("Symbol"), _("Value"), _("Label")]          shapeType -- the type of shape that the layer uses
325    
326            view -- a Grid object that uses this class for its table
327            """
328    
329            grid.PyGridTableBase.__init__(self)
330    
331            assert len(ClassTable.__col_labels) == NUM_COLS
332    
333            self.clazz = None
334            self.__colAttr = {}
335    
     def __init__(self, fieldData, shapeType, view = None):  
         wxPyGridTableBase.__init__(self)  
336          self.SetView(view)          self.SetView(view)
         self.tdata = []  
337    
338          self.Reset(fieldData, shapeType)      def Reset(self, clazz, fieldType, shapeType, group = None):
339            """Reset the table with the given data.
340    
341            This is necessary because wxWindows does not allow a grid's
342            table to change once it has been intially set and so we
343            need a way of modifying the data.
344    
345            clazz -- the working classification that this table should
346                     use for display. This may be different from the
347                     classification in the layer.
348    
349            shapeType -- the type of shape that the layer uses
350            """
351    
352      def Reset(self, fieldData, shapeType):          assert isinstance(clazz, Classification)
353    
354          self.GetView().BeginBatch()          self.GetView().BeginBatch()
355    
356          self.fieldData = fieldData          self.fieldType = fieldType
357          self.shapeType = shapeType          self.shapeType = shapeType
         self.renderer = ClassRenderer(self.shapeType)  
358    
359          old_len = len(self.tdata)          self.SetClassification(clazz, group)
360            self.__Modified(-1)
361    
362          self.tdata = []          self.__colAttr = {}
363    
364          clazz = fieldData[FIELD_CLASS]          attr = grid.GridCellAttr()
365          if clazz is None:          attr.SetEditor(grid.GridCellBoolEditor())
366              clazz = Classification()          attr.SetRenderer(grid.GridCellBoolRenderer())
367            attr.SetAlignment(wx.ALIGN_CENTER, wx.ALIGN_CENTER)
368            self.__colAttr[COL_VISIBLE] = attr
369    
370            attr = grid.GridCellAttr()
371            attr.SetRenderer(ClassRenderer(self.shapeType))
372            attr.SetReadOnly()
373            self.__colAttr[COL_SYMBOL] = attr
374    
375            self.GetView().EndBatch()
376            self.GetView().FitInside()
377    
378        def GetClassification(self):
379            """Return the current classification."""
380            return self.clazz
381    
382        def SetClassification(self, clazz, group = None):
383            """Fill in the table with the given classification.
384            Select the given group if group is not None.
385            """
386    
387            self.GetView().BeginBatch()
388    
389            old_len = self.GetNumberRows()
390    
391            row = -1
392            self.clazz = clazz
393    
394            self.__NotifyRowChanges(old_len, self.GetNumberRows())
395    
396  #       p = clazz.GetDefaultGroup()          #
397  #       np = ClassDataDefault(classData = p)          # XXX: this is dead code at the moment
398  #       self.tdata.append([np, 'DEFAULT', np.GetLabel()])          #
399            if row > -1:
400  #       for p in clazz.points.values():              self.GetView().ClearSelection()
401  #           np = ClassDataPoint(p.GetValue(), classData = p)              self.GetView().SelectRow(row)
402  #           self.tdata.append([np, np.GetValue(), np.GetLabel()])              self.GetView().MakeCellVisible(row, 0)
   
 #       for p in clazz.ranges:  
 #           np = ClassDataRange(p.GetMin(), p.GetMax(), classData = p)  
 #           self.tdata.append([np,  
 #                              '%s - %s' % (np.GetMin(), np.GetMax()),  
 #                              np.GetLabel()])  
   
         i = 0  
         for p in clazz:  
             np = copy.copy(p)  
             self.__SetRow(i, np)  
             i += 1  
403    
404            self.__Modified()
         self.modified = 0  
405    
         self.__NotifyRowChanges(old_len, len(self.tdata))  
406          self.GetView().EndBatch()          self.GetView().EndBatch()
407            self.GetView().FitInside()
408    
409      def __NotifyRowChanges(self, curRows, newRows):      def __NotifyRowChanges(self, curRows, newRows):
410            """Make sure table updates correctly if the number of
411            rows changes.
412            """
413          #          #
414          # silly message processing for updates to the number of          # silly message processing for updates to the number of
415          # rows and columns          # rows and columns
416          #          #
417          if newRows > curRows:          if newRows > curRows:
418              msg = wxGridTableMessage(self,              msg = grid.GridTableMessage(self,
419                          wxGRIDTABLE_NOTIFY_ROWS_APPENDED,                          grid.GRIDTABLE_NOTIFY_ROWS_APPENDED,
420                          newRows - curRows)    # how many                          newRows - curRows)    # how many
421              self.GetView().ProcessTableMessage(msg)              self.GetView().ProcessTableMessage(msg)
422                self.GetView().FitInside()
423          elif newRows < curRows:          elif newRows < curRows:
424              msg = wxGridTableMessage(self,              msg = grid.GridTableMessage(self,
425                          wxGRIDTABLE_NOTIFY_ROWS_DELETED,                          grid.GRIDTABLE_NOTIFY_ROWS_DELETED,
426                          curRows - newRows,    # position                          curRows,              # position
427                          curRows - newRows)    # how many                          curRows - newRows)    # how many
428              self.GetView().ProcessTableMessage(msg)              self.GetView().ProcessTableMessage(msg)
429                self.GetView().FitInside()
430    
431      def __SetRow(self, row, group):      def __SetRow(self, row, group):
432            """Set a row's data to that of the group.
433    
434          if isinstance(group, ClassGroupDefault):          The table is considered modified after this operation.
             data = [group, _('DEFAULT'), group.GetLabel()]  
         elif isinstance(group, ClassGroupSingleton):  
             data = [group, group.GetValue(), group.GetLabel()]  
         elif isinstance(group, ClassGroupRange):  
             data = [group,  
                     _('%s - %s') % (group.GetMin(), group.GetMax()),  
                     group.GetLabel()]  
435    
436          if row >= len(self.tdata):          row -- if row is < 0 'group' is inserted at the top of the table
437              self.tdata.append(data)                 if row is >= GetNumberRows() or None 'group' is append to
438                        the end of the table.
439                   otherwise 'group' replaces row 'row'
440            """
441    
442            # either append or replace
443            if row is None or row >= self.GetNumberRows():
444                self.clazz.AppendGroup(group)
445            elif row < 0:
446                self.clazz.InsertGroup(0, group)
447          else:          else:
448              self.tdata[row] = data              if row == 0:
449                    self.clazz.SetDefaultGroup(group)
450                else:
451                    self.clazz.ReplaceGroup(row - 1, group)
452    
453            self.__Modified()
454    
455      def GetColLabelValue(self, col):      def GetColLabelValue(self, col):
456            """Return the label for the given column."""
457          return self.__col_labels[col]          return self.__col_labels[col]
458    
459      def GetRowLabelValue(self, row):      def GetRowLabelValue(self, row):
460          data = self.tdata[row][COL_VISUAL]          """Return the label for the given row."""
461          if isinstance(data, ClassGroupDefault):   return _("Default")  
462          if isinstance(data, ClassGroupSingleton): return _("Singleton")          if row == 0:
463          if isinstance(data, ClassGroupRange):     return _("Range")              return _("Default")
464          if isinstance(data, ClassGroupMap):       return _("Map")          else:
465                group = self.clazz.GetGroup(row - 1)
466                if isinstance(group, ClassGroupDefault):   return _("Default")
467                if isinstance(group, ClassGroupSingleton): return _("Singleton")
468                if isinstance(group, ClassGroupPattern):   return _("Pattern")
469                if isinstance(group, ClassGroupRange):     return _("Range")
470                if isinstance(group, ClassGroupMap):       return _("Map")
471    
472            assert False # shouldn't get here
473            return ""
474    
475      def GetNumberRows(self):      def GetNumberRows(self):
476          return len(self.tdata)          """Return the number of rows."""
477            if self.clazz is None:
478                return 0
479    
480            return self.clazz.GetNumGroups() + 1 # +1 for default group
481    
482      def GetNumberCols(self):      def GetNumberCols(self):
483          return self.NUM_COLS          """Return the number of columns."""
484            return NUM_COLS
485    
486      def IsEmptyCell(self, row, col):      def IsEmptyCell(self, row, col):
487          return 0          """Determine if a cell is empty. This is always false."""
488            return False
489    
490      def GetValue(self, row, col):      def GetValue(self, row, col):
491          return self.GetValueAsCustom(row, col, "")          """Return the object that is used to represent the given
492               cell coordinates. This may not be a string."""
493            return self.GetValueAsCustom(row, col, None)
494    
495      def SetValue(self, row, col, value):      def SetValue(self, row, col, value):
496          self.SetValueAsCustom(row, col, "", value)          """Assign 'value' to the cell specified by 'row' and 'col'.
497          self.__Modified()  
498                  The table is considered modified after this operation.
499            """
500    
501            self.SetValueAsCustom(row, col, None, value)
502    
503      def GetValueAsCustom(self, row, col, typeName):      def GetValueAsCustom(self, row, col, typeName):
504          return self.tdata[row][col]          """Return the object that is used to represent the given
505               cell coordinates. This may not be a string.
506    
507            typeName -- unused, but needed to overload wxPyGridTableBase
508            """
509    
510            if row == 0:
511                group = self.clazz.GetDefaultGroup()
512            else:
513                group = self.clazz.GetGroup(row - 1)
514    
515    
516            if col == COL_VISIBLE:
517                return group.IsVisible()
518    
519            if col == COL_SYMBOL:
520                return group.GetProperties()
521    
522            if col == COL_LABEL:
523                return group.GetLabel()
524    
525            # col must be COL_VALUE
526            assert col == COL_VALUE
527    
528            if isinstance(group, ClassGroupDefault):
529                return _("DEFAULT")
530            elif isinstance(group, ClassGroupSingleton):
531                return group.GetValue()
532            elif isinstance(group, ClassGroupPattern):
533                return group.GetPattern()
534            elif isinstance(group, ClassGroupRange):
535                return group.GetRange()
536    
537            assert False # shouldn't get here
538            return None
539    
540      def __ParseInput(self, value):      def __ParseInput(self, value):
541          """Try to determine what kind of input value is          """Try to determine what kind of input value is
542             (a single number or a range)             (string, number, or range)
543    
544            Returns a tuple (type, data) where type is 0 if data is
545            a singleton value, 1 if is a range or 2 if it is a pattern.
546          """          """
547    
548          type = self.fieldData[FIELD_TYPE]          type = self.fieldType
549    
550          if type == FIELD_TYPE_STRING:          if type == FIELDTYPE_STRING:
551              return (value,)              # Approach: if we can compile the value as an expression,
552          elif type == FIELD_TYPE_INT or type == FIELD_TYPE_DOUBLE:              # make it a pattern, else a singleton.
553                # This is quite crude, however I don't have a better idea:
554                # How to distinct the singleton "Thuban" from the pattern "Thuban"?
555                try:
556                    re.compile(value)
557                except:
558                    return (0, value)
559                else:
560                    return (2, value)
561            elif type in (FIELDTYPE_INT, FIELDTYPE_DOUBLE):
562                if type == FIELDTYPE_INT:
563                    # the float call allows the user to enter 1.0 for 1
564                    conv = lambda p: int(float(p))
565                else:
566                    conv = float
567    
568              #              #
569              # first try to take the input as a single number              # first try to take the input as a single number
570              # if there's an exception try to break it into              # if there's an exception try to break it into
571              # a range seperated by a '-'. take care to ignore              # a range. if there is an exception here, let it
572              # a leading '-' as that could be for a negative number.              # pass up to the calling function.
             # then try to parse the individual parts. if there  
             # is an exception here, let it pass up to the calling  
             # function.  
573              #              #
574              try:              try:
575                  return (Str2Num(value),)                  return (0, conv(value))
576              except:              except ValueError:
577                  i = value.find('-')                  return (1, Range(value))
                 if i == 0:  
                     i = value.find('-', 1)  
578    
579                  return (Str2Num(value[:i]), Str2Num(value[i+1:]))          assert False  # shouldn't get here
580                        return (0,None)
581    
582      def SetValueAsCustom(self, row, col, typeName, value):      def SetValueAsCustom(self, row, col, typeName, value):
583          group = self.tdata[row][COL_VISUAL]          """Set the cell specified by 'row' and 'col' to 'value'.
584    
585            If column represents the value column, the input is parsed
586            to determine if a string, number, or range was entered.
587            A new ClassGroup may be created if the type of data changes.
588    
589            The table is considered modified after this operation.
590    
591          if col == COL_VISUAL:          typeName -- unused, but needed to overload wxPyGridTableBase
592              self.tdata[row][COL_VISUAL] = value          """
593    
594            assert 0 <= col < self.GetNumberCols()
595            assert 0 <= row < self.GetNumberRows()
596    
597            if row == 0:
598                group = self.clazz.GetDefaultGroup()
599            else:
600                group = self.clazz.GetGroup(row - 1)
601    
602            mod = True # assume the data will change
603    
604            if col == COL_VISIBLE:
605                group.SetVisible(value)
606            elif col == COL_SYMBOL:
607                group.SetProperties(value)
608            elif col == COL_LABEL:
609                group.SetLabel(value)
610          elif col == COL_VALUE:          elif col == COL_VALUE:
611              if isinstance(group, ClassGroupDefault):              if isinstance(group, ClassGroupDefault):
612                  # not allowed to modify the default value                  # not allowed to modify the default value
# Line 318  class ClassTable(wxPyGridTableBase): Line 617  class ClassTable(wxPyGridTableBase):
617              else: # SINGLETON, RANGE              else: # SINGLETON, RANGE
618                  try:                  try:
619                      dataInfo = self.__ParseInput(value)                      dataInfo = self.__ParseInput(value)
620                  except:                  except ValueError:
621                      # bad input, ignore the request                      # bad input, ignore the request
622                      pass                      mod = False
623                  else:                  else:
624    
625                        changed = False
626                      ngroup = group                      ngroup = group
627                      props = group.GetProperties()                      props = group.GetProperties()
628                      if len(dataInfo) == 1:  
629                        #
630                        # try to update the values, which may include
631                        # changing the underlying group type if the
632                        # group was a singleton and a range was entered
633                        #
634                        if dataInfo[0] == 0:
635                          if not isinstance(group, ClassGroupSingleton):                          if not isinstance(group, ClassGroupSingleton):
636                              ngroup = ClassGroupSingleton(prop = props)                              ngroup = ClassGroupSingleton(props = props)
637                          ngroup.SetValue(dataInfo[0])                              changed = True
638                      elif len(dataInfo) == 2:                          ngroup.SetValue(dataInfo[1])
639                        elif dataInfo[0] == 1:
640                          if not isinstance(group, ClassGroupRange):                          if not isinstance(group, ClassGroupRange):
641                              ngroup = ClassGroupRange(prop = props)                              ngroup = ClassGroupRange(props = props)
642                          ngroup.SetRange(dataInfo[0], dataInfo[1])                              changed = True
643                            ngroup.SetRange(dataInfo[1])
644                        elif dataInfo[0] == 2:
645                            if not isinstance(group, ClassGroupPattern):
646                                ngroup = ClassGroupPattern(props = props)
647                                changed = True
648                            ngroup.SetPattern(dataInfo[1])
649                      else:                      else:
650                          assert(False)                          assert False
651                            pass
                     ngroup.SetLabel(group.GetLabel())  
                     self.__SetRow(row, ngroup)  
652    
653                      self.GetView().Refresh()                      if changed:
654                            ngroup.SetLabel(group.GetLabel())
655          elif col == COL_LABEL:                          self.SetClassGroup(row, ngroup)
             group.SetLabel(value)  
             self.tdata[row][COL_LABEL] = group.GetLabel()  
656          else:          else:
657              raise ValueError(_("Invalid column request"))              assert False # shouldn't be here
658                pass
659    
660          self.__Modified()          if mod:
661                self.__Modified()
662                self.GetView().Refresh()
663    
664      def GetAttr(self, row, col, someExtraParameter):      def GetAttr(self, row, col, someExtraParameter):
665          attr = wxGridCellAttr()          """Returns the cell attributes"""
         #attr = wxPyGridTableBase.GetAttr(self, row, col, someExtraParameter)  
   
         if col == COL_VISUAL:  
             attr.SetRenderer(ClassRenderer(self.shapeType))  
             attr.SetReadOnly()  
666    
667          return attr          return self.__colAttr.get(col, grid.GridCellAttr()).Clone()
668    
669      def GetClassGroup(self, row):      def GetClassGroup(self, row):
670          return self.tdata[row][COL_VISUAL]          """Return the ClassGroup object representing row 'row'."""
671    
672      def __Modified(self):          #return self.GetValueAsCustom(row, COL_SYMBOL, None)
673          self.modified = 1          if row == 0:
674                return self.clazz.GetDefaultGroup()
675            else:
676                return self.clazz.GetGroup(row - 1)
677    
678        def SetClassGroup(self, row, group):
679            """Set the given row to properties of group."""
680            self.__SetRow(row, group)
681            self.GetView().Refresh()
682    
683        def __Modified(self, mod = True):
684            """Adjust the modified flag.
685    
686            mod -- if -1 set the modified flag to False, otherwise perform
687                   an 'or' operation with the current value of the flag and
688                   'mod'
689            """
690    
691            if mod == -1:
692                self.modified = False
693            else:
694                self.modified = mod or self.modified
695    
696      def IsModified(self):      def IsModified(self):
697            """True if this table is considered modified."""
698          return self.modified          return self.modified
699    
700      def DeleteRows(self, pos, numRows = 1):      def DeleteRows(self, pos, numRows = 1):
701          assert(pos >= 0)          """Deletes 'numRows' beginning at row 'pos'.
702          old_len = len(self.tdata)  
703            The row representing the default group is not removed.
704    
705            The table is considered modified if any rows are removed.
706            """
707    
708            assert pos >= 0
709            old_len = self.GetNumberRows()
710          for row in range(pos, pos - numRows, -1):          for row in range(pos, pos - numRows, -1):
711              group = self.GetValueAsCustom(row, COL_VISUAL, None)              group = self.GetClassGroup(row)
712              if not isinstance(group, ClassGroupDefault):              if row != 0:
713                  self.tdata.pop(row)                  self.clazz.RemoveGroup(row - 1)
714                  self.__Modified()                  self.__Modified()
715        
716          if self.IsModified():          if self.IsModified():
717              self.__NotifyRowChanges(old_len, len(self.tdata))              self.__NotifyRowChanges(old_len, self.GetNumberRows())
718    
719      def AppendRows(self, numRows = 1):      def AppendRows(self, numRows = 1):
720          old_len = len(self.tdata)          """Append 'numRows' empty rows to the end of the table.
721    
722            The table is considered modified if any rows are appended.
723            """
724    
725            old_len = self.GetNumberRows()
726          for i in range(numRows):          for i in range(numRows):
727              np = ClassGroupSingleton()              np = ClassGroupSingleton()
728              self.tdata.append([np, np.GetValue(), np.GetLabel()])              self.__SetRow(None, np)
             self.__Modified()  
729    
730          if self.IsModified():          if self.IsModified():
731              self.__NotifyRowChanges(old_len, len(self.tdata))              self.__NotifyRowChanges(old_len, self.GetNumberRows())
732    
733    
734    ID_PROPERTY_REVERT = 4002
735    ID_PROPERTY_ADD = 4003
736    ID_PROPERTY_GENCLASS = 4004
737    ID_PROPERTY_REMOVE = 4005
738    ID_PROPERTY_MOVEUP = 4006
739    ID_PROPERTY_MOVEDOWN = 4007
740    ID_PROPERTY_TRY = 4008
741    ID_PROPERTY_EDITSYM = 4009
742    ID_PROPERTY_SELECT = 4011
743    ID_PROPERTY_TITLE = 4012
744    ID_PROPERTY_FIELDTEXT = 4013
745    
746    BTN_ADD = 0
747    BTN_EDIT = 1
748    BTN_GEN = 2
749    BTN_UP = 3
750    BTN_DOWN = 4
751    BTN_RM = 5
752    
753    EB_LAYER_TITLE = 0
754    EB_SELECT_FIELD = 1
755    EB_GEN_CLASS = 2
756    
757    class Classifier(LayerProperties):
758    
759        type2string = {None:             _("None"),
760                       FIELDTYPE_STRING: _("Text"),
761                       FIELDTYPE_INT:    _("Integer"),
762                       FIELDTYPE_DOUBLE: _("Decimal")}
763    
764        def __init__(self, parent, name, layer, group = None):
765            """Create a Properties/Classification dialog for a layer.
766            The layer is part of map. If group is not None, select that
767            group in the classification table.
768            """
769    
770            LayerProperties.__init__(self, parent, name, layer)
771    
772            self.layer.Subscribe(LAYER_SHAPESTORE_REPLACED,
773                                 self.layer_shapestore_replaced)
774    
775  class Classifier(wxDialog):          self.genDlg = None
776                self.group = group
     def __init__(self, parent, layer):  
         wxDialog.__init__(self, parent, -1, _("Classify"),  
                           style = wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)  
777    
778            LayerProperties.dialog_layout(self)
779    
780          self.layer = layer      def dialog_layout(self, panel, panelBox):
781    
782          topBox = wxBoxSizer(wxVERTICAL)          if self.layer.HasClassification():
783    
784          topBox.Add(wxStaticText(self, -1, _("Layer: %s") % layer.Title()),              self.fieldTypeText = wx.StaticText(panel, -1, "")
             0, wxALIGN_LEFT | wxALL, 4)  
         topBox.Add(wxStaticText(self, -1, _("Type: %s") % layer.ShapeType()),  
             0, wxALIGN_LEFT | wxALL, 4)  
785    
786          propertyBox = wxBoxSizer(wxHORIZONTAL)              self.originalClass = self.layer.GetClassification()
787          propertyBox.Add(wxStaticText(self, -1, _("Field: ")),              self.originalClassField = self.layer.GetClassificationColumn()
788              0, wxALIGN_CENTER | wxALL, 4)              field = self.originalClassField
789                fieldType = self.layer.GetFieldType(field)
790    
791                table = self.layer.ShapeStore().Table()
792                #
793                # make field choice box
794                #
795                self.fields = wx.Choice(panel, ID_PROPERTY_SELECT,)
796    
797          self.fields = wxComboBox(self, ID_PROPERTY_SELECT, "",              self.num_cols = table.NumColumns()
798                                       style = wxCB_READONLY)              # just assume the first field in case one hasn't been
799                # specified in the file.
800                self.__cur_field = 0
801    
802          self.num_cols = layer.table.field_count()              self.fields.Append("<None>")
         # just assume the first field in case one hasn't been  
         # specified in the file.  
         self.__cur_field = 0  
         clazz = layer.GetClassification()  
         field = clazz.GetField()  
         for i in range(self.num_cols):  
             type, name, len, decc = layer.table.field_info(i)  
             self.fields.Append(name)  
803    
804              if name == field:              if fieldType is None:
805                  self.__cur_field = i                  self.fields.SetClientData(0, copy.deepcopy(self.originalClass))
                 self.fields.SetClientData(i, [clazz, type, name, len, decc])  
806              else:              else:
807                  self.fields.SetClientData(i, [None, type, name, len, decc])                  self.fields.SetClientData(0, None)
808    
809          self.fields.SetSelection(self.__cur_field)              for i in range(self.num_cols):
810                    name = table.Column(i).name
811                    self.fields.Append(name)
812    
813                    if name == field:
814                        self.__cur_field = i + 1
815                        self.fields.SetClientData(i + 1,
816                                                  copy.deepcopy(self.originalClass))
817                    else:
818                        self.fields.SetClientData(i + 1, None)
819    
820          propertyBox.Add(self.fields, 1, wxGROW|wxALL, 4)              button_gen = wx.Button(panel, ID_PROPERTY_GENCLASS,
821          EVT_COMBOBOX(self, ID_PROPERTY_SELECT, self.OnFieldSelect)                  _("Generate Class"))
822                button_add = wx.Button(panel, ID_PROPERTY_ADD,
823                    _("Add"))
824                button_moveup = wx.Button(panel, ID_PROPERTY_MOVEUP,
825                    _("Move Up"))
826                button_movedown = wx.Button(panel, ID_PROPERTY_MOVEDOWN,
827                    _("Move Down"))
828                button_edit = wx.Button(panel, ID_PROPERTY_EDITSYM,
829                    _("Edit Symbol"))
830                button_remove = wx.Button(panel, ID_PROPERTY_REMOVE,
831                    _("Remove"))
832    
833                self.classGrid = ClassGrid(panel, self)
834    
835                # calling __SelectField after creating the classGrid fills in the
836                # grid with the correct information
837                self.fields.SetSelection(self.__cur_field)
838                self.__SelectField(self.__cur_field, group = self.group)
839    
840    
841                classBox = wx.StaticBoxSizer(
842                            wx.StaticBox(panel, -1, _("Classification")), wx.VERTICAL)
843    
844    
845                sizer = wx.BoxSizer(wx.HORIZONTAL)
846                sizer.Add(wx.StaticText(panel, ID_PROPERTY_FIELDTEXT, _("Field: ")),
847                    0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 4)
848                sizer.Add(self.fields, 1, wx.GROW | wx.ALL, 4)
849    
850                classBox.Add(sizer, 0, wx.GROW, 4)
851    
852                classBox.Add(self.fieldTypeText, 0,
853                            wx.GROW | wx.ALIGN_LEFT | wx.ALL | wx.ADJUST_MINSIZE, 4)
854    
855                controlBox = wx.BoxSizer(wx.HORIZONTAL)
856                controlButtonBox = wx.BoxSizer(wx.VERTICAL)
857    
858                controlButtonBox.Add(button_gen, 0, wx.GROW|wx.ALL, 4)
859                controlButtonBox.Add(button_add, 0, wx.GROW|wx.ALL, 4)
860                controlButtonBox.Add(button_moveup, 0, wx.GROW|wx.ALL, 4)
861                controlButtonBox.Add(button_movedown, 0, wx.GROW|wx.ALL, 4)
862                controlButtonBox.Add(button_edit, 0, wx.GROW|wx.ALL, 4)
863                controlButtonBox.Add( (60, 20), 0, wx.GROW|wx.ALL|wx.ALIGN_BOTTOM, 4)
864                controlButtonBox.Add(button_remove, 0,
865                                     wx.GROW|wx.ALL|wx.ALIGN_BOTTOM, 4)
866    
867                controlBox.Add(self.classGrid, 1, wx.GROW, 0)
868                controlBox.Add(controlButtonBox, 0, wx.GROW, 10)
869    
870                classBox.Add(controlBox, 1, wx.GROW, 10)
871                panelBox.Add(classBox, 1, wx.GROW, 0)
872    
873    
874            self.Bind(wx.EVT_CHOICE, self._OnFieldSelect, id=ID_PROPERTY_SELECT)
875            self.Bind(wx.EVT_BUTTON, self._OnAdd, id=ID_PROPERTY_ADD)
876            self.Bind(wx.EVT_BUTTON, self._OnEditSymbol, id=ID_PROPERTY_EDITSYM)
877            self.Bind(wx.EVT_BUTTON, self._OnRemove, id=ID_PROPERTY_REMOVE)
878            self.Bind(wx.EVT_BUTTON, self._OnGenClass, id=ID_PROPERTY_GENCLASS)
879            self.Bind(wx.EVT_BUTTON, self._OnMoveUp, id=ID_PROPERTY_MOVEUP)
880            self.Bind(wx.EVT_BUTTON, self._OnMoveDown, id=ID_PROPERTY_MOVEDOWN)
881    
882        def unsubscribe_messages(self):
883            """Unsubscribe from all messages."""
884            LayerProperties.unsubscribe_messages(self)
885            self.layer.Unsubscribe(LAYER_SHAPESTORE_REPLACED,
886                                   self.layer_shapestore_replaced)
887    
888        def layer_shapestore_replaced(self, *args):
889            """Subscribed to the map's LAYER_SHAPESTORE_REPLACED message.
890            Close self.
891            """
892            self.Close()
893    
894          topBox.Add(propertyBox, 0, wxGROW, 4)      def EditSymbol(self, row):
895            """Open up a dialog where the user can select the properties
896            for a group.
897            """
898            table = self.classGrid.GetTable()
899            prop = table.GetValueAsCustom(row, COL_SYMBOL, None)
900    
901          #          # get a new ClassGroupProperties object and copy the
902          # Classification data table          # values over to our current object
903          #          propDlg = SelectPropertiesDialog(self, prop, self.layer.ShapeType())
904    
905            self.Enable(False)
906            if propDlg.ShowModal() == wx.ID_OK:
907                new_prop = propDlg.GetClassGroupProperties()
908                table.SetValueAsCustom(row, COL_SYMBOL, None, new_prop)
909            self.Enable(True)
910            propDlg.Destroy()
911    
912        def _SetClassification(self, clazz):
913            """Called from the ClassGen dialog when a new classification has
914            been created and should be set in the table.
915            """
916            # FIXME: This could be implemented using a message
917    
918          controlBox = wxBoxSizer(wxHORIZONTAL)          self.fields.SetClientData(self.__cur_field, clazz)
919          self.classGrid = ClassGrid(self,          self.classGrid.GetTable().SetClassification(clazz)
                                    layer,  
                                    self.fields.GetClientData(self.__cur_field))  
   
         controlBox.Add(self.classGrid, 1, wxGROW, 0)  
   
         controlButtonBox = wxBoxSizer(wxVERTICAL)  
         controlButtonBox.Add(wxButton(self, ID_CLASSIFY_ADD,  
             _("Add")), 0, wxGROW | wxALL, 4)  
         controlButtonBox.Add(wxButton(self, ID_CLASSIFY_GENRANGE,  
             _("Generate Ranges")), 0, wxGROW | wxALL, 4)  
   
         controlButtonBox.Add(wxButton(self, ID_CLASSIFY_REMOVE,  
             _("Remove")), 0, wxGROW | wxALL | wxALIGN_BOTTOM, 4)  
   
         controlBox.Add(controlButtonBox, 0, wxGROW, 10)  
         topBox.Add(controlBox, 1, wxGROW, 10)  
   
         EVT_BUTTON(self, ID_CLASSIFY_ADD, self.OnAdd)  
         EVT_BUTTON(self, ID_CLASSIFY_REMOVE, self.OnRemove)  
         EVT_BUTTON(self, ID_CLASSIFY_GENRANGE, self.OnGenRange)  
920    
921          #      def __BuildClassification(self, fieldIndex, copyClass=False, force=False):
922          # Control buttons:          """Pack the classification setting into a Classification object.
923          #          Returns (Classification, fieldName) where fieldName is the selected
924          buttonBox = wxBoxSizer(wxHORIZONTAL)          field in the table that the classification should be used with.
925          buttonBox.Add(wxButton(self, ID_CLASSIFY_OK, _("OK")),          """
                       0, wxALL, 4)  
         buttonBox.Add(wxButton(self, ID_CLASSIFY_CANCEL, _("Cancel")),  
                       0, wxALL, 4)  
         topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)  
926    
927          EVT_BUTTON(self, ID_CLASSIFY_OK, self.OnOK)  #       numRows = self.classGrid.GetNumberRows()
928          EVT_BUTTON(self, ID_CLASSIFY_CANCEL, self.OnCancel)  #       assert numRows > 0  # there should always be a default row
929    
930            if fieldIndex == 0:
931                fieldName = None
932                fieldType = None
933            else:
934                fieldName = internal_from_wxstring(self.fields.GetString(fieldIndex))
935                fieldType = self.layer.GetFieldType(fieldName)
936    
937            clazz = self.fields.GetClientData(fieldIndex)
938            if clazz is None or self.classGrid.GetTable().IsModified() or force:
939                clazz = self.classGrid.GetTable().GetClassification()
940                if copyClass:
941                    clazz = copy.deepcopy(clazz)
942    
943            return clazz, fieldName
944    
945        def __SetGridTable(self, fieldIndex, group = None):
946            """Set the table with the classification associated with the
947            selected field at fieldIndex. Select the specified group
948            if group is not None.
949            """
950    
951          self.SetAutoLayout(true)          clazz = self.fields.GetClientData(fieldIndex)
952          self.SetSizer(topBox)  
953          topBox.Fit(self)          if clazz is None:
954          topBox.SetSizeHints(self)              clazz = Classification()
955                clazz.SetDefaultGroup(
956                    ClassGroupDefault(
957                        self.layer.GetClassification().
958                                   GetDefaultGroup().GetProperties()))
959    
960            fieldName = internal_from_wxstring(self.fields.GetString(fieldIndex))
961            fieldType = self.layer.GetFieldType(fieldName)
962    
963            self.classGrid.CreateTable(clazz, fieldType,
964                                       self.layer.ShapeType(), group)
965    
966        def __SetFieldTypeText(self, fieldIndex):
967            """Set the field type string using the data type of the field
968            at fieldIndex.
969            """
970            fieldName = internal_from_wxstring(self.fields.GetString(fieldIndex))
971            fieldType = self.layer.GetFieldType(fieldName)
972    
973      def __BuildClassification(self, prop):          assert Classifier.type2string.has_key(fieldType)
974    
975          clazz = Classification()          text = Classifier.type2string[fieldType]
         clazz.SetField(self.fields.GetString(prop))  
976    
977          numRows = self.classGrid.GetNumberRows()          self.fieldTypeText.SetLabel(_("Data Type: %s") % text)
978    
979          if numRows > 0:      def __SelectField(self, newIndex, oldIndex = -1, group = None):
980              table = self.classGrid.GetTable()          """This method assumes that the current selection for the
981              clazz.SetDefaultGroup(table.GetClassGroup(0))          combo has already been set by a call to SetSelection().
982            """
983    
984              for i in range(1, numRows):          assert oldIndex >= -1
                 clazz.AddGroup(table.GetClassGroup(i))  
985    
986          return clazz          if oldIndex != -1:
987                clazz, name = self.__BuildClassification(oldIndex, force = True)
988                self.fields.SetClientData(oldIndex, clazz)
989    
990      def OnFieldSelect(self, event):          self.__SetGridTable(newIndex, group)
         data = self.fields.GetClientData(self.__cur_field)  
         data[FIELD_CLASS] = self.__BuildClassification(self.__cur_field)  
991    
992          self.fields.SetClientData(self.__cur_field, data)          self.__EnableButtons(EB_SELECT_FIELD)
993    
994          self.__cur_field = self.fields.GetSelection()          self.__SetFieldTypeText(newIndex)
         fieldData = self.fields.GetClientData(self.__cur_field)  
         self.classGrid.GetTable().Reset(fieldData, self.layer.ShapeType())  
995    
996      def OnOK(self, event):      def __SetTitle(self, title):
997            """Set the title of the dialog."""
998            if title != "":
999                title = ": " + title
1000    
1001            self.SetTitle(_("Layer Properties") + title)
1002    
1003        def _OnEditSymbol(self, event):
1004            """Open up a dialog for the user to select group properties."""
1005            sel = self.classGrid.GetCurrentSelection()
1006    
1007            if len(sel) == 1:
1008                self.EditSymbol(sel[0])
1009    
1010        def _OnFieldSelect(self, event):
1011            index = self.fields.GetSelection()
1012            self.__SelectField(index, self.__cur_field)
1013            self.__cur_field = index
1014    
1015        def OnTry(self, event):
1016          """Put the data from the table into a new Classification and hand          """Put the data from the table into a new Classification and hand
1017             it to the layer.             it to the layer.
1018          """          """
1019    
1020          clazz = self.fields.GetClientData(self.__cur_field)[FIELD_CLASS]          if self.layer.HasClassification():
1021                clazz = self.fields.GetClientData(self.__cur_field)
1022    
1023          #              #
1024          # only build the classification if there wasn't one to              # only build the classification if there wasn't one to
1025          # to begin with or it has been modified              # to begin with or it has been modified
1026          #              #
1027          if clazz is None or self.classGrid.GetTable().IsModified():              self.classGrid.SaveEditControlValue()
1028              clazz = self.__BuildClassification(self.__cur_field)              clazz, name = self.__BuildClassification(self.__cur_field, True)
1029    
1030          clazz.SetLayer(self.layer)              self.layer.SetClassificationColumn(name)
1031                self.layer.SetClassification(clazz)
1032    
1033          self.layer.SetClassification(clazz)          self.haveApplied = True
1034    
1035          self.EndModal(wxID_OK)      def OnOK(self, event):
1036            self.OnTry(event)
1037            self.Close()
1038    
1039      def OnCancel(self, event):      def OnRevert(self, event):
1040          """Do nothing. The layer's current classification stays the same."""          """The layer's current classification stays the same."""
1041          self.EndModal(wxID_CANCEL)          if self.haveApplied and self.layer.HasClassification():
1042                self.layer.SetClassificationColumn(self.originalClassField)
1043                self.layer.SetClassification(self.originalClass)
1044    
1045      def OnAdd(self, event):          #self.Close()
1046    
1047        def _OnAdd(self, event):
1048          self.classGrid.AppendRows()          self.classGrid.AppendRows()
1049    
1050      def OnRemove(self, event):      def _OnRemove(self, event):
1051          self.classGrid.DeleteSelectedRows()          self.classGrid.DeleteSelectedRows()
1052    
1053      def OnGenRange(self, event):      def _OnGenClass(self, event):
1054          print "Classifier.OnGenRange()"          """Open up a dialog for the user to generate classifications."""
1055    
1056            self.genDlg = ClassGenDialog(self, self.layer,
1057                  internal_from_wxstring(self.fields.GetString(self.__cur_field)))
1058    
1059            self.Bind(wx.EVT_CLOSE, self._OnGenDialogClose, self.genDlg)
1060    
1061  ID_SELPROP_OK = 4001          self.__EnableButtons(EB_GEN_CLASS)
1062  ID_SELPROP_CANCEL = 4002  
1063  ID_SELPROP_SPINCTRL = 4002          self.genDlg.Show()
1064    
1065        def _OnGenDialogClose(self, event):
1066            """Reenable buttons after the generate classification
1067            dialog is closed.
1068            """
1069            self.genDlg.Destroy()
1070            self.genDlg = None
1071            self.__EnableButtons(EB_GEN_CLASS)
1072    
1073        def _OnMoveUp(self, event):
1074            """When the user clicks MoveUp, try to move a group up one row."""
1075            sel = self.classGrid.GetCurrentSelection()
1076    
1077            if len(sel) == 1:
1078                i = sel[0]
1079                if i > 1:
1080                    table = self.classGrid.GetTable()
1081                    x = table.GetClassGroup(i - 1)
1082                    y = table.GetClassGroup(i)
1083                    table.SetClassGroup(i - 1, y)
1084                    table.SetClassGroup(i, x)
1085                    self.classGrid.ClearSelection()
1086                    self.classGrid.SelectRow(i - 1)
1087                    self.classGrid.MakeCellVisible(i - 1, 0)
1088    
1089        def _OnMoveDown(self, event):
1090            """When the user clicks MoveDown, try to move a group down one row."""
1091            sel = self.classGrid.GetCurrentSelection()
1092    
1093            if len(sel) == 1:
1094                i = sel[0]
1095                table = self.classGrid.GetTable()
1096                if 0 < i < table.GetNumberRows() - 1:
1097                    x = table.GetClassGroup(i)
1098                    y = table.GetClassGroup(i + 1)
1099                    table.SetClassGroup(i, y)
1100                    table.SetClassGroup(i + 1, x)
1101                    self.classGrid.ClearSelection()
1102                    self.classGrid.SelectRow(i + 1)
1103                    self.classGrid.MakeCellVisible(i + 1, 0)
1104    
1105        def _OnTitleChanged(self, event):
1106            """Update the dialog title when the user changed the layer name."""
1107            obj = event.GetEventObject()
1108    
1109            self.layer.SetTitle(obj.GetValue())
1110            self.__SetTitle(self.layer.Title())
1111    
1112            self.__EnableButtons(EB_LAYER_TITLE)
1113    
1114        def __EnableButtons(self, case):
1115            """Helper method that enables/disables the appropriate buttons
1116            based on the case provided. Cases are constants beginning with EB_.
1117            """
1118    
1119            list = {wx.ID_OK                 : True,
1120                    wx.ID_CANCEL             : True,
1121                    ID_PROPERTY_ADD         : True,
1122                    ID_PROPERTY_MOVEUP      : True,
1123                    ID_PROPERTY_MOVEDOWN    : True,
1124                    ID_PROPERTY_REMOVE      : True,
1125                    ID_PROPERTY_SELECT      : True,
1126                    ID_PROPERTY_FIELDTEXT   : True,
1127                    ID_PROPERTY_GENCLASS    : True,
1128                    ID_PROPERTY_EDITSYM     : True}
1129    
1130            if case == EB_LAYER_TITLE:
1131                if self.layer.Title() == "":
1132                    list[wxID_OK] = False
1133                    list[wxID_CANCEL] = False
1134    
1135            elif case == EB_SELECT_FIELD:
1136                if self.fields.GetSelection() == 0:
1137                    list[ID_PROPERTY_GENCLASS] = False
1138                    list[ID_PROPERTY_ADD] = False
1139                    list[ID_PROPERTY_MOVEUP] = False
1140                    list[ID_PROPERTY_MOVEDOWN] = False
1141                    list[ID_PROPERTY_REMOVE] = False
1142    
1143            elif case == EB_GEN_CLASS:
1144                if self.genDlg is not None:
1145                    list[ID_PROPERTY_SELECT] = False
1146                    list[ID_PROPERTY_FIELDTEXT] = False
1147                    list[ID_PROPERTY_GENCLASS] = False
1148    
1149            for id, enable in list.items():
1150                win = self.FindWindowById(id)
1151                if win:
1152                    win.Enable(enable)
1153    
1154    ID_SELPROP_SPINCTRL_LINEWIDTH = 4002
1155  ID_SELPROP_PREVIEW = 4003  ID_SELPROP_PREVIEW = 4003
1156  ID_SELPROP_STROKECLR = 4004  ID_SELPROP_STROKECLR = 4004
1157  ID_SELPROP_FILLCLR = 4005  ID_SELPROP_FILLCLR = 4005
1158    ID_SELPROP_STROKECLRTRANS = 4006
1159    ID_SELPROP_FILLCLRTRANS = 4007
1160    ID_SELPROP_SPINCTRL_SIZE = 4008
1161    
1162  class SelectPropertiesDialog(wxDialog):  class SelectPropertiesDialog(wx.Dialog):
1163        """Dialog that allows the user to select group properties."""
1164    
1165      def __init__(self, parent, prop, shapeType):      def __init__(self, parent, prop, shapeType):
1166          wxDialog.__init__(self, parent, -1, _("Select Properties"),          """Open the dialog with the initial prop properties and shapeType."""
1167                            style = wxRESIZE_BORDER)  
1168            wx.Dialog.__init__(self, parent, -1, _("Select Properties"),
1169                              style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
1170    
1171          self.prop = ClassGroupProperties(prop)          self.prop = ClassGroupProperties(prop)
1172    
1173          topBox = wxBoxSizer(wxVERTICAL)          topBox = wx.BoxSizer(wx.VERTICAL)
1174    
1175          itemBox = wxBoxSizer(wxHORIZONTAL)          itemBox = wx.BoxSizer(wx.HORIZONTAL)
1176    
1177          # preview box          # preview box
1178          previewBox = wxBoxSizer(wxVERTICAL)          previewBox = wx.BoxSizer(wx.VERTICAL)
1179          previewBox.Add(wxStaticText(self, -1, _("Preview:")),          previewBox.Add(wx.StaticText(self, -1, _("Preview:")),
1180              0, wxALIGN_LEFT | wxALL, 4)              0, wx.ALIGN_LEFT | wx.ALL, 4)
1181          self.previewer = ClassDataPreviewer(None, self.prop, shapeType,  
1182                                              self, ID_SELPROP_PREVIEW, (40, 40))          self.previewWin = ClassGroupPropertiesCtrl(
1183          previewBox.Add(self.previewer, 1, wxGROW, 15)              self, ID_SELPROP_PREVIEW, self.prop, shapeType,
1184                (40, 40), wx.SIMPLE_BORDER)
1185    
1186            self.previewWin.AllowEdit(False)
1187    
1188            previewBox.Add(self.previewWin, 1, wx.GROW | wx.ALL, 4)
1189    
1190          itemBox.Add(previewBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)          itemBox.Add(previewBox, 1, wx.ALIGN_LEFT | wx.ALL | wx.GROW, 0)
1191    
1192          # control box          # control box
1193          ctrlBox = wxBoxSizer(wxVERTICAL)          ctrlBox = wx.BoxSizer(wx.VERTICAL)
1194          ctrlBox.Add(  
1195              wxButton(self, ID_SELPROP_STROKECLR, "Change Stroke Color"),          lineColorBox = wx.BoxSizer(wx.HORIZONTAL)
1196              1, wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)          button = wx.Button(self, ID_SELPROP_STROKECLR, _("Change Line Color"))
1197          EVT_BUTTON(self, ID_SELPROP_STROKECLR, self.OnChangeStrokeColor)          button.SetFocus()
1198            lineColorBox.Add(button, 1, wx.ALL | wx.GROW, 4)
1199            self.Bind(wx.EVT_BUTTON, self._OnChangeLineColor, id=ID_SELPROP_STROKECLR)
1200    
1201            lineColorBox.Add(
1202                wx.Button(self, ID_SELPROP_STROKECLRTRANS, _("Transparent")),
1203                1, wx.ALL | wx.GROW, 4)
1204            self.Bind(wx.EVT_BUTTON, self._OnChangeLineColorTrans, \
1205                                       id=ID_SELPROP_STROKECLRTRANS)
1206    
1207            ctrlBox.Add(lineColorBox, 0,
1208                        wx.ALIGN_CENTER_HORIZONTAL | wx.ALL | wx.GROW, 4)
1209    
1210          if shapeType != SHAPETYPE_ARC:          if shapeType != SHAPETYPE_ARC:
1211              ctrlBox.Add(              fillColorBox = wx.BoxSizer(wx.HORIZONTAL)
1212                  wxButton(self, ID_SELPROP_FILLCLR, "Change Fill Color"),              fillColorBox.Add(
1213                  0, wxALIGN_LEFT | wxALL | wxGROW, 4)                  wx.Button(self, ID_SELPROP_FILLCLR, _("Change Fill Color")),
1214              EVT_BUTTON(self, ID_SELPROP_FILLCLR, self.OnChangeFillColor)                  1, wx.ALL | wx.GROW, 4)
1215                self.Bind(wx.EVT_BUTTON, self._OnChangeFillColor, id=ID_SELPROP_FILLCLR)
1216          spinBox = wxBoxSizer(wxHORIZONTAL)              fillColorBox.Add(
1217          spinBox.Add(wxStaticText(self, -1, _("Stroke Width: ")),                  wx.Button(self, ID_SELPROP_FILLCLRTRANS, _("Transparent")),
1218                  0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)                  1, wx.ALL | wx.GROW, 4)
1219          self.spinCtrl = wxSpinCtrl(self, ID_SELPROP_SPINCTRL,              self.Bind(wx.EVT_BUTTON, self._OnChangeFillColorTrans,\
1220                                     min=1, max=10,                                          id=ID_SELPROP_FILLCLRTRANS)
1221                                     value=str(prop.GetStrokeWidth()),              ctrlBox.Add(fillColorBox, 0,
1222                                     initial=prop.GetStrokeWidth())                          wx.ALIGN_CENTER_HORIZONTAL | wx.ALL | wx.GROW, 4)
1223    
1224          EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL, self.OnSpin)          # Line width selection
1225            spinBox = wx.BoxSizer(wx.HORIZONTAL)
1226          spinBox.Add(self.spinCtrl, 0, wxALIGN_LEFT | wxALL, 4)          spinBox.Add(wx.StaticText(self, -1, _("Line Width: ")),
1227                    0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 4)
1228          ctrlBox.Add(spinBox, 0, wxALIGN_RIGHT | wxALL, 0)          self.spinCtrl_linewidth = wx.SpinCtrl(self,
1229          itemBox.Add(ctrlBox, 0, wxALIGN_RIGHT | wxALL | wxGROW, 0)                                               ID_SELPROP_SPINCTRL_LINEWIDTH,
1230          topBox.Add(itemBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)                                               min=1, max=10,
1231                                                 value=str(prop.GetLineWidth()),
1232                                                 initial=prop.GetLineWidth())
1233    
1234            self.Bind(wx.EVT_SPINCTRL, self._OnSpinLineWidth, \
1235                        id=ID_SELPROP_SPINCTRL_LINEWIDTH)
1236    
1237            spinBox.Add(self.spinCtrl_linewidth, 0, wx.ALIGN_LEFT | wx.ALL, 4)
1238            ctrlBox.Add(spinBox, 0, wx.ALIGN_RIGHT | wx.ALL, 0)
1239    
1240            # Size selection
1241            if shapeType == SHAPETYPE_POINT:
1242                spinBox = wx.BoxSizer(wx.HORIZONTAL)
1243                spinBox.Add(wx.StaticText(self, -1, _("Size: ")),
1244                            0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 4)
1245                self.spinCtrl_size = wx.SpinCtrl(self, ID_SELPROP_SPINCTRL_SIZE,
1246                                                min=1, max=100,
1247                                                value=str(prop.GetSize()),
1248                                                initial=prop.GetSize())
1249    
1250                self.Bind(wx.EVT_SPINCTRL, self._OnSpinSize, id=ID_SELPROP_SPINCTRL_SIZE)
1251    
1252                spinBox.Add(self.spinCtrl_size, 0, wx.ALIGN_LEFT | wx.ALL, 4)
1253                ctrlBox.Add(spinBox, 0, wx.ALIGN_RIGHT | wx.ALL, 0)
1254    
1255    
1256            itemBox.Add(ctrlBox, 0, wx.ALIGN_RIGHT | wx.ALL | wx.GROW, 0)
1257            topBox.Add(itemBox, 1, wx.ALIGN_LEFT | wx.ALL | wx.GROW, 0)
1258    
1259          #          #
1260          # Control buttons:          # Control buttons:
1261          #          #
1262          buttonBox = wxBoxSizer(wxHORIZONTAL)          buttonBox = wx.BoxSizer(wx.HORIZONTAL)
1263          buttonBox.Add(wxButton(self, ID_CLASSIFY_OK, _("OK")),          button_ok = wx.Button(self, wx.ID_OK, _("OK"))
1264                        0, wxALL, 4)          buttonBox.Add(button_ok, 0, wx.RIGHT|wx.EXPAND, 10)
1265          buttonBox.Add(wxButton(self, ID_CLASSIFY_CANCEL, _("Cancel")),          buttonBox.Add(wx.Button(self, wx.ID_CANCEL, _("Cancel")),
1266                        0, wxALL, 4)                        0, wx.RIGHT|wx.EXPAND, 10)
1267          topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)          topBox.Add(buttonBox, 0, wx.ALIGN_RIGHT|wx.BOTTOM|wx.TOP, 10)
1268                                                                                    
1269          EVT_BUTTON(self, ID_SELPROP_OK, self.OnOK)          button_ok.SetDefault()
1270          EVT_BUTTON(self, ID_SELPROP_CANCEL, self.OnCancel)  
1271                                                                                            #EVT_BUTTON(self, wxID_OK, self._OnOK)
1272          self.SetAutoLayout(true)          #EVT_BUTTON(self, ID_SELPROP_CANCEL, self._OnCancel)
1273    
1274            self.SetAutoLayout(True)
1275          self.SetSizer(topBox)          self.SetSizer(topBox)
1276          topBox.Fit(self)          topBox.Fit(self)
1277          topBox.SetSizeHints(self)          topBox.SetSizeHints(self)
1278    
1279      def OnOK(self, event):      def OnOK(self, event):
1280          self.EndModal(wxID_OK)          self.EndModal(wx.ID_OK)
1281    
1282      def OnCancel(self, event):      def OnCancel(self, event):
1283          self.EndModal(wxID_CANCEL)          self.EndModal(wx.ID_CANCEL)
1284    
1285      def OnSpin(self, event):      def _OnSpinLineWidth(self, event):
1286          self.prop.SetStrokeWidth(self.spinCtrl.GetValue())          self.prop.SetLineWidth(self.spinCtrl_linewidth.GetValue())
1287          self.previewer.Refresh()          self.previewWin.Refresh()
1288    
1289        def _OnSpinSize(self, event):
1290            self.prop.SetSize(self.spinCtrl_size.GetValue())
1291            self.previewWin.Refresh()
1292    
1293      def __GetColor(self, cur):      def __GetColor(self, cur):
1294          dialog = wxColourDialog(self)          dialog = ColorDialog(self)
1295          dialog.GetColourData().SetColour(Color2wxColour(cur))          dialog.SetColor(cur)
1296    
1297          ret = None          ret = None
1298          if dialog.ShowModal() == wxID_OK:          if dialog.ShowModal() == wx.ID_OK:
1299              ret = wxColour2Color(dialog.GetColourData().GetColour())              ret = dialog.GetColor()
1300    
1301          dialog.Destroy()          dialog.Destroy()
1302    
1303          return ret          return ret
1304            
1305      def OnChangeStrokeColor(self, event):      def _OnChangeLineColor(self, event):
1306          clr = self.__GetColor(self.prop.GetStroke())          clr = self.__GetColor(self.prop.GetLineColor())
1307          if clr is not None:          if clr is not None:
1308              self.prop.SetStroke(clr)              self.prop.SetLineColor(clr)
1309          self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer          self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1310    
1311      def OnChangeFillColor(self, event):      def _OnChangeLineColorTrans(self, event):
1312            self.prop.SetLineColor(Transparent)
1313            self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1314    
1315        def _OnChangeFillColor(self, event):
1316          clr = self.__GetColor(self.prop.GetFill())          clr = self.__GetColor(self.prop.GetFill())
1317          if clr is not None:          if clr is not None:
1318              self.prop.SetFill(clr)              self.prop.SetFill(clr)
1319          self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer          self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1320    
1321        def _OnChangeFillColorTrans(self, event):
1322            self.prop.SetFill(Transparent)
1323            self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1324    
1325      def GetClassGroupProperties(self):      def GetClassGroupProperties(self):
1326          return self.prop          return self.prop
1327    
1328    
1329  class ClassDataPreviewer(wxWindow):  class ClassDataPreviewWindow(wx.Window):
1330        """A custom window that draws group properties using the correct shape."""
1331    
1332      def __init__(self, rect, prop, shapeType,      def __init__(self, rect, prop, shapeType,
1333                         parent = None, id = -1, size = wxDefaultSize):                         parent = None, id = -1, size = wx.DefaultSize):
1334            """Draws the appropriate shape as specified with shapeType using
1335            prop properities.
1336            """
1337          if parent is not None:          if parent is not None:
1338              wxWindow.__init__(self, parent, id, size=size)              wx.Window.__init__(self, parent, id, (0, 0), size)
1339              EVT_PAINT(self, self.OnPaint)              self.Bind(wx.EVT_PAINT, self._OnPaint)
1340    
1341          self.rect = rect          self.rect = rect
1342    
1343          self.prop = prop          self.prop = prop
1344          self.shapeType = shapeType          self.shapeType = shapeType
1345            self.previewer = ClassDataPreviewer()
1346    
1347        def GetProperties():
1348            return self.prop
1349    
1350      def OnPaint(self, event):      def _OnPaint(self, event):
1351          dc = wxPaintDC(self)          dc = wx.PaintDC(self)
1352    
1353          # XXX: this doesn't seem to be having an effect:          # XXX: this doesn't seem to be having an effect:
1354          dc.DestroyClippingRegion()          dc.DestroyClippingRegion()
1355    
1356            if self.rect is None:
1357                w, h = self.GetSize()
1358                rect = wx.Rect(0, 0, w, h)
1359            else:
1360                rect = self.rect
1361    
1362          self.Draw(dc, None)          self.previewer.Draw(dc, rect, self.prop, self.shapeType)
1363    
1364      def Draw(self, dc, rect, prop = None, shapeType = None):  class ClassDataPreviewer:
1365        """Class that actually draws a group property preview."""
1366    
1367          if prop is None: prop = self.prop      def Draw(self, dc, rect, prop, shapeType):
1368          if shapeType is None: shapeType = self.shapeType          """Draw the property.
1369    
1370            returns: (w, h) as adapted extend if the drawing size
1371            exceeded the given rect. This can only be the case
1372            for point symbols. If the symbol fits the given rect,
1373            None is returned.
1374            """
1375    
1376            assert dc is not None
1377            assert isinstance(prop, ClassGroupProperties)
1378    
1379          if rect is None:          if rect is None:
1380              x = y = 0              x = 0
1381              w, h = self.GetClientSizeTuple()              y = 0
1382                w = dc.GetSize().GetWidth()
1383                h = dc.GetSize().GetHeight()
1384          else:          else:
1385              x = rect.GetX()              x = rect.GetX()
1386              y = rect.GetY()              y = rect.GetY()
1387              w = rect.GetWidth()              w = rect.GetWidth()
1388              h = rect.GetHeight()              h = rect.GetHeight()
1389    
1390          stroke = prop.GetStroke()          stroke = prop.GetLineColor()
1391          if stroke is Color.None:          if stroke is Transparent:
1392              pen = wxTRANSPARENT_PEN              pen = wx.TRANSPARENT_PEN
1393          else:          else:
1394              pen = wxPen(Color2wxColour(stroke),              pen = wx.Pen(Color2wxColour(stroke),
1395                          prop.GetStrokeWidth(),                          prop.GetLineWidth(),
1396                          wxSOLID)                          wx.SOLID)
1397    
1398          stroke = prop.GetFill()          stroke = prop.GetFill()
1399          if stroke is Color.None:          if stroke is Transparent:
1400              brush = wxTRANSPARENT_BRUSH              brush = wx.TRANSPARENT_BRUSH
1401          else:          else:
1402              brush = wxBrush(Color2wxColour(stroke), wxSOLID)              brush = wx.Brush(Color2wxColour(stroke), wx.SOLID)
1403    
1404          dc.SetPen(pen)          dc.SetPen(pen)
1405          dc.SetBrush(brush)          dc.SetBrush(brush)
1406    
1407          if shapeType == SHAPETYPE_ARC:          if shapeType == SHAPETYPE_ARC:
1408              dc.DrawSpline([wxPoint(x, y + h),              dc.DrawSpline([wx.Point(x, y + h),
1409                             wxPoint(x + w/2, y + h/4),                             wx.Point(x + w/2, y + h/4),
1410                             wxPoint(x + w/2, y + h/4*3),                             wx.Point(x + w/2, y + h/4*3),
1411                             wxPoint(x + w, y)])                             wx.Point(x + w, y)])
1412    
1413          elif shapeType == SHAPETYPE_POINT or \          elif shapeType == SHAPETYPE_POINT:
1414               shapeType == SHAPETYPE_POLYGON:  
1415                dc.DrawCircle(x + w/2, y + h/2, prop.GetSize())
1416              dc.DrawCircle(x + w/2, y + h/2,              circle_size =  prop.GetSize() * 2 + prop.GetLineWidth() * 2
1417                            (min(w, h) - prop.GetStrokeWidth())/2)              new_h = h
1418                new_w = w
1419  class ClassRenderer(wxPyGridCellRenderer):              if h < circle_size: new_h = circle_size
1420                if w < circle_size: new_w = circle_size
1421                if new_h > h or new_w > w:
1422                    return (new_w, new_h)
1423    
1424            elif shapeType == SHAPETYPE_POLYGON:
1425                dc.DrawRectangle(x, y, w, h)
1426    
1427            return None
1428    
1429    class ClassRenderer(grid.PyGridCellRenderer):
1430        """A wrapper class that can be used to draw group properties in a
1431        grid table.
1432        """
1433    
1434      def __init__(self, shapeType):      def __init__(self, shapeType):
1435          wxPyGridCellRenderer.__init__(self)          grid.PyGridCellRenderer.__init__(self)
1436          self.previewer = ClassDataPreviewer(None, None, shapeType)          self.shapeType = shapeType
1437            self.previewer = ClassDataPreviewer()
1438    
1439      def Draw(self, grid, attr, dc, rect, row, col, isSelected):      def Draw(self, grid, attr, dc, rect, row, col, isSelected):
1440          data = grid.GetTable().GetValueAsCustom(row, col, None)          data = grid.GetTable().GetClassGroup(row)
1441    
1442          dc.SetClippingRegion(rect.GetX(), rect.GetY(),          dc.SetClippingRegion(rect.GetX(), rect.GetY(),
1443                               rect.GetWidth(), rect.GetHeight())                               rect.GetWidth(), rect.GetHeight())
1444          dc.SetPen(wxPen(wxLIGHT_GREY))          dc.SetPen(wx.Pen(wx.LIGHT_GREY))
1445          dc.SetBrush(wxBrush(wxLIGHT_GREY, wxSOLID))          dc.SetBrush(wx.Brush(wx.LIGHT_GREY, wx.SOLID))
1446          dc.DrawRectangle(rect.GetX(), rect.GetY(),          dc.DrawRectangle(rect.GetX(), rect.GetY(),
1447                           rect.GetWidth(), rect.GetHeight())                           rect.GetWidth(), rect.GetHeight())
1448    
1449          if not isinstance(data, ClassGroupMap):          if not isinstance(data, ClassGroupMap):
1450              self.previewer.Draw(dc, rect, data.GetProperties())              new_size = self.previewer.Draw(dc, rect, data.GetProperties(),
1451                                               self.shapeType)
1452                if new_size is not None:
1453                    (new_w, new_h) = new_size
1454                    grid.SetRowSize(row, new_h)
1455                    grid.SetColSize(col, new_h)
1456                    grid.ForceRefresh()
1457    
1458                    # now that we know the height, redraw everything
1459                    rect.SetHeight(new_h)
1460                    rect.SetWidth(new_w)
1461                    dc.DestroyClippingRegion()
1462                    dc.SetClippingRegion(rect.GetX(), rect.GetY(),
1463                                         rect.GetWidth(), rect.GetHeight())
1464                    dc.SetPen(wx.Pen(wx.LIGHT_GREY))
1465                    dc.SetBrush(wx.Brush(wx.LIGHT_GREY, wx.SOLID))
1466                    dc.DrawRectangle(rect.GetX(), rect.GetY(),
1467                                     rect.GetWidth(), rect.GetHeight())
1468                    self.previewer.Draw(dc, rect, data.GetProperties(),
1469                                        self.shapeType)
1470    
1471          if isSelected:          if isSelected:
1472              dc.SetPen(wxPen(wxColour(0 * 255, 0 * 255, 0 * 255),              dc.SetPen(wx.Pen(wx.BLACK, 1, wx.SOLID))
1473                        4, wxSOLID))              dc.SetBrush(wx.TRANSPARENT_BRUSH)
1474              dc.SetBrush(wxTRANSPARENT_BRUSH)  
1475              dc.DrawRectangle(rect.GetX(), rect.GetY(),              dc.DrawRectangle(rect.GetX(), rect.GetY(),
1476                               rect.GetWidth(), rect.GetHeight())                               rect.GetWidth(), rect.GetHeight())
1477    
1478          dc.DestroyClippingRegion()          dc.DestroyClippingRegion()
1479    
1480    
1481    class ClassGroupPropertiesCtrl(wx.Control):
1482        """A custom window and control that draw a preview of group properties
1483        and can open a dialog to modify the properties if the user double-clicks
1484        it.
1485        """
1486    
1487        def __init__(self, parent, id, props, shapeType,
1488                     size = wx.DefaultSize, style = 0):
1489            wx.Control.__init__(self, parent, id, size = size, style = style)
1490    
1491            self.parent = parent
1492    
1493            self.SetProperties(props)
1494            self.SetShapeType(shapeType)
1495            self.AllowEdit(True)
1496    
1497            self.Bind(wx.EVT_PAINT, self._OnPaint)
1498            self.Bind(wx.EVT_LEFT_DCLICK, self._OnLeftDClick)
1499    
1500            self.previewer = ClassDataPreviewer()
1501    
1502        def _OnPaint(self, event):
1503            dc = wx.PaintDC(self)
1504    
1505            # XXX: this doesn't seem to be having an effect:
1506            dc.DestroyClippingRegion()
1507    
1508            w, h = self.GetClientSize()
1509    
1510            self.previewer.Draw(dc,
1511                                wx.Rect(0, 0, w, h),
1512                                self.GetProperties(),
1513                                self.GetShapeType())
1514    
1515    
1516        def GetProperties(self):
1517            return self.props
1518    
1519        def SetProperties(self, props):
1520            self.props = props
1521            self.Refresh()
1522    
1523        def GetShapeType(self):
1524            return self.shapeType
1525    
1526        def SetShapeType(self, shapeType):
1527            self.shapeType = shapeType
1528            self.Refresh()
1529    
1530        def AllowEdit(self, allow):
1531            """Allow/Disallow double-clicking on the control."""
1532            self.allowEdit = allow
1533    
1534        def DoEdit(self):
1535            """Open the properties selector dialog."""
1536    
1537            if not self.allowEdit: return
1538    
1539            propDlg = SelectPropertiesDialog(self.parent,
1540                                             self.GetProperties(),
1541                                             self.GetShapeType())
1542    
1543            if propDlg.ShowModal() == wx.ID_OK:
1544                new_prop = propDlg.GetClassGroupProperties()
1545                self.SetProperties(new_prop)
1546                self.Refresh()
1547    
1548            propDlg.Destroy()
1549    
1550        def _OnLeftDClick(self, event):
1551            self.DoEdit()
1552    

Legend:
Removed from v.451  
changed lines
  Added in v.2817

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26