/[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 638 by jonathan, Thu Apr 10 14:35:20 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    
34    # widget id's
35  ID_PROPERTY_SELECT = 4010  ID_PROPERTY_SELECT = 4010
36  ID_CLASS_TABLE = 40011  ID_CLASS_TABLE = 40011
37    
 ID_CLASSIFY_OK = 4001  
 ID_CLASSIFY_CANCEL = 4002  
 ID_CLASSIFY_ADD = 4003  
 ID_CLASSIFY_GENRANGE = 4004  
38    
39  COL_VISUAL = 0  # table columns
40  COL_VALUE  = 1  COL_VISIBLE = 0
41  COL_LABEL  = 2  COL_SYMBOL  = 1
42    COL_VALUE   = 2
43    COL_LABEL   = 3
44    NUM_COLS    = 4
45    
46    # indices into the client data lists in Classifier.fields
47    FIELD_CLASS = 0
48    FIELD_TYPE = 1
49    FIELD_NAME = 2
50    
51  #  #
52  # 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 55  COL_LABEL  = 2
55  import weakref  import weakref
56  class ClassGrid(wxGrid):  class ClassGrid(wxGrid):
57    
     def __init__(self, parent, layer):  
         wxGrid.__init__(self, parent, ID_CLASS_TABLE, size = (300, 150))  
         self.SetTable(  
             ClassTable(layer.GetClassification(), layer.ShapeType(), self),  
             true)  
58    
59      def SetCellRenderer(self, row, col):      def __init__(self, parent, classifier):
60          raise ValueError(_("Must not allow setting of renderer in ClassGrid!"))          """Constructor.
61    
62            parent -- the parent window
63    
64            clazz -- the working classification that this grid should
65                     use for display.
66            """
67    
68            wxGrid.__init__(self, parent, ID_CLASS_TABLE)
69    
70            self.classifier = classifier
71    
72            self.currentSelection = []
73    
74            EVT_GRID_CELL_LEFT_DCLICK(self, self._OnCellDClick)
75            EVT_GRID_RANGE_SELECT(self, self._OnSelectedRange)
76            EVT_GRID_SELECT_CELL(self, self._OnSelectedCell)
77            EVT_GRID_COL_SIZE(self, self._OnCellResize)
78            EVT_GRID_ROW_SIZE(self, self._OnCellResize)
79    
80        #def GetCellAttr(self, row, col):
81            #print "GetCellAttr ", row, col
82            #wxGrid.GetCellAttr(self, row, col)
83    
84        def CreateTable(self, clazz, shapeType, group = None):
85    
86            assert isinstance(clazz, Classification)
87    
88            table = self.GetTable()
89            if table is None:
90                w = self.GetDefaultColSize() * 3 + self.GetDefaultRowLabelSize()
91                h = self.GetDefaultRowSize() * 4 + self.GetDefaultColLabelSize()
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            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      def Reset(self, clazz, shapeType):          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            attr = wxGridCellAttr()
304            attr.SetRenderer(ClassRenderer(self.shapeType))
305            attr.SetReadOnly()
306            self.__colAttr[COL_SYMBOL] = attr
307    
308            self.GetView().EndBatch()
309            self.GetView().FitInside()
310    
311          p = clazz.GetDefaultData()      def GetClassification(self):
312          np = ClassDataPoint(classData = p)          return self.clazz
         self.tdata.append([np, 'DEFAULT', np.GetLabel()])  
   
         for p in clazz.points.values():  
             np = ClassDataPoint(p.GetValue(), classData = p)  
             self.tdata.append([np, np.GetValue(), np.GetLabel()])  
   
         for p in clazz.ranges:  
             np = ClassDataRange(p.GetMin(), p.GetMax(), classData = p)  
             self.tdata.append([np,  
                                '%s - %s' % (np.GetMin(), np.GetMax()),  
                                np.GetLabel()])  
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.                  conv = lambda p: int(float(p))
481          #              else:
482          try:                  conv = lambda p: p
483              return (ClassData.POINT, Str2Num(value))  
484          except:              #
485              i = value.find('-')              # first try to take the input as a single number
486              if i == 0:              # if there's an exception try to break it into
487                  i = value.find('-', 1)              # a range seperated by a '-'. take care to ignore
488                # a leading '-' as that could be for a negative number.
489              return (ClassData.RANGE,              # then try to parse the individual parts. if there
490                      Str2Num(value[:i]),              # is an exception here, let it pass up to the calling
491                      Str2Num(value[i+1:]))              # function.
492                #
493                try:
494                    return (conv(Str2Num(value)),)
495                except ValueError:
496                    i = value.find('-')
497                    if i == 0:
498                        i = value.find('-', 1)
499    
500                    return (conv(Str2Num(value[:i])), conv(Str2Num(value[i+1:])))
501    
502            assert False  # shouldn't get here
503            return (0,)
504                            
505    
506      def SetValueAsCustom(self, row, col, typeName, value):      def SetValueAsCustom(self, row, col, typeName, value):
507          data = self.tdata[row][COL_VISUAL]          """Set the cell specified by 'row' and 'col' to 'value'.
508    
509          if col == COL_VISUAL:          If column represents the value column, the input is parsed
510              self.tdata[row][COL_VISUAL] = value          to determine if a string, number, or range was entered.
511          elif col == COL_VALUE:          A new ClassGroup may be created if the type of data changes.
             if row != 0: # DefaultData row  
                 type = data.GetType()  
512    
513                  if type == ClassData.MAP:          The table is considered modified after this operation.
                     # something special  
                     pass  
                 else: # POINT, RANGE  
                     try:  
                         dataInfo = self.__ParseInput(value)  
                     except: pass  
                         # bad input, ignore the request  
                     else:  
514    
515                          if dataInfo[0] == ClassData.POINT:          typeName -- unused, but needed to overload wxPyGridTableBase
516                              if type != ClassData.POINT:          """
517                                  data = ClassDataPoint(classData = data)  
518                              data.SetValue(dataInfo[1])          assert col >= 0 and col < self.GetNumberCols()
519                              self.tdata[row][COL_VALUE] = data.GetValue()          assert row >= 0 and row < self.GetNumberRows()
520                          elif dataInfo[0] == ClassData.RANGE:  
521                              if type != ClassData.RANGE:          if row == 0:
522                                  data = ClassDataRange(classData = data)              group = self.clazz.GetDefaultGroup()
523                              data.SetRange(dataInfo[1], dataInfo[2])          else:
524                              self.tdata[row][COL_VALUE] = \              group = self.clazz.GetGroup(row - 1)
                                 "%s - %s" % (data.GetMin(), data.GetMax())  
   
                         self.tdata[row][COL_VISUAL] = data  
   
                         # i just want it to redraw!  
                         self.GetView().BeginBatch()  
                         self.GetView().EndBatch()  
525    
526            mod = True # assume the data will change
527    
528            if col == COL_VISIBLE:
529                group.SetVisible(value)
530            elif col == COL_SYMBOL:
531                group.SetProperties(value)
532          elif col == COL_LABEL:          elif col == COL_LABEL:
533              data.SetLabel(value)              group.SetLabel(value)
534              self.tdata[row][COL_LABEL] = data.GetLabel()          elif col == COL_VALUE:
535                if isinstance(group, ClassGroupDefault):
536                    # not allowed to modify the default value
537                    pass
538                elif isinstance(group, ClassGroupMap):
539                    # something special
540                    pass
541                else: # SINGLETON, RANGE
542                    try:
543                        dataInfo = self.__ParseInput(value)
544                    except ValueError:
545                        # bad input, ignore the request
546                        mod = False
547                    else:
548    
549                        changed = False
550                        ngroup = group
551                        props = group.GetProperties()
552    
553                        #
554                        # try to update the values, which may include
555                        # changing the underlying group type if the
556                        # group was a singleton and a range was entered
557                        #
558                        if len(dataInfo) == 1:
559                            if not isinstance(group, ClassGroupSingleton):
560                                ngroup = ClassGroupSingleton(prop = props)
561                                changed = True
562                            ngroup.SetValue(dataInfo[0])
563                        elif len(dataInfo) == 2:
564                            if not isinstance(group, ClassGroupRange):
565                                ngroup = ClassGroupRange(prop = props)
566                                changed = True
567                            ngroup.SetRange(dataInfo[0], dataInfo[1])
568                        else:
569                            assert False
570                            pass
571    
572                        if changed:
573                            ngroup.SetLabel(group.GetLabel())
574                            self.SetClassGroup(row, ngroup)
575          else:          else:
576              raise ValueError(_("Invalid column request"))              assert False # shouldn't be here
577                pass
578    
579          self.__Modified()          if mod:
580                self.__Modified()
581                self.GetView().Refresh()
582    
583      def GetAttr(self, row, col, someExtraParameter):      def GetAttr(self, row, col, someExtraParameter):
584          attr = wxGridCellAttr()          """Returns the cell attributes"""
585          #attr = wxPyGridTableBase.GetAttr(self, row, col, someExtraParameter)  
586            return self.__colAttr.get(col, wxGridCellAttr()).Clone()
         if col == COL_VISUAL:  
             attr.SetRenderer(ClassRenderer(self.shapeType))  
             attr.SetReadOnly()  
587    
588          return attr      def GetClassGroup(self, row):
589            """Return the ClassGroup object representing row 'row'."""
590    
591      def GetClassData(self, row):          #return self.GetValueAsCustom(row, COL_SYMBOL, None)
592          return self.tdata[row][COL_VISUAL]          if row == 0:
593                return self.clazz.GetDefaultGroup()
594            else:
595                return self.clazz.GetGroup(row - 1)
596    
597      def __Modified(self):      def SetClassGroup(self, row, group):
598          self.modified = 1          self.__SetRow(row, group)
599            self.GetView().Refresh()
600    
601        def __Modified(self, mod = True):
602            """Adjust the modified flag.
603    
604            mod -- if -1 set the modified flag to False, otherwise perform
605                   an 'or' operation with the current value of the flag and
606                   'mod'
607            """
608    
609            if mod == -1:
610                self.modified = False
611            else:
612                self.modified = mod or self.modified
613    
614      def IsModified(self):      def IsModified(self):
615            """True if this table is considered modified."""
616          return self.modified          return self.modified
617    
618      def AddNewDataRow(self):      def DeleteRows(self, pos, numRows = 1):
619          np = ClassDataPoint()          """Deletes 'numRows' beginning at row 'pos'.
620          self.tdata.append([np, np.GetValue(), np.GetLabel()])  
621          msg = wxGridTableMessage(self, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, 1)          The row representing the default group is not removed.
622          self.GetView().ProcessTableMessage(msg)  
623          self.GetView().BeginBatch()          The table is considered modified if any rows are removed.
624          self.GetView().EndBatch()          """
625    
626  class Classifier(wxDialog):          assert pos >= 0
627            old_len = self.GetNumberRows()
628            for row in range(pos, pos - numRows, -1):
629                group = self.GetClassGroup(row)
630                if row != 0:
631                    self.clazz.RemoveGroup(row - 1)
632                    self.__Modified()
633            
634      def __init__(self, parent, layer):          if self.IsModified():
635          wxDialog.__init__(self, parent, -1, _("Classify"),              self.__NotifyRowChanges(old_len, self.GetNumberRows())
636                            style = wxRESIZE_BORDER)  
637        def AppendRows(self, numRows = 1):
638            """Append 'numRows' empty rows to the end of the table.
639    
640            The table is considered modified if any rows are appended.
641            """
642    
643            old_len = self.GetNumberRows()
644            for i in range(numRows):
645                np = ClassGroupSingleton()
646                self.__SetRow(None, np)
647    
648            if self.IsModified():
649                self.__NotifyRowChanges(old_len, self.GetNumberRows())
650    
651    
652    ID_CLASSIFY_OK = 4001
653    ID_CLASSIFY_REVERT = 4002
654    ID_CLASSIFY_ADD = 4003
655    ID_CLASSIFY_GENCLASS = 4004
656    ID_CLASSIFY_REMOVE = 4005
657    ID_CLASSIFY_MOVEUP = 4006
658    ID_CLASSIFY_MOVEDOWN = 4007
659    ID_CLASSIFY_TRY = 4008
660    ID_CLASSIFY_EDITSYM = 4009
661    ID_CLASSIFY_CLOSE = 4010
662    
663    BTN_ADD = 0
664    BTN_EDIT = 1
665    BTN_GEN = 2
666    BTN_UP = 3
667    BTN_DOWN = 4
668    BTN_RM = 5
669    
670    class Classifier(NonModalDialog):
671    
672        type2string = {None:             _("None"),
673                       FIELDTYPE_STRING: _("Text"),
674                       FIELDTYPE_INT:    _("Integer"),
675                       FIELDTYPE_DOUBLE: _("Decimal")}
676    
677        def __init__(self, parent, name, layer, group = None):
678            NonModalDialog.__init__(self, parent, name,
679                                    _("Classifier: %s") % layer.Title())
680    
681            panel = wxPanel(self, -1, size=(100, 100))
682    
683          self.layer = layer          self.layer = layer
684    
685            self.originalClass = self.layer.GetClassification()
686            field = self.originalClass.GetField()
687            fieldType = self.originalClass.GetFieldType()
688    
689            self.genDlg = None
690    
691          topBox = wxBoxSizer(wxVERTICAL)          topBox = wxBoxSizer(wxVERTICAL)
692            panelBox = wxBoxSizer(wxVERTICAL)
693    
694          topBox.Add(wxStaticText(self, -1, _("Layer: %s") % layer.Title()),          #panelBox.Add(wxStaticText(panel, -1, _("Layer: %s") % layer.Title()),
695              0, wxALIGN_LEFT | wxBOTTOM, 4)              #0, wxALIGN_LEFT | wxALL, 4)
696          topBox.Add(wxStaticText(self, -1, _("Type: %s") % layer.ShapeType()),          panelBox.Add(wxStaticText(panel, -1,
697              0, wxALIGN_LEFT | wxBOTTOM, 4)                                  _("Layer Type: %s") % layer.ShapeType()),
698                0, wxALIGN_LEFT | wxALL, 4)
699    
         propertyBox = wxBoxSizer(wxHORIZONTAL)  
         propertyBox.Add(wxStaticText(self, -1, _("Property: ")),  
             0, wxALIGN_CENTER | wxALL, 4)  
700    
701          self.properties = wxComboBox(self, ID_PROPERTY_SELECT, "",          #
702            # make field combo box
703            #
704            self.fields = wxComboBox(panel, ID_PROPERTY_SELECT, "",
705                                       style = wxCB_READONLY)                                       style = wxCB_READONLY)
706    
707          self.num_cols = layer.table.field_count()          self.num_cols = layer.table.field_count()
708          self.__cur_prop = -1          # just assume the first field in case one hasn't been
709          field = layer.GetClassification().GetField()          # specified in the file.
710            self.__cur_field = 0
711    
712            self.fields.Append("<None>")
713            self.fields.SetClientData(0, None)
714    
715          for i in range(self.num_cols):          for i in range(self.num_cols):
716              type, name, len, decc = layer.table.field_info(i)              type, name, len, decc = layer.table.field_info(i)
717                self.fields.Append(name)
718    
719              if name == field:              if name == field:
720                  self.__cur_prop = i                  self.__cur_field = i + 1
721              self.properties.Append(name)                  self.fields.SetClientData(i + 1,
722              self.properties.SetClientData(i, None)                                            copy.deepcopy(self.originalClass))
723                else:
724          self.properties.SetSelection(self.__cur_prop)                  self.fields.SetClientData(i + 1, None)
725          propertyBox.Add(self.properties, 1, wxGROW|wxALL, 0)  
726          EVT_COMBOBOX(self, ID_PROPERTY_SELECT, self.OnPropertySelect)  
727            ###########
728    
729            self.fieldTypeText = wxStaticText(panel, -1, "")
730            panelBox.Add(self.fieldTypeText, 0,
731                         wxGROW | wxALIGN_LEFT | wxALL | wxADJUST_MINSIZE, 4)
732    
733            propertyBox = wxBoxSizer(wxHORIZONTAL)
734            propertyBox.Add(wxStaticText(panel, -1, _("Field: ")),
735                0, wxALIGN_LEFT | wxALL, 4)
736            propertyBox.Add(self.fields, 1, wxGROW|wxALL, 4)
737            EVT_COMBOBOX(self, ID_PROPERTY_SELECT, self._OnFieldSelect)
738    
739            panelBox.Add(propertyBox, 0, wxGROW, 4)
740    
         topBox.Add(propertyBox, 0, wxGROW, 4)  
741    
742          #          #
743          # Classification data table          # Control Box
744          #          #
   
745          controlBox = wxBoxSizer(wxHORIZONTAL)          controlBox = wxBoxSizer(wxHORIZONTAL)
         self.classGrid = ClassGrid(self, layer)  
746    
747          controlBox.Add(self.classGrid, 1, wxGROW, 0)  
748            ###########
749            #
750            # Control buttons:
751            #
752            self.controlButtons = []
753    
754          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)  
755    
756          controlBox.Add(controlButtonBox, 0, wxGROW, 10)          button = wxButton(panel, ID_CLASSIFY_GENCLASS, _("Generate Class"))
757          topBox.Add(controlBox, 1, wxGROW, 10)          controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
758            self.controlButtons.append(button)
759    
760            button = wxButton(panel, ID_CLASSIFY_ADD, _("Add"))
761            controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
762            self.controlButtons.append(button)
763    
764            button = wxButton(panel, ID_CLASSIFY_MOVEUP, _("Move Up"))
765            controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
766            self.controlButtons.append(button)
767    
768            button = wxButton(panel, ID_CLASSIFY_MOVEDOWN, _("Move Down"))
769            controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
770            self.controlButtons.append(button)
771    
772          EVT_BUTTON(self, ID_CLASSIFY_ADD, self.OnAdd)          button = wxButton(panel, ID_CLASSIFY_EDITSYM, _("Edit Symbol"))
773          EVT_BUTTON(self, ID_CLASSIFY_GENRANGE, self.OnGenRange)          controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
774          EVT_GRID_CELL_LEFT_DCLICK(self.classGrid, self.OnCellDClick)          self.controlButtons.append(button)
775    
776            controlButtonBox.Add(60, 20, 0, wxGROW | wxALL | wxALIGN_BOTTOM, 4)
777    
778            button = wxButton(panel, ID_CLASSIFY_REMOVE, _("Remove"))
779            controlButtonBox.Add(button, 0, wxGROW | wxALL | wxALIGN_BOTTOM, 4)
780            self.controlButtons.append(button)
781    
782    
783            ###########
784          #          #
785          # Control buttons:          # Classification data table
786          #          #
787    
788            self.classGrid = ClassGrid(panel, self)
789    #       self.classGrid = wxGrid(panel, -1)
790    #       self.classGrid.SetDefaultRenderer(wxGridCellBoolRenderer())
791    #       self.classGrid.SetDefaultEditor(wxGridCellBoolEditor())
792            #self.classGrid.CreateGrid(5, 5)
793            #self.classGrid.SetCellEditor(0, 0, wxGridCellBoolEditor())
794            #self.classGrid.SetCellRenderer(0, 0, wxGridCellBoolRenderer())
795            #print self.classGrid.GetCellEditor(0, 0)
796            #print self.classGrid.GetCellRenderer(0, 0)
797            #self.classGrid = ClassGrid(panel, self)
798    
799            # need these
800            self.__SetGridTable(self.__cur_field, group)
801            self.fields.SetSelection(self.__cur_field)
802            #self.classGrid.SetCellEditor(0, 0, wxGridCellBoolEditor())
803            #self.classGrid.SetCellRenderer(0, 0, wxGridCellBoolRenderer())
804    
805            # calling __SelectField after creating the classGrid fills in the
806            # grid with the correct information
807            #self.fields.SetSelection(self.__cur_field)
808            #self.__SelectField(self.__cur_field, group = group)
809    
810            #self.classGrid.SelectGroup(group)
811    
812            controlBox.Add(self.classGrid, 1, wxGROW, 0)
813    
814    
815    
816            controlBox.Add(controlButtonBox, 0, wxGROW, 10)
817            panelBox.Add(controlBox, 1, wxGROW, 10)
818    
819            EVT_BUTTON(self, ID_CLASSIFY_ADD, self._OnAdd)
820            EVT_BUTTON(self, ID_CLASSIFY_EDITSYM, self._OnEditSymbol)
821            EVT_BUTTON(self, ID_CLASSIFY_REMOVE, self._OnRemove)
822            EVT_BUTTON(self, ID_CLASSIFY_GENCLASS, self._OnGenClass)
823            EVT_BUTTON(self, ID_CLASSIFY_MOVEUP, self._OnMoveUp)
824            EVT_BUTTON(self, ID_CLASSIFY_MOVEDOWN, self._OnMoveDown)
825    
826            ###########
827    
828          buttonBox = wxBoxSizer(wxHORIZONTAL)          buttonBox = wxBoxSizer(wxHORIZONTAL)
829          buttonBox.Add(wxButton(self, ID_CLASSIFY_OK, _("OK")),          buttonBox.Add(wxButton(panel, ID_CLASSIFY_TRY, _("Try")),
830                        0, wxALL, 4)                        0, wxALL, 4)
831          buttonBox.Add(wxButton(self, ID_CLASSIFY_CANCEL, _("Cancel")),          buttonBox.Add(60, 20, 0, wxALL, 4)
832            buttonBox.Add(wxButton(panel, ID_CLASSIFY_REVERT, _("Revert")),
833                        0, wxALL, 4)                        0, wxALL, 4)
834          topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)          buttonBox.Add(60, 20, 0, wxALL, 4)
835            buttonBox.Add(wxButton(panel, ID_CLASSIFY_OK, _("OK")),
836                          0, wxALL, 4)
837            buttonBox.Add(60, 20, 0, wxALL, 4)
838            buttonBox.Add(wxButton(panel, ID_CLASSIFY_CLOSE, _("Close")),
839                          0, wxALL, 4)
840            panelBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 0)
841    
842            EVT_BUTTON(self, ID_CLASSIFY_OK, self._OnOK)
843            EVT_BUTTON(self, ID_CLASSIFY_TRY, self._OnTry)
844            EVT_BUTTON(self, ID_CLASSIFY_CLOSE, self._OnCloseBtn)
845            EVT_BUTTON(self, ID_CLASSIFY_REVERT, self._OnRevert)
846    
847            ###########
848    
         EVT_BUTTON(self, ID_CLASSIFY_OK, self.OnOK)  
         EVT_BUTTON(self, ID_CLASSIFY_CANCEL, self.OnCancel)  
