/[thuban]/trunk/thuban/Thuban/UI/classifier.py
ViewVC logotype

Diff of /trunk/thuban/Thuban/UI/classifier.py

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

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

Legend:
Removed from v.441  
changed lines
  Added in v.977

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26