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

Legend:
Removed from v.415  
changed lines
  Added in v.2362

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26