849    
850          self.SetAutoLayout(true)          panel.SetAutoLayout(True)
851            panel.SetSizer(panelBox)
852            panelBox.SetSizeHints(panel)
853    
854            topBox.Add(panel, 1, wxGROW, 0)
855            panelBox.SetSizeHints(self)
856            self.SetAutoLayout(True)
857          self.SetSizer(topBox)          self.SetSizer(topBox)
         topBox.Fit(self)  
         topBox.SetSizeHints(self)  
858    
859      def __BuildClassification(self, prop):          #self.Fit()
860            ######################
861    
862          clazz = Classification()          self.haveApplied = False
         clazz.SetField(self.properties.GetStringSelection())  
863    
864          numRows = self.classGrid.GetNumberRows()      def EditSymbol(self, row):
865            table = self.classGrid.GetTable()
866            prop = table.GetValueAsCustom(row, COL_SYMBOL, None)
867    
868            # get a new ClassGroupProperties object and copy the
869            # values over to our current object
870            propDlg = SelectPropertiesDialog(NULL, prop, self.layer.ShapeType())
871    
872            self.Enable(False)
873            if propDlg.ShowModal() == wxID_OK:
874                new_prop = propDlg.GetClassGroupProperties()
875                table.SetValueAsCustom(row, COL_SYMBOL, None, new_prop)
876            self.Enable(True)
877            propDlg.Destroy()
878            
879        def _SetClassification(self, clazz):
880            
881            self.fields.SetClientData(self.__cur_field, clazz)
882            self.classGrid.GetTable().SetClassification(clazz)
883    
884        def __BuildClassification(self, fieldIndex, copyClass = False):
885    
886    #       numRows = self.classGrid.GetNumberRows()
887    #       assert numRows > 0  # there should always be a default row
888    
889    #       clazz = Classification()
890            if fieldIndex == 0:
891                fieldName = None
892                fieldType = None
893            else:
894                fieldName = self.fields.GetString(fieldIndex)
895                fieldType = self.layer.GetFieldType(fieldName)
896    
897            clazz = self.classGrid.GetTable().GetClassification()
898    
899            if copyClass:
900                clazz = copy.deepcopy(clazz)
901    
902            clazz.SetField(fieldName)
903            clazz.SetFieldType(fieldType)
904    
         if numRows > 0:  
             table = self.classGrid.GetTable()  
             clazz.SetDefaultData(table.GetClassData(0))  
