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

Legend:
Removed from v.535  
changed lines
  Added in v.2556

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26