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

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

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

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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26