905    
906              for i in range(1, numRows):  #       table = self.classGrid.GetTable()
907                  clazz.AddClassData(table.GetClassData(i))  #       clazz.SetDefaultGroup(table.GetClassGroup(0))
908    
909    #       for i in range(1, numRows):
910    #           clazz.AppendGroup(table.GetClassGroup(i))
911    
912          return clazz          return clazz
913    
914      def OnPropertySelect(self, event):      def __SetGridTable(self, fieldIndex, group = None):
         self.properties.SetClientData(  
             self.__cur_prop, self.__BuildClassification(self.__cur_prop))  
915    
916          self.__cur_prop = self.properties.GetSelection()          clazz = self.fields.GetClientData(fieldIndex)
         clazz = self.properties.GetClientData(self.__cur_prop)  
         table = self.classGrid.GetTable()  
917    
918          table.Reset(clazz, self.layer.ShapeType())          if clazz is None:
919                clazz = Classification()
920                clazz.SetDefaultGroup(
921                    ClassGroupDefault(
922                        self.layer.GetClassification().
923                                   GetDefaultGroup().GetProperties()))
924    
925                fieldName = self.fields.GetString(fieldIndex)
926                fieldType = self.layer.GetFieldType(fieldName)
927                clazz.SetFieldType(fieldType)
928                    
929            self.classGrid.CreateTable(clazz, self.layer.ShapeType(), group)
930    
931        def __SetFieldTypeText(self, fieldIndex):
932            fieldName = self.fields.GetString(fieldIndex)
933            fieldType = self.layer.GetFieldType(fieldName)
934    
935            assert Classifier.type2string.has_key(fieldType)
936    
937            text = Classifier.type2string[fieldType]
938    
939            self.fieldTypeText.SetLabel(_("Field Type: %s") % text)
940    
941        def __SelectField(self, newIndex, oldIndex = -1, group = None):
942            """This method assumes that the current selection for the
943            combo has already been set by a call to SetSelection().
944            """
945    
946            assert oldIndex >= -1
947    
948            if oldIndex != -1:
949                clazz = self.__BuildClassification(oldIndex)
950                self.fields.SetClientData(oldIndex, clazz)
951    
952            self.__SetGridTable(newIndex, group)
953    
954      def OnOK(self, event):          enabled = newIndex != 0
955    
956            for b in self.controlButtons:
957                b.Enable(enabled)
958    
959            self.__SetFieldTypeText(newIndex)
960    
961    
962        def _OnEditSymbol(self, event):
963            sel = self.classGrid.GetCurrentSelection()
964    
965            if len(sel) == 1:
966                self.EditSymbol(sel[0])
967    
968        def _OnFieldSelect(self, event):
969            index = self.fields.GetSelection()
970            self.__SelectField(index, self.__cur_field)
971            self.__cur_field = index
972    
973        def _OnTry(self, event):
974          """Put the data from the table into a new Classification and hand          """Put the data from the table into a new Classification and hand
975             it to the layer.             it to the layer.
976          """          """
977    
978          clazz = self.properties.GetClientData(self.__cur_prop)          clazz = self.fields.GetClientData(self.__cur_field)
979    
980          #          #
981          # only build the classification if there wasn't one to          # only build the classification if there wasn't one to
982          # to begin with or it has been modified          # to begin with or it has been modified
983          #          #
984          if clazz is None or self.classGrid.GetTable().IsModified():          if clazz is None or self.classGrid.GetTable().IsModified():
985              clazz = self.__BuildClassification(self.__cur_prop)              clazz = self.__BuildClassification(self.__cur_field, True)
   
         clazz.SetLayer(self.layer)  
