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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26