/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/UI/classifier.py
ViewVC logotype

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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26