986    
987          self.layer.SetClassification(clazz)          self.layer.SetClassification(clazz)
988    
989          self.EndModal(wxID_OK)          self.haveApplied = True
990    
991      def OnCancel(self, event):      def _OnOK(self, event):
992          """Do nothing. The layer's current classification stays the same."""          self._OnTry(event)
993          self.EndModal(wxID_CANCEL)          self.Close()
994    
995        def _OnCloseBtn(self, event):
996            """Close is similar to Cancel except that any changes that were
997            made and applied remain applied, but the currently displayed
998            classification is discarded.
999            """
1000    
1001            self.Close()
1002    
1003      def OnAdd(self, event):      def _OnRevert(self, event):
1004          self.classGrid.GetTable().AddNewDataRow()          """The layer's current classification stays the same."""
1005          print "Classifier.OnAdd()"          if self.haveApplied:
1006                self.layer.SetClassification(self.originalClass)
1007    
1008            #self.Close()
1009    
1010        def _OnAdd(self, event):
1011            self.classGrid.AppendRows()
1012    
1013        def _OnRemove(self, event):
1014            self.classGrid.DeleteSelectedRows()
1015    
1016        def _OnGenClass(self, event):
1017    
1018            #if self.genDlg is None:
1019            self.genDlg = ClassGenDialog(self, self.layer,
1020                              self.fields.GetString(self.__cur_field))
1021    
1022            EVT_CLOSE(self.genDlg, self._OnGenDialogClose)
1023    
1024            self.fields.Enable(False)
1025            self.controlButtons[BTN_EDIT].Enable(False)
1026            self.controlButtons[BTN_GEN].Enable(False)
1027    
1028            self.genDlg.Show()
1029            #if self.genDlg.ShowModal() == wxID_OK:
1030            #    clazz = self.genDlg.GetClassification()
1031            #    self.fields.SetClientData(self.__cur_field, clazz)
1032            #    self.classGrid.GetTable().SetClassification(clazz)
1033            #self.Enable(True)
1034            #self.genDlg.Destroy()
1035    
1036        def _OnGenDialogClose(self, event):
1037            self.genDlg.Destroy()
1038    
1039            self.fields.Enable(True)
1040            self.controlButtons[BTN_EDIT].Enable(True)
1041            self.controlButtons[BTN_GEN].Enable(True)
1042    
1043        def _OnMoveUp(self, event):
1044            sel = self.classGrid.GetCurrentSelection()
1045    
1046            if len(sel) == 1:
1047                i = sel[0]
1048                if i > 1:
1049                    table = self.classGrid.GetTable()
1050                    x = table.GetClassGroup(i - 1)
1051                    y = table.GetClassGroup(i)
1052                    table.SetClassGroup(i - 1, y)
1053                    table.SetClassGroup(i, x)
1054                    self.classGrid.ClearSelection()
1055                    self.classGrid.SelectRow(i - 1)
1056                    self.classGrid.MakeCellVisible(i - 1, 0)
1057    
1058      def OnGenRange(self, event):      def _OnMoveDown(self, event):
1059          print "Classifier.OnGenRange()"          sel = self.classGrid.GetCurrentSelection()
1060    
1061      def OnCellDClick(self, event):          if len(sel) == 1:
1062          r = event.GetRow()              i = sel[0]
1063          c = event.GetCol()              table = self.classGrid.GetTable()
1064          if c == COL_VISUAL:              if 0 < i < table.GetNumberRows() - 1:
1065              prop = self.classGrid.GetTable().GetValueAsCustom(r, c, None)                  x = table.GetClassGroup(i)
1066              propDlg = SelectPropertiesDialog(NULL, prop, self.layer.ShapeType())                  y = table.GetClassGroup(i + 1)
1067              if propDlg.ShowModal() == wxID_OK:                  table.SetClassGroup(i, y)
1068                  new_prop = propDlg.GetClassData()                  table.SetClassGroup(i + 1, x)
1069                  prop.SetStroke(new_prop.GetStroke())                  self.classGrid.ClearSelection()
1070                  prop.SetStrokeWidth(new_prop.GetStrokeWidth())                  self.classGrid.SelectRow(i + 1)
1071                  prop.SetFill(new_prop.GetFill())                  self.classGrid.MakeCellVisible(i + 1, 0)
                 self.classGrid.BeginBatch()  
                 self.classGrid.EndBatch()  
             propDlg.Destroy()  
