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

Legend:
Removed from v.444  
changed lines
  Added in v.2688

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26