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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26