1072    
1073    
1074  ID_SELPROP_OK = 4001  ID_SELPROP_OK = 4001
1075  ID_SELPROP_CANCEL = 4002  ID_SELPROP_CANCEL = 4002
1076  ID_SELPROP_SPINCTRL = 4002  ID_SELPROP_SPINCTRL = 4002
1077    ID_SELPROP_PREVIEW = 4003
1078    ID_SELPROP_STROKECLR = 4004
1079    ID_SELPROP_FILLCLR = 4005
1080    ID_SELPROP_STROKECLRTRANS = 4006
1081    ID_SELPROP_FILLCLRTRANS = 4007
1082    
1083  class SelectPropertiesDialog(wxDialog):  class SelectPropertiesDialog(wxDialog):
1084    
1085      def __init__(self, parent, prop, shapeType):      def __init__(self, parent, prop, shapeType):
1086          wxDialog.__init__(self, parent, -1, _("Select Properties"),          wxDialog.__init__(self, parent, -1, _("Select Properties"),
1087                            style = wxRESIZE_BORDER)                            style = wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
1088    
1089            self.prop = ClassGroupProperties(prop)
1090    
1091          topBox = wxBoxSizer(wxVERTICAL)          topBox = wxBoxSizer(wxVERTICAL)
   
         self.prop = ClassData(classData = prop)  
1092    
1093          topBox.Add(wxStaticText(self, -1, _("Stroke Color: ")),          itemBox = wxBoxSizer(wxHORIZONTAL)
1094              0, wxALIGN_LEFT | wxALL, 4)  
1095            # preview box
1096            previewBox = wxBoxSizer(wxVERTICAL)
1097            previewBox.Add(wxStaticText(self, -1, _("Preview:")),
1098                0, wxALIGN_LEFT | wxALL, 4)
1099    
1100            self.previewWin = ClassGroupPropertiesCtrl(
1101                self, ID_SELPROP_PREVIEW, self.prop, shapeType,
1102                (40, 40), wxSIMPLE_BORDER)
1103    
1104            self.previewWin.AllowEdit(False)
1105    
1106            previewBox.Add(self.previewWin, 1, wxGROW | wxALL, 4)
1107    
1108            itemBox.Add(previewBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
1109    
1110            # control box
1111            ctrlBox = wxBoxSizer(wxVERTICAL)
1112    
1113            lineColorBox = wxBoxSizer(wxHORIZONTAL)
1114            lineColorBox.Add(
1115                wxButton(self, ID_SELPROP_STROKECLR, _("Change Line Color")),
1116                1, wxALL | wxGROW, 4)
1117            EVT_BUTTON(self, ID_SELPROP_STROKECLR, self._OnChangeLineColor)
1118    
1119            lineColorBox.Add(
1120                wxButton(self, ID_SELPROP_STROKECLRTRANS, _("Transparent")),
1121                1, wxALL | wxGROW, 4)
1122            EVT_BUTTON(self, ID_SELPROP_STROKECLRTRANS,
1123                       self._OnChangeLineColorTrans)
1124    
1125            ctrlBox.Add(lineColorBox, 0,
1126                        wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
1127    
1128            if shapeType != SHAPETYPE_ARC:
1129                fillColorBox = wxBoxSizer(wxHORIZONTAL)
1130                fillColorBox.Add(
1131                    wxButton(self, ID_SELPROP_FILLCLR, _("Change Fill Color")),
1132                    1, wxALL | wxGROW, 4)
1133                EVT_BUTTON(self, ID_SELPROP_FILLCLR, self._OnChangeFillColor)
1134                fillColorBox.Add(
1135                    wxButton(self, ID_SELPROP_FILLCLRTRANS, _("Transparent")),
1136                    1, wxALL | wxGROW, 4)
1137                EVT_BUTTON(self, ID_SELPROP_FILLCLRTRANS,
1138                           self._OnChangeFillColorTrans)
1139                ctrlBox.Add(fillColorBox, 0,
1140                            wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
1141    
1142          spinBox = wxBoxSizer(wxHORIZONTAL)          spinBox = wxBoxSizer(wxHORIZONTAL)
1143          spinBox.Add(wxStaticText(self, -1, _("Stroke Width: ")),          spinBox.Add(wxStaticText(self, -1, _("Line Width: ")),
1144                  0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxBOTTOM, 4)                  0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
1145          self.spinCtrl = wxSpinCtrl(self, ID_SELPROP_SPINCTRL,          self.spinCtrl = wxSpinCtrl(self, ID_SELPROP_SPINCTRL,
1146                                     min=1, max=10,                                     min=1, max=10,
1147                                     value=str(prop.GetStrokeWidth()),                                     value=str(prop.GetLineWidth()),
1148                                     initial=prop.GetStrokeWidth())                                     initial=prop.GetLineWidth())
1149    
1150          EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL, self.OnSpin)          EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL, self._OnSpin)
1151    
1152          spinBox.Add(self.spinCtrl, 0, wxALIGN_LEFT | wxALL, 4)          spinBox.Add(self.spinCtrl, 0, wxALIGN_LEFT | wxALL, 4)
1153    
1154          topBox.Add(spinBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)          ctrlBox.Add(spinBox, 0, wxALIGN_RIGHT | wxALL, 0)
1155            itemBox.Add(ctrlBox, 0, wxALIGN_RIGHT | wxALL | wxGROW, 0)
1156          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)  
1157    
1158          #          #
1159          # Control buttons:          # Control buttons:
1160          #          #
1161          buttonBox = wxBoxSizer(wxHORIZONTAL)          buttonBox = wxBoxSizer(wxHORIZONTAL)
1162          buttonBox.Add(wxButton(self, ID_CLASSIFY_OK, _("OK")),          buttonBox.Add(wxButton(self, ID_SELPROP_OK, _("OK")),
1163                        0, wxALL, 4)                        0, wxALL, 4)
1164          buttonBox.Add(wxButton(self, ID_CLASSIFY_CANCEL, _("Cancel")),          buttonBox.Add(wxButton(self, ID_SELPROP_CANCEL, _("Cancel")),
1165                        0, wxALL, 4)                        0, wxALL, 4)
1166          topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)          topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)
1167                                                                                                                                                                    
1168          EVT_BUTTON(self, ID_SELPROP_OK, self.OnOK)          EVT_BUTTON(self, ID_SELPROP_OK, self._OnOK)
1169          EVT_BUTTON(self, ID_SELPROP_CANCEL, self.OnCancel)          EVT_BUTTON(self, ID_SELPROP_CANCEL, self._OnCancel)
1170                                                                                                                                                                    
1171          self.SetAutoLayout(true)          self.SetAutoLayout(True)
1172          self.SetSizer(topBox)          self.SetSizer(topBox)
1173          topBox.Fit(self)          topBox.Fit(self)
1174          topBox.SetSizeHints(self)          topBox.SetSizeHints(self)
1175    
1176      def OnOK(self, event):      def _OnOK(self, event):
1177          self.EndModal(wxID_OK)          self.EndModal(wxID_OK)
1178    
1179      def OnCancel(self, event):      def _OnCancel(self, event):
1180          self.EndModal(wxID_CANCEL)          self.EndModal(wxID_CANCEL)
1181    
1182      def OnSpin(self, event):      def _OnSpin(self, event):
1183          self.prop.SetStrokeWidth(self.spinCtrl.GetValue())          self.prop.SetLineWidth(self.spinCtrl.GetValue())
1184            self.previewWin.Refresh()
1185    
1186        def __GetColor(self, cur):
1187            dialog = wxColourDialog(self)
1188            if cur is not Color.Transparent:
1189                dialog.GetColourData().SetColour(Color2wxColour(cur))
1190    
1191            ret = None
1192            if dialog.ShowModal() == wxID_OK:
1193                ret = wxColour2Color(dialog.GetColourData().GetColour())
1194    
1195            dialog.Destroy()
1196    
1197            return ret
1198            
1199        def _OnChangeLineColor(self, event):
1200            clr = self.__GetColor(self.prop.GetLineColor())
1201            if clr is not None:
1202                self.prop.SetLineColor(clr)
1203            self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1204    
1205        def _OnChangeLineColorTrans(self, event):
1206            self.prop.SetLineColor(Color.Transparent)
1207            self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1208            
1209        def _OnChangeFillColor(self, event):
1210            clr = self.__GetColor(self.prop.GetFill())
1211            if clr is not None:
1212                self.prop.SetFill(clr)
1213            self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1214    
1215        def _OnChangeFillColorTrans(self, event):
1216            self.prop.SetFill(Color.Transparent)
1217            self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1218    
1219        def GetClassGroupProperties(self):
1220            return self.prop
1221    
1222    
1223    class ClassDataPreviewWindow(wxWindow):
1224    
1225        def __init__(self, rect, prop, shapeType,
1226                           parent = None, id = -1, size = wxDefaultSize):
1227            if parent is not None:
1228                wxWindow.__init__(self, parent, id, (0, 0), size)
1229                EVT_PAINT(self, self._OnPaint)
1230    
1231            self.rect = rect
1232    
1233            self.prop = prop
1234            self.shapeType = shapeType
1235            self.previewer = ClassDataPreviewer()
1236    
1237      def GetClassData(self):      def GetProperties():
1238          return self.prop          return self.prop
1239    
1240        def _OnPaint(self, event):
1241            dc = wxPaintDC(self)
1242    
1243            # XXX: this doesn't seem to be having an effect:
1244            dc.DestroyClippingRegion()
1245    
1246            if self.rect is None:
1247                w, h = self.GetSize()
1248                rect = wxRect(0, 0, w, h)
1249            else:
1250                rect = self.rect
1251    
1252            self.previewer.Draw(dc, rect, self.prop, self.shapeType)
1253    
1254  class ClassDataPreviewer:  class ClassDataPreviewer:
1255    
1256      def Draw(self, dc, rect, data, shapeType):      def Draw(self, dc, rect, prop, shapeType):
1257    
1258            assert dc is not None
1259            assert isinstance(prop, ClassGroupProperties)
1260    
1261          stroke = data.GetStroke()          if rect is None:
1262          if stroke is Color.None:              x = 0
1263                y = 0
1264                w, h = dc.GetSize()
1265            else:
1266                x = rect.GetX()
1267                y = rect.GetY()
1268                w = rect.GetWidth()
1269                h = rect.GetHeight()
1270    
1271            stroke = prop.GetLineColor()
1272            if stroke is Color.Transparent:
1273              pen = wxTRANSPARENT_PEN              pen = wxTRANSPARENT_PEN
1274          else:          else:
1275              pen = wxPen(wxColour(stroke.red * 255,              pen = wxPen(Color2wxColour(stroke),
1276                                   stroke.green * 255,                          prop.GetLineWidth(),
                                  stroke.blue * 255),  
                         data.GetStrokeWidth(),  
1277                          wxSOLID)                          wxSOLID)
1278    
1279          stroke = data.GetFill()          stroke = prop.GetFill()
1280          if stroke is Color.None:          if stroke is Color.Transparent:
1281              brush = wxTRANSPARENT_BRUSH              brush = wxTRANSPARENT_BRUSH
1282          else:          else:
1283              brush = wxBrush(wxColour(stroke.red * 255,              brush = wxBrush(Color2wxColour(stroke), wxSOLID)
                                      stroke.green * 255,  
                                      stroke.blue * 255), wxSOLID)  
