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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26