1284    
1285          dc.SetPen(pen)          dc.SetPen(pen)
1286          dc.SetBrush(brush)          dc.SetBrush(brush)
1287    
1288          if shapeType == SHAPETYPE_ARC:          if shapeType == SHAPETYPE_ARC:
1289              dc.DrawSpline([wxPoint(rect.GetX(), rect.GetY() + rect.GetHeight()),              dc.DrawSpline([wxPoint(x, y + h),
1290                             wxPoint(rect.GetX() + rect.GetWidth()/2,                             wxPoint(x + w/2, y + h/4),
1291                                     rect.GetY() + rect.GetHeight()/4),                             wxPoint(x + w/2, y + h/4*3),
1292                             wxPoint(rect.GetX() + rect.GetWidth()/2,                             wxPoint(x + w, y)])
1293                                     rect.GetY() + rect.GetHeight()/4*3),  
1294                             wxPoint(rect.GetX() + rect.GetWidth(), rect.GetY())])          elif shapeType == SHAPETYPE_POINT:
1295    
1296          elif shapeType == SHAPETYPE_POINT or \              dc.DrawCircle(x + w/2, y + h/2,
1297               shapeType == SHAPETYPE_POLYGON:                            (min(w, h) - prop.GetLineWidth())/2)
1298    
1299              dc.DrawCircle(rect.GetX() + rect.GetWidth()/2,          elif shapeType == SHAPETYPE_POLYGON:
1300                            rect.GetY() + rect.GetHeight()/2,              dc.DrawRectangle(x, y, w, h)
                           (min(rect.GetWidth(), rect.GetHeight())  
                            - data.GetStrokeWidth())/2)  
1301    
1302  class ClassRenderer(wxPyGridCellRenderer):  class ClassRenderer(wxPyGridCellRenderer):
1303    
1304      def __init__(self, shapeType):      def __init__(self, shapeType):
1305          wxPyGridCellRenderer.__init__(self)          wxPyGridCellRenderer.__init__(self)
1306          self.shapeType = shapeType          self.shapeType = shapeType
1307            self.previewer = ClassDataPreviewer()
1308    
1309      def Draw(self, grid, attr, dc, rect, row, col, isSelected):      def Draw(self, grid, attr, dc, rect, row, col, isSelected):
1310          data = grid.GetTable().GetValueAsCustom(row, col, "")          data = grid.GetTable().GetClassGroup(row)
   
1311    
1312          dc.SetClippingRegion(rect.GetX(), rect.GetY(),          dc.SetClippingRegion(rect.GetX(), rect.GetY(),
1313                               rect.GetWidth(), rect.GetHeight())                               rect.GetWidth(), rect.GetHeight())
# Line 526  class ClassRenderer(wxPyGridCellRenderer Line 1316  class ClassRenderer(wxPyGridCellRenderer
1316          dc.DrawRectangle(rect.GetX(), rect.GetY(),          dc.DrawRectangle(rect.GetX(), rect.GetY(),
1317                           rect.GetWidth(), rect.GetHeight())                           rect.GetWidth(), rect.GetHeight())
1318    
1319          ClassDataPreviewer().Draw(dc, rect, data, self.shapeType)          if not isinstance(data, ClassGroupMap):
1320                self.previewer.Draw(dc, rect, data.GetProperties(), self.shapeType)
1321    
1322          if isSelected:          if isSelected:
1323              dc.SetPen(wxPen(wxColour(0 * 255, 0 * 255, 0 * 255),              dc.SetPen(wxPen(wxBLACK, 1, wxSOLID))
                       4, wxSOLID))  
1324              dc.SetBrush(wxTRANSPARENT_BRUSH)              dc.SetBrush(wxTRANSPARENT_BRUSH)
1325    
1326              dc.DrawRectangle(rect.GetX(), rect.GetY(),              dc.DrawRectangle(rect.GetX(), rect.GetY(),
1327                               rect.GetWidth(), rect.GetHeight())                               rect.GetWidth(), rect.GetHeight())
1328    
1329          dc.DestroyClippingRegion()          dc.DestroyClippingRegion()
1330    
1331    
1332    class ClassGroupPropertiesCtrl(wxWindow, wxControl):
1333    
1334        def __init__(self, parent, id, props, shapeType,
1335                     size = wxDefaultSize, style = 0):
1336    
1337            wxWindow.__init__(self, parent, id, size = size, style = style)
1338    
1339            self.SetProperties(props)
1340            self.SetShapeType(shapeType)
1341            self.AllowEdit(True)
1342    
1343            EVT_PAINT(self, self._OnPaint)
1344            EVT_LEFT_DCLICK(self, self._OnLeftDClick)
1345    
1346            self.previewer = ClassDataPreviewer()
1347    
1348        def _OnPaint(self, event):
1349            dc = wxPaintDC(self)
1350    
1351            # XXX: this doesn't seem to be having an effect:
1352            dc.DestroyClippingRegion()
1353    
1354            w, h = self.GetClientSize()
1355    
1356            self.previewer.Draw(dc,
1357                                wxRect(0, 0, w, h),
1358                                self.GetProperties(),
1359                                self.GetShapeType())
1360    
1361    
1362        def GetProperties(self):
1363            return self.props
1364    
1365        def SetProperties(self, props):
1366            self.props = props
1367            self.Refresh()
1368    
1369        def GetShapeType(self):
1370            return self.shapeType
1371    
1372        def SetShapeType(self, shapeType):
1373            self.shapeType = shapeType
1374            self.Refresh()
1375    
1376        def AllowEdit(self, allow):
1377            self.allowEdit = allow
1378    
1379        def DoEdit(self):
1380            if not self.allowEdit: return
1381    
1382            propDlg = SelectPropertiesDialog(NULL,
1383                                             self.GetProperties(),
1384                                             self.GetShapeType())
1385    
1386            if propDlg.ShowModal() == wxID_OK:
1387                new_prop = propDlg.GetClassGroupProperties()
1388                self.SetProperties(new_prop)
1389                self.Refresh()
1390    
1391            propDlg.Destroy()
1392    
1393        def _OnLeftDClick(self, event):
1394            self.DoEdit()

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26