/[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 400 by jonathan, Tue Feb 11 14:29:09 2003 UTC revision 509 by jonathan, Tue Mar 11 09:45:59 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 *
22    from Thuban.UI.common import *
23    
24    from Thuban.Model.classification import *
25    
26    from Thuban.Model.color import Color
27    
28  from Thuban.Model.classification import Classification  from Thuban.Model.layer import Layer, SHAPETYPE_ARC, SHAPETYPE_POLYGON, SHAPETYPE_POINT
29    
30  from Thuban.Model.layer import SHAPETYPE_ARC, SHAPETYPE_POLYGON, SHAPETYPE_POINT  from dialogs import NonModalDialog
31    
32    # widget id's
33  ID_PROPERTY_SELECT = 4010  ID_PROPERTY_SELECT = 4010
34  ID_CLASS_TABLE = 40011  ID_CLASS_TABLE = 40011
35    
36  ID_CLASSIFY_OK = 4001  ID_CLASSIFY_OK = 4001
37  ID_CLASSIFY_CANCEL = 4002  ID_CLASSIFY_CANCEL = 4002
38    ID_CLASSIFY_ADD = 4003
39    ID_CLASSIFY_GENRANGE = 4004
40    ID_CLASSIFY_REMOVE = 4005
41    ID_CLASSIFY_MOVEUP = 4006
42    ID_CLASSIFY_MOVEDOWN = 4007
43    ID_CLASSIFY_APPLY = 4008
44    
45    # table columns
46    COL_SYMBOL = 0
47    COL_VALUE  = 1
48    COL_LABEL  = 2
49    
50    # indices into the client data lists in Classifier.fields
51    FIELD_CLASS = 0
52    FIELD_TYPE = 1
53    FIELD_NAME = 2
54    
55    #
56    # this is a silly work around to ensure that the table that is
57    # passed into SetTable is the same that is returned by GetTable
58    #
59    import weakref
60    class ClassGrid(wxGrid):
61    
62        def __init__(self, parent):
63            """Constructor.
64    
65            parent -- the parent window
66    
67            clazz -- the working classification that this grid should
68                     use for display.
69            """
70    
71            #wxGrid.__init__(self, parent, ID_CLASS_TABLE, size = (340, 160))
72            wxGrid.__init__(self, parent, ID_CLASS_TABLE)
73            #self.SetTable(ClassTable(fieldData, layer.ShapeType(), self), True)
74    
75            EVT_GRID_CELL_LEFT_DCLICK(self, self._OnCellDClick)
76            EVT_GRID_RANGE_SELECT(self, self._OnSelectedRange)
77            EVT_GRID_SELECT_CELL(self, self._OnSelectedCell)
78    
79            self.currentSelection = []
80    
81        def CreateTable(self, clazz, shapeType):
82    
83            assert(isinstance(clazz, Classification))
84    
85            self.shapeType = shapeType
86            table = self.GetTable()
87            if table is None:
88                w = self.GetDefaultColSize() * 3 + self.GetDefaultRowLabelSize()
89                h = self.GetDefaultRowSize() * 4 + self.GetDefaultColLabelSize()
90                self.SetDimensions(-1, -1, w, h)
91                self.SetSizeHints(w, h, -1, -1)
92                self.SetTable(ClassTable(clazz, self.shapeType, self), True)
93            else:
94                table.Reset(clazz, self.shapeType)
95    
96            self.SetSelectionMode(wxGrid.wxGridSelectRows)
97            self.ClearSelection()
98    
99        def GetCurrentSelection(self):
100            """Return the currently highlighted rows as an increasing list
101               of row numbers."""
102            sel = copy.copy(self.currentSelection)
103            sel.sort()
104            return sel
105    
106        def SetCellRenderer(self, row, col):
107            raise ValueError(_("Must not allow setting of renderer in ClassGrid!"))
108    
109        #
110        # [Set|Get]Table is taken from http://wiki.wxpython.org
111        # they are needed as a work around to ensure that the table
112        # that is passed to SetTable is the one that is returned
113        # by GetTable.
114        #
115        def SetTable(self, object, *attributes):
116            self.tableRef = weakref.ref(object)
117            return wxGrid.SetTable(self, object, *attributes)
118    
119        def GetTable(self):
120            try:
121                return self.tableRef()
122            except:
123                return None
124    
125        def DeleteSelectedRows(self):
126            """Deletes all highlighted rows.
127      
128            If only one row is highlighted then after it is deleted the
129            row that was below the deleted row is highlighted."""
130    
131            sel = self.GetCurrentSelection()
132    
133            # nothing to do
134            if len(sel) == 0: return
135    
136            # if only one thing is selected check if it is the default
137            # data row, because we can't remove that
138            if len(sel) == 1:
139                #group = self.GetTable().GetValueAsCustom(sel[0], COL_SYMBOL, None)
140                group = self.GetTable().GetClassGroup(sel[0])
141                if isinstance(group, ClassGroupDefault):
142                    wxMessageDialog(self,
143                                    "The Default group cannot be removed.",
144                                    style = wxOK | wxICON_EXCLAMATION).ShowModal()
145                    return
146            
147    
148            self.ClearSelection()
149    
150            # we need to remove things from the bottom up so we don't
151            # change the indexes of rows that will be deleted next
152            sel.reverse()
153    
154            #
155            # actually remove the rows
156            #
157            table = self.GetTable()
158            for row in sel:
159                table.DeleteRows(row)
160    
161            #
162            # if there was only one row selected highlight the row
163            # that was directly below it, or move up one if the
164            # deleted row was the last row.
165            #
166            if len(sel) == 1:
167                r = sel[0]
168                if r > self.GetNumberRows() - 1:
169                    r = self.GetNumberRows() - 1
170                self.SelectRow(r)
171            
172    #
173    # XXX: This isn't working, and there is no way to deselect rows wxPython!
174    #
175    #   def DeselectRow(self, row):
176    #       self.ProcessEvent(
177    #           wxGridRangeSelectEvent(-1,
178    #                                  wxEVT_GRID_RANGE_SELECT,
179    #                                  self,
180    #                                  (row, row), (row, row),
181    #                                  sel = False))
182    
183        def _OnCellDClick(self, event):
184            """Handle a double on a cell."""
185    
186            r = event.GetRow()
187            c = event.GetCol()
188            if c == COL_SYMBOL:
189                prop = self.GetTable().GetValueAsCustom(r, c, None)
190                #prop = group.GetProperties()
191    
192                # get a new ClassGroupProperties object and copy the
193                # values over to our current object
194                propDlg = SelectPropertiesDialog(NULL, prop, self.shapeType)
195                if propDlg.ShowModal() == wxID_OK:
196                    new_prop = propDlg.GetClassGroupProperties()
197                    #prop.SetProperties(new_prop)
198                    self.GetTable().SetValueAsCustom(r, c, None, new_prop)
199                propDlg.Destroy()
200    
201        #
202        # _OnSelectedRange() and _OnSelectedCell() were borrowed
203        # from http://wiki.wxpython.org to keep track of which
204        # cells are currently highlighted
205        #
206        def _OnSelectedRange(self, event):
207            """Internal update to the selection tracking list"""
208            if event.Selecting():
209                for index in range( event.GetTopRow(), event.GetBottomRow()+1):
210                    if index not in self.currentSelection:
211                        self.currentSelection.append( index )
212            else:
213                for index in range( event.GetTopRow(), event.GetBottomRow()+1):
214                    while index in self.currentSelection:
215                        self.currentSelection.remove( index )
216            #self.ConfigureForSelection()
217    
218            event.Skip()
219    
220        def _OnSelectedCell( self, event ):
221            """Internal update to the selection tracking list"""
222            self.currentSelection = [ event.GetRow() ]
223            #self.ConfigureForSelection()
224            event.Skip()
225    
226  class ClassTable(wxPyGridTableBase):  class ClassTable(wxPyGridTableBase):
227        """Represents the underlying data structure for the grid."""
228    
229        NUM_COLS = 3
230    
231        __col_labels = [_("Symbol"), _("Value"), _("Label")]
232    
233        def __init__(self, clazz, shapeType, view = None):
234            """Constructor.
235    
236            shapeType -- the type of shape that the layer uses
237    
238            view -- a wxGrid object that uses this class for its table
239            """
240    
     def __init__(self, clinfo):  
241          wxPyGridTableBase.__init__(self)          wxPyGridTableBase.__init__(self)
242    
243            self.SetView(view)
244          self.tdata = []          self.tdata = []
245    
246          self.tdata.append([clinfo.DefaultData, 'DEFAULT'])          self.Reset(clazz, shapeType)
247    
248        def Reset(self, clazz, shapeType):
249            """Reset the table with the given data.
250    
251            This is necessary because wxWindows does not allow a grid's
252            table to change once it has been intially set and so we
253            need a way of modifying the data.
254    
255            clazz -- the working classification that this table should
256                     use for display. This may be different from the
257                     classification in the layer.
258    
259            shapeType -- the type of shape that the layer uses
260            """
261    
262          for value, data in clinfo.points.items():          assert(isinstance(clazz, Classification))
             self.tdata.append([data, value])  
263    
264          for range in clinfo.ranges:          self.GetView().BeginBatch()
265              self.tdata.append([range[2], '%s-%s' % range[0], range[1]])  
266            self.fieldType = clazz.GetFieldType()
267            self.shapeType = shapeType
268    
269            old_len = len(self.tdata)
270    
271            self.tdata = []
272    
273            #
274            # copy the data out of the classification and into our
275            # array
276            #
277            for p in clazz:
278                np = copy.deepcopy(p)
279                self.__SetRow(-1, np)
280    
         self.SetColLabelValue(1, _("Data Values"))  
281    
282            self.__Modified(-1)
283    
284            self.__NotifyRowChanges(old_len, len(self.tdata))
285    
286                
287            self.GetView().EndBatch()
288    
289        def __NotifyRowChanges(self, curRows, newRows):
290            #
291            # silly message processing for updates to the number of
292            # rows and columns
293            #
294            if newRows > curRows:
295                msg = wxGridTableMessage(self,
296                            wxGRIDTABLE_NOTIFY_ROWS_APPENDED,
297                            newRows - curRows)    # how many
298                self.GetView().ProcessTableMessage(msg)
299            elif newRows < curRows:
300                msg = wxGridTableMessage(self,
301                            wxGRIDTABLE_NOTIFY_ROWS_DELETED,
302                            curRows - newRows,    # position
303                            curRows - newRows)    # how many
304                self.GetView().ProcessTableMessage(msg)
305    
306        def __SetRow(self, row, group):
307            """Set a row's data to that of the group.
308    
309            The table is considered modified after this operation.
310    
311            row -- if row is -1 or greater than the current number of rows
312                   then group is appended to the end.
313            """
314    
315            # either append or replace
316            if row == -1 or row >= self.GetNumberRows():
317                self.tdata.append(group)
318            else:
319                self.tdata[row] = group
320    
321            self.__Modified()
322    
323        def GetColLabelValue(self, col):
324            """Return the label for the given column."""
325            return self.__col_labels[col]
326    
327        def GetRowLabelValue(self, row):
328            """Return the label for the given row."""
329    
330            group = self.tdata[row]
331            if isinstance(group, ClassGroupDefault):   return _("Default")
332            if isinstance(group, ClassGroupSingleton): return _("Singleton")
333            if isinstance(group, ClassGroupRange):     return _("Range")
334            if isinstance(group, ClassGroupMap):       return _("Map")
335    
336            assert(False) # shouldn't get here
337            return _("")
338    
339      def GetNumberRows(self):      def GetNumberRows(self):
340            """Return the number of rows."""
341          return len(self.tdata)          return len(self.tdata)
342    
343      def GetNumberCols(self):      def GetNumberCols(self):
344          return 2          """Return the number of columns."""
345            return self.NUM_COLS
346    
347      def IsEmptyCell(self, row, col):      def IsEmptyCell(self, row, col):
348          return false          """Determine if a cell is empty. This is always false."""
349            return False
350    
351      def GetValue(self, row, col):      def GetValue(self, row, col):
352          return self.tdata[row][col]          """Return the object that is used to represent the given
353               cell coordinates. This may not be a string."""
354            return self.GetValueAsCustom(row, col, None)
355    
356      def SetValue(self, row, col, value):      def SetValue(self, row, col, value):
357          self.tdata[row][col] = value          """Assign 'value' to the cell specified by 'row' and 'col'.
358    
359            The table is considered modified after this operation.
360            """
361    
362            self.SetValueAsCustom(row, col, None, value)
363            self.__Modified()
364          
365      def GetValueAsCustom(self, row, col, typeName):      def GetValueAsCustom(self, row, col, typeName):
366          return self.tdata[row][col]          """Return the object that is used to represent the given
367               cell coordinates. This may not be a string.
368    
369            typeName -- unused, but needed to overload wxPyGridTableBase
370            """
371    
372            group = self.tdata[row]
373    
374            if col == COL_SYMBOL:
375                return group.GetProperties()
376    
377            if col == COL_LABEL:
378                return group.GetLabel()
379    
380            # col must be COL_VALUE
381            assert(col == COL_VALUE)
382    
383            if isinstance(group, ClassGroupDefault):
384                return _("DEFAULT")
385            elif isinstance(group, ClassGroupSingleton):
386                return group.GetValue()
387            elif isinstance(group, ClassGroupRange):
388                return _("%s - %s") % (group.GetMin(), group.GetMax())
389    
390            assert(False) # shouldn't get here
391            return None
392    
393        def __ParseInput(self, value):
394            """Try to determine what kind of input value is
395               (string, number, or range)
396    
397            Returns a tuple of length one if there is a single
398            value, or of length two if it is a range.
399            """
400    
401            type = self.fieldType
402    
403            if type == FIELDTYPE_STRING:
404                return (value,)
405            elif type == FIELDTYPE_INT or type == FIELDTYPE_DOUBLE:
406    
407                if type == FIELDTYPE_INT:
408                    conv = lambda p: int(float(p))
409                else:
410                    conv = lambda p: p
411    
412                #
413                # first try to take the input as a single number
414                # if there's an exception try to break it into
415                # a range seperated by a '-'. take care to ignore
416                # a leading '-' as that could be for a negative number.
417                # then try to parse the individual parts. if there
418                # is an exception here, let it pass up to the calling
419                # function.
420                #
421                try:
422                    return (conv(Str2Num(value)),)
423                except ValueError:
424                    i = value.find('-')
425                    if i == 0:
426                        i = value.find('-', 1)
427    
428                    return (conv(Str2Num(value[:i])), conv(Str2Num(value[i+1:])))
429    
430            assert(False) # shouldn't get here
431            return (0,)
432                
433    
434        def SetValueAsCustom(self, row, col, typeName, value):
435            """Set the cell specified by 'row' and 'col' to 'value'.
436    
437            If column represents the value column, the input is parsed
438            to determine if a string, number, or range was entered.
439            A new ClassGroup may be created if the type of data changes.
440    
441            The table is considered modified after this operation.
442    
443            typeName -- unused, but needed to overload wxPyGridTableBase
444            """
445    
446            assert(col >= 0 and col < self.GetNumberCols())
447            assert(row >= 0 and row < self.GetNumberRows())
448    
449            group = self.tdata[row]
450    
451            mod = True # assume the data will change
452    
453            if col == COL_SYMBOL:
454                group.SetProperties(value)
455            elif col == COL_LABEL:
456                group.SetLabel(value)
457            elif col == COL_VALUE:
458                if isinstance(group, ClassGroupDefault):
459                    # not allowed to modify the default value
460                    pass
461                elif isinstance(group, ClassGroupMap):
462                    # something special
463                    pass
464                else: # SINGLETON, RANGE
465                    try:
466                        dataInfo = self.__ParseInput(value)
467                    except ValueError:
468                        # bad input, ignore the request
469                        mod = False
470                    else:
471    
472                        changed = False
473                        ngroup = group
474                        props = group.GetProperties()
475    
476                        #
477                        # try to update the values, which may include
478                        # changing the underlying group type if the
479                        # group was a singleton and a range was entered
480                        #
481                        if len(dataInfo) == 1:
482                            if not isinstance(group, ClassGroupSingleton):
483                                ngroup = ClassGroupSingleton(prop = props)
484                                changed = True
485                            ngroup.SetValue(dataInfo[0])
486                        elif len(dataInfo) == 2:
487                            if not isinstance(group, ClassGroupRange):
488                                ngroup = ClassGroupRange(prop = props)
489                                changed = True
490                            ngroup.SetRange(dataInfo[0], dataInfo[1])
491                        else:
492                            assert(False)
493                            pass
494    
495                        if changed:
496                            ngroup.SetLabel(group.GetLabel())
497                            self.SetClassGroup(row, ngroup)
498            else:
499                assert(False) # shouldn't be here
500                pass
501    
502            if mod:
503                self.__Modified()
504                self.GetView().Refresh()
505    
506        def GetAttr(self, row, col, someExtraParameter):
507            """Returns the cell attributes"""
508    
509            attr = wxGridCellAttr()
510            #attr = wxPyGridTableBase.GetAttr(self, row, col, someExtraParameter)
511    
512            if col == COL_SYMBOL:
513                # we need to create a new renderer each time, because
514                # SetRenderer takes control of the parameter
515                attr.SetRenderer(ClassRenderer(self.shapeType))
516                attr.SetReadOnly()
517    
518            return attr
519    
520        def GetClassGroup(self, row):
521            """Return the ClassGroup object representing row 'row'."""
522    
523            return self.tdata[row] # self.GetValueAsCustom(row, COL_SYMBOL, None)
524    
525        def SetClassGroup(self, row, group):
526            self.__SetRow(row, group)
527            self.GetView().Refresh()
528    
529        def __Modified(self, mod = True):
530            """Adjust the modified flag.
531    
532            mod -- if -1 set the modified flag to False, otherwise perform
533                   an 'or' operation with the current value of the flag and
534                   'mod'
535            """
536    
537            if mod == -1:
538                self.modified = False
539            else:
540                self.modified = mod or self.modified
541    
542        def IsModified(self):
543            """True if this table is considered modified."""
544            return self.modified
545    
546        def DeleteRows(self, pos, numRows = 1):
547            """Deletes 'numRows' beginning at row 'pos'.
548    
549            The row representing the default group is not removed.
550    
551            The table is considered modified if any rows are removed.
552            """
553    
554            assert(pos >= 0)
555            old_len = len(self.tdata)
556            for row in range(pos, pos - numRows, -1):
557                group = self.GetClassGroup(row)
558                if not isinstance(group, ClassGroupDefault):
559                    self.tdata.pop(row)
560                    self.__Modified()
561        
562            if self.IsModified():
563                self.__NotifyRowChanges(old_len, len(self.tdata))
564    
565        def AppendRows(self, numRows = 1):
566            """Append 'numRows' empty rows to the end of the table.
567    
568            The table is considered modified if any rows are appended.
569            """
570    
571            old_len = len(self.tdata)
572            for i in range(numRows):
573                np = ClassGroupSingleton()
574                self.__SetRow(-1, np)
575    
576  class Classifier(wxDialog):          if self.IsModified():
577                self.__NotifyRowChanges(old_len, len(self.tdata))
578    
579    
580    class Classifier(NonModalDialog):
581            
582      def __init__(self, parent, layer):      def __init__(self, parent, interactor, name, layer):
583          wxDialog.__init__(self, parent, -1, _("Classify"),          NonModalDialog.__init__(self, parent, interactor, name,
584                            style = wxRESIZE_BORDER)                                  _("Classifier: %s") % layer.Title())
585    
586            panel = wxPanel(self, -1)
587    
588            self.layer = layer
589    
590            self.originalClass = self.layer.GetClassification()
591            field = self.originalClass.GetField()
592            fieldType = self.originalClass.GetFieldType()
593    
594          topBox = wxBoxSizer(wxVERTICAL)          topBox = wxBoxSizer(wxVERTICAL)
595    
596          propertyBox = wxBoxSizer(wxHORIZONTAL)          #topBox.Add(wxStaticText(panel, -1, _("Layer: %s") % layer.Title()),
597          propertyBox.Add(wxStaticText(self, -1, _("Property")),              #0, wxALIGN_LEFT | wxALL, 4)
598              0, wxALIGN_CENTER | wxALL, 4)          topBox.Add(wxStaticText(panel, -1,
599                                    _("Layer Type: %s") % layer.ShapeType()),
600                0, wxALIGN_LEFT | wxALL, 4)
601    
602          self.properties = wxComboBox(self, ID_PROPERTY_SELECT, "",  
603            #
604            # make field combo box
605            #
606            self.fields = wxComboBox(panel, ID_PROPERTY_SELECT, "",
607                                       style = wxCB_READONLY)                                       style = wxCB_READONLY)
608    
609          self.num_cols = layer.table.field_count()          self.num_cols = layer.table.field_count()
610          cur_hilight = 0          # just assume the first field in case one hasn't been
611            # specified in the file.
612            self.__cur_field = 0
613    
614            self.fields.Append("<None>")
615            self.fields.SetClientData(0, None)
616    
617          for i in range(self.num_cols):          for i in range(self.num_cols):
618              type, name, len, decc = layer.table.field_info(i)              type, name, len, decc = layer.table.field_info(i)
619              if name == layer.classification.field:              self.fields.Append(name)
620                  cur_hilight = i  
621              self.properties.Append(name)              if name == field:
622                    self.__cur_field = i + 1
623          self.properties.SetSelection(cur_hilight)                  self.fields.SetClientData(i + 1, self.originalClass)
624          propertyBox.Add(self.properties, 1, wxGROW, 4)              else:
625          EVT_COMBOBOX(self, ID_PROPERTY_SELECT, self.OnPropertySelect)                  self.fields.SetClientData(i + 1, None)
626    
627    
628            ###########
629    
630            self.fieldTypeText = wxStaticText(panel, -1, "")
631            topBox.Add(self.fieldTypeText, 0, wxGROW | wxALIGN_LEFT | wxALL, 4)
632    
633            propertyBox = wxBoxSizer(wxHORIZONTAL)
634            propertyBox.Add(wxStaticText(panel, -1, _("Field: ")),
635                0, wxALIGN_LEFT | wxALL, 4)
636            propertyBox.Add(self.fields, 1, wxGROW|wxALL, 4)
637            EVT_COMBOBOX(self, ID_PROPERTY_SELECT, self._OnFieldSelect)
638    
639          topBox.Add(propertyBox, 0, wxGROW, 4)          topBox.Add(propertyBox, 0, wxGROW, 4)
640    
641            ###########
642          #          #
643          # Classification data table          # Classification data table
644          #          #
645    
646          self.classTable = wxGrid(self, ID_CLASS_TABLE, size=(300, 150))          controlBox = wxBoxSizer(wxHORIZONTAL)
647    
648          table = ClassTable(layer.classification)          self.classGrid = ClassGrid(self)
649          self.classTable.SetTable(table, true)          self.__SetGridTable(self.__cur_field)
         self.classTable.EnableEditing(false)  
         cr = ClassRenderer(layer.ShapeType())  
         for i in range(self.classTable.GetNumberRows()):  
             self.classTable.SetCellRenderer(i, 0, cr)  
650    
651          topBox.Add(self.classTable, 1, wxGROW, 0)          controlBox.Add(self.classGrid, 1, wxGROW, 0)
652    
653            ###########
654          #          #
655          # Control buttons:          # Control buttons:
656          #          #
657            self.controlButtons = []
658    
659            controlButtonBox = wxBoxSizer(wxVERTICAL)
660    
661            button = wxButton(panel, ID_CLASSIFY_ADD, _("Add"))
662            controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
663            self.controlButtons.append(button)
664    
665            #button = wxButton(panel, ID_CLASSIFY_GENRANGE, _("Generate Ranges"))
666            #controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
667            #self.controlButtons.append(button)
668    
669            button = wxButton(panel, ID_CLASSIFY_MOVEUP, _("Move Up"))
670            controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
671            self.controlButtons.append(button)
672    
673            button = wxButton(panel, ID_CLASSIFY_MOVEDOWN, _("Move Down"))
674            controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
675            self.controlButtons.append(button)
676    
677            controlButtonBox.Add(60, 20, 0, wxGROW | wxALL | wxALIGN_BOTTOM, 4)
678    
679            button = wxButton(panel, ID_CLASSIFY_REMOVE, _("Remove"))
680            controlButtonBox.Add(button, 0, wxGROW | wxALL | wxALIGN_BOTTOM, 4)
681            self.controlButtons.append(button)
682    
683            controlBox.Add(controlButtonBox, 0, wxGROW, 10)
684            topBox.Add(controlBox, 0, wxGROW, 10)
685    
686            EVT_BUTTON(self, ID_CLASSIFY_ADD, self._OnAdd)
687            EVT_BUTTON(self, ID_CLASSIFY_REMOVE, self._OnRemove)
688            EVT_BUTTON(self, ID_CLASSIFY_GENRANGE, self._OnGenRange)
689            EVT_BUTTON(self, ID_CLASSIFY_MOVEUP, self._OnMoveUp)
690            EVT_BUTTON(self, ID_CLASSIFY_MOVEDOWN, self._OnMoveDown)
691    
692            ###########
693    
694          buttonBox = wxBoxSizer(wxHORIZONTAL)          buttonBox = wxBoxSizer(wxHORIZONTAL)
695          buttonBox.Add(wxButton(self, ID_CLASSIFY_OK, _("OK")),          buttonBox.Add(wxButton(panel, ID_CLASSIFY_OK, _("OK")),
696                        0, wxALL, 4)                        0, wxALL, 4)
697          buttonBox.Add(wxButton(self, ID_CLASSIFY_CANCEL, _("Cancel")),          buttonBox.Add(60, 20, 0, wxALL, 4)
698            buttonBox.Add(wxButton(panel, ID_CLASSIFY_APPLY, _("Apply")),
699                        0, wxALL, 4)                        0, wxALL, 4)
700          topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)          buttonBox.Add(60, 20, 0, wxALL, 4)
701            buttonBox.Add(wxButton(panel, ID_CLASSIFY_CANCEL, _("Cancel")),
702                          0, wxALL, 4)
703            topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 0)
704    
705            EVT_BUTTON(self, ID_CLASSIFY_OK, self._OnOK)
706            EVT_BUTTON(self, ID_CLASSIFY_APPLY, self._OnApply)
707            EVT_BUTTON(self, ID_CLASSIFY_CANCEL, self._OnCancel)
708    
709            ###########
710    
711            self.fields.SetSelection(self.__cur_field)
712            self.__SelectField(self.__cur_field)
713    
714            panel.SetAutoLayout(True)
715            panel.SetSizer(topBox)
716    
717            topBox.SetSizeHints(panel)
718            topBox.Fit(panel)
719    
720            self.SetAutoLayout(True)
721            self.Fit()
722    
723    
724        def __BuildClassification(self, fieldIndex):
725    
726            numRows = self.classGrid.GetNumberRows()
727            assert(numRows > 0) # there should always be a default row
728    
729            clazz = Classification()
730            if fieldIndex == 0:
731                fieldName = None
732                fieldType = None
733            else:
734                fieldName = self.fields.GetString(fieldIndex)
735                fieldType = self.layer.GetFieldType(fieldName)
736    
737            clazz.SetField(fieldName)
738            clazz.SetFieldType(fieldType)
739    
740    
741            table = self.classGrid.GetTable()
742            clazz.SetDefaultGroup(table.GetClassGroup(0))
743    
744            for i in range(1, numRows):
745                clazz.AddGroup(table.GetClassGroup(i))
746    
747            return clazz
748    
749        def __SetGridTable(self, fieldIndex):
750    
751            clazz = self.fields.GetClientData(fieldIndex)
752    
753            if clazz is None:
754                clazz = Classification()
755                clazz.SetDefaultGroup(
756                    ClassGroupDefault(
757                        self.layer.GetClassification().
758                                   GetDefaultGroup().GetProperties()))
759    
760                fieldName = self.fields.GetString(fieldIndex)
761                fieldType = self.layer.GetFieldType(fieldName)
762                clazz.SetFieldType(fieldType)
763                    
764            self.classGrid.CreateTable(clazz, self.layer.ShapeType())
765    
766    
767    
768        type2string = {None:             _("None"),
769                       FIELDTYPE_STRING: _("Text"),
770                       FIELDTYPE_INT:    _("Integer"),
771                       FIELDTYPE_DOUBLE: _("Decimal")}
772    
773        def __SetFieldTypeText(self, fieldIndex):
774            fieldName = self.fields.GetString(fieldIndex)
775            fieldType = self.layer.GetFieldType(fieldName)
776    
777            assert(Classifier.type2string.has_key(fieldType))
778    
779            text = Classifier.type2string[fieldType]
780    
781            self.fieldTypeText.SetLabel(_("Field Type: %s") % text)
782    
783        def __SelectField(self, newIndex, oldIndex = -1):
784    
785            assert(oldIndex >= -1)
786    
787            if oldIndex != -1:
788                clazz = self.__BuildClassification(oldIndex)
789                self.fields.SetClientData(oldIndex, clazz)
790    
791            self.__SetGridTable(newIndex)
792    
793            enabled = newIndex != 0
794    
795            for b in self.controlButtons:
796                b.Enable(enabled)
797    
798            self.__SetFieldTypeText(newIndex)
799    
800    
801        def _OnFieldSelect(self, event):
802            index = self.fields.GetSelection()
803            self.__SelectField(index, self.__cur_field)
804            self.__cur_field = index
805    
806        def _OnApply(self, event):
807            """Put the data from the table into a new Classification and hand
808               it to the layer.
809            """
810    
811          EVT_BUTTON(self, ID_CLASSIFY_OK, self.OnOK)          clazz = self.fields.GetClientData(self.__cur_field)
         EVT_BUTTON(self, ID_CLASSIFY_CANCEL, self.OnCancel)  
812    
813          self.SetAutoLayout(true)          #
814            # only build the classification if there wasn't one to
815            # to begin with or it has been modified
816            #
817            if clazz is None or self.classGrid.GetTable().IsModified():
818                clazz = self.__BuildClassification(self.__cur_field)
819    
820            self.layer.SetClassification(clazz)
821    
822        def _OnOK(self, event):
823            self._OnApply(event)
824            self.OnClose(event)
825    
826        def _OnCancel(self, event):
827            """The layer's current classification stays the same."""
828            self.layer.SetClassification(self.originalClass)
829            self.OnClose(event)
830    
831        def _OnAdd(self, event):
832            self.classGrid.AppendRows()
833    
834        def _OnRemove(self, event):
835            self.classGrid.DeleteSelectedRows()
836    
837        def _OnGenRange(self, event):
838            print "Classifier._OnGenRange()"
839    
840        def _OnMoveUp(self, event):
841            sel = self.classGrid.GetCurrentSelection()
842    
843            if len(sel) == 1:
844                i = sel[0]
845                if i > 1:
846                    table = self.classGrid.GetTable()
847                    x = table.GetClassGroup(i - 1)
848                    y = table.GetClassGroup(i)
849                    table.SetClassGroup(i - 1, y)
850                    table.SetClassGroup(i, x)
851                    self.classGrid.ClearSelection()
852                    self.classGrid.SelectRow(i - 1)
853    
854        def _OnMoveDown(self, event):
855            sel = self.classGrid.GetCurrentSelection()
856    
857            if len(sel) == 1:
858                i = sel[0]
859                table = self.classGrid.GetTable()
860                if 0 < i < table.GetNumberRows() - 1:
861                    x = table.GetClassGroup(i)
862                    y = table.GetClassGroup(i + 1)
863                    table.SetClassGroup(i, y)
864                    table.SetClassGroup(i + 1, x)
865                    self.classGrid.ClearSelection()
866                    self.classGrid.SelectRow(i + 1)
867    
868    
869    ID_SELPROP_OK = 4001
870    ID_SELPROP_CANCEL = 4002
871    ID_SELPROP_SPINCTRL = 4002
872    ID_SELPROP_PREVIEW = 4003
873    ID_SELPROP_STROKECLR = 4004
874    ID_SELPROP_FILLCLR = 4005
875    ID_SELPROP_STROKECLRTRANS = 4006
876    ID_SELPROP_FILLCLRTRANS = 4007
877    
878    class SelectPropertiesDialog(wxDialog):
879    
880        def __init__(self, parent, prop, shapeType):
881            wxDialog.__init__(self, parent, -1, _("Select Properties"),
882                              style = wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
883    
884            self.prop = ClassGroupProperties(prop)
885    
886            topBox = wxBoxSizer(wxVERTICAL)
887    
888            itemBox = wxBoxSizer(wxHORIZONTAL)
889    
890            # preview box
891            previewBox = wxBoxSizer(wxVERTICAL)
892            previewBox.Add(wxStaticText(self, -1, _("Preview:")),
893                0, wxALIGN_LEFT | wxALL, 4)
894            self.previewer = ClassDataPreviewer(None, self.prop, shapeType,
895                                                self, ID_SELPROP_PREVIEW, (40, 40))
896            previewBox.Add(self.previewer, 1, wxGROW, 15)
897    
898            itemBox.Add(previewBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
899    
900            # control box
901            ctrlBox = wxBoxSizer(wxVERTICAL)
902    
903            lineColorBox = wxBoxSizer(wxHORIZONTAL)
904            lineColorBox.Add(
905                wxButton(self, ID_SELPROP_STROKECLR, _("Change Line Color")),
906                1, wxALL | wxGROW, 4)
907            EVT_BUTTON(self, ID_SELPROP_STROKECLR, self._OnChangeLineColor)
908    
909            lineColorBox.Add(
910                wxButton(self, ID_SELPROP_STROKECLRTRANS, _("Transparent")),
911                1, wxALL | wxGROW, 4)
912            EVT_BUTTON(self, ID_SELPROP_STROKECLRTRANS,
913                       self._OnChangeLineColorTrans)
914    
915            ctrlBox.Add(lineColorBox, 0,
916                        wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
917    
918            if shapeType != SHAPETYPE_ARC:
919                fillColorBox = wxBoxSizer(wxHORIZONTAL)
920                fillColorBox.Add(
921                    wxButton(self, ID_SELPROP_FILLCLR, _("Change Fill Color")),
922                    1, wxALL | wxGROW, 4)
923                EVT_BUTTON(self, ID_SELPROP_FILLCLR, self._OnChangeFillColor)
924                fillColorBox.Add(
925                    wxButton(self, ID_SELPROP_FILLCLRTRANS, _("Transparent")),
926                    1, wxALL | wxGROW, 4)
927                EVT_BUTTON(self, ID_SELPROP_FILLCLRTRANS,
928                           self._OnChangeFillColorTrans)
929                ctrlBox.Add(fillColorBox, 0,
930                            wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
931    
932            spinBox = wxBoxSizer(wxHORIZONTAL)
933            spinBox.Add(wxStaticText(self, -1, _("Line Width: ")),
934                    0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
935            self.spinCtrl = wxSpinCtrl(self, ID_SELPROP_SPINCTRL,
936                                       min=1, max=10,
937                                       value=str(prop.GetLineWidth()),
938                                       initial=prop.GetLineWidth())
939    
940            EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL, self._OnSpin)
941    
942            spinBox.Add(self.spinCtrl, 0, wxALIGN_LEFT | wxALL, 4)
943    
944            ctrlBox.Add(spinBox, 0, wxALIGN_RIGHT | wxALL, 0)
945            itemBox.Add(ctrlBox, 0, wxALIGN_RIGHT | wxALL | wxGROW, 0)
946            topBox.Add(itemBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
947    
948            #
949            # Control buttons:
950            #
951            buttonBox = wxBoxSizer(wxHORIZONTAL)
952            buttonBox.Add(wxButton(self, ID_SELPROP_OK, _("OK")),
953                          0, wxALL, 4)
954            buttonBox.Add(wxButton(self, ID_SELPROP_CANCEL, _("Cancel")),
955                          0, wxALL, 4)
956            topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)
957                                                                                    
958            EVT_BUTTON(self, ID_SELPROP_OK, self._OnOK)
959            EVT_BUTTON(self, ID_SELPROP_CANCEL, self._OnCancel)
960                                                                                    
961            self.SetAutoLayout(True)
962          self.SetSizer(topBox)          self.SetSizer(topBox)
963          topBox.Fit(self)          topBox.Fit(self)
964          topBox.SetSizeHints(self)          topBox.SetSizeHints(self)
965    
966      def OnPropertySelect(self, event): pass      def _OnOK(self, event):
   
     def OnOK(self, event):  
967          self.EndModal(wxID_OK)          self.EndModal(wxID_OK)
968    
969      def OnCancel(self, event):      def _OnCancel(self, event):
970          self.EndModal(wxID_CANCEL)          self.EndModal(wxID_CANCEL)
971    
972        def _OnSpin(self, event):
973            self.prop.SetLineWidth(self.spinCtrl.GetValue())
974            self.previewer.Refresh()
975    
976        def __GetColor(self, cur):
977            dialog = wxColourDialog(self)
978            dialog.GetColourData().SetColour(Color2wxColour(cur))
979            ret = None
980            if dialog.ShowModal() == wxID_OK:
981                ret = wxColour2Color(dialog.GetColourData().GetColour())
982    
983            dialog.Destroy()
984    
985            return ret
986            
987        def _OnChangeLineColor(self, event):
988            clr = self.__GetColor(self.prop.GetLineColor())
989            if clr is not None:
990                self.prop.SetLineColor(clr)
991            self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
992    
993        def _OnChangeLineColorTrans(self, event):
994            self.prop.SetLineColor(Color.None)
995            self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
996            
997        def _OnChangeFillColor(self, event):
998            clr = self.__GetColor(self.prop.GetFill())
999            if clr is not None:
1000                self.prop.SetFill(clr)
1001            self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
1002    
1003        def _OnChangeFillColorTrans(self, event):
1004            self.prop.SetFill(Color.None)
1005            self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
1006    
1007        def GetClassGroupProperties(self):
1008            return self.prop
1009    
1010    
1011    class ClassDataPreviewer(wxWindow):
1012    
1013        def __init__(self, rect, prop, shapeType,
1014                           parent = None, id = -1, size = wxDefaultSize):
1015            if parent is not None:
1016                wxWindow.__init__(self, parent, id, size=size)
1017                EVT_PAINT(self, self._OnPaint)
1018    
1019  class ClassRenderer(wxPyGridCellRenderer):          self.rect = rect
1020            self.prop = prop
     def __init__(self, shapeType):  
         wxPyGridCellRenderer.__init__(self)  
1021          self.shapeType = shapeType          self.shapeType = shapeType
1022    
1023      def Draw(self, grid, attr, dc, rect, row, col, isSelected):      def _OnPaint(self, event):
1024          value = grid.GetTable().GetValueAsCustom(row, col, "")          dc = wxPaintDC(self)
1025          # XXX: check if value is a dictionary  
1026          stroke = value.GetStroke()          # XXX: this doesn't seem to be having an effect:
1027          if stroke is None:          dc.DestroyClippingRegion()
1028    
1029            self.Draw(dc, None)
1030    
1031        def Draw(self, dc, rect, prop = None, shapeType = None):
1032    
1033            if prop is None: prop = self.prop
1034            if shapeType is None: shapeType = self.shapeType
1035    
1036            if rect is None:
1037                x = y = 0
1038                w, h = self.GetClientSizeTuple()
1039            else:
1040                x = rect.GetX()
1041                y = rect.GetY()
1042                w = rect.GetWidth()
1043                h = rect.GetHeight()
1044    
1045            stroke = prop.GetLineColor()
1046            if stroke is Color.None:
1047              pen = wxTRANSPARENT_PEN              pen = wxTRANSPARENT_PEN
1048          else:          else:
1049              pen = wxPen(wxColour(stroke.red * 255,              pen = wxPen(Color2wxColour(stroke),
1050                                   stroke.green * 255,                          prop.GetLineWidth(),
                                  stroke.blue * 255),  
                         value.GetStrokeWidth(),  
1051                          wxSOLID)                          wxSOLID)
1052    
1053          stroke = value.GetFill()          stroke = prop.GetFill()
1054          if stroke is None:          if stroke is Color.None:
1055              brush = wxTRANSPARENT_BRUSH              brush = wxTRANSPARENT_BRUSH
1056          else:          else:
1057              brush = wxBrush(wxColour(stroke.red * 255,              brush = wxBrush(Color2wxColour(stroke), wxSOLID)
1058                                       stroke.green * 255,  
1059                                       stroke.blue * 255), wxSOLID)          dc.SetPen(pen)
1060            dc.SetBrush(brush)
1061    
1062            if shapeType == SHAPETYPE_ARC:
1063                dc.DrawSpline([wxPoint(x, y + h),
1064                               wxPoint(x + w/2, y + h/4),
1065                               wxPoint(x + w/2, y + h/4*3),
1066                               wxPoint(x + w, y)])
1067    
1068            elif shapeType == SHAPETYPE_POINT or \
1069                 shapeType == SHAPETYPE_POLYGON:
1070    
1071                dc.DrawCircle(x + w/2, y + h/2,
1072                              (min(w, h) - prop.GetLineWidth())/2)
1073    
1074    class ClassRenderer(wxPyGridCellRenderer):
1075    
1076        def __init__(self, shapeType):
1077            wxPyGridCellRenderer.__init__(self)
1078            self.previewer = ClassDataPreviewer(None, None, shapeType)
1079    
1080        def Draw(self, grid, attr, dc, rect, row, col, isSelected):
1081            data = grid.GetTable().GetClassGroup(row)
1082    
1083          dc.SetClippingRegion(rect.GetX(), rect.GetY(),          dc.SetClippingRegion(rect.GetX(), rect.GetY(),
1084                               rect.GetWidth(), rect.GetHeight())                               rect.GetWidth(), rect.GetHeight())
# Line 167  class ClassRenderer(wxPyGridCellRenderer Line 1087  class ClassRenderer(wxPyGridCellRenderer
1087          dc.DrawRectangle(rect.GetX(), rect.GetY(),          dc.DrawRectangle(rect.GetX(), rect.GetY(),
1088                           rect.GetWidth(), rect.GetHeight())                           rect.GetWidth(), rect.GetHeight())
1089    
1090          dc.SetPen(pen)          if not isinstance(data, ClassGroupMap):
1091          dc.SetBrush(brush)              self.previewer.Draw(dc, rect, data.GetProperties())
1092    
1093          if self.shapeType == SHAPETYPE_ARC:          if isSelected:
1094              dc.DrawSpline([wxPoint(rect.GetX(), rect.GetY() + rect.GetHeight()),              dc.SetPen(wxPen(wxColour(0 * 255, 0 * 255, 0 * 255),
1095                             wxPoint(rect.GetX() + rect.GetWidth()/2,                        4, wxSOLID))
1096                                     rect.GetY() + rect.GetHeight()/4),              dc.SetBrush(wxTRANSPARENT_BRUSH)
1097                             wxPoint(rect.GetX() + rect.GetWidth()/2,              dc.DrawRectangle(rect.GetX(), rect.GetY(),
1098                                     rect.GetY() + rect.GetHeight()/4*3),                               rect.GetWidth(), rect.GetHeight())
                            wxPoint(rect.GetX() + rect.GetWidth(), rect.GetY())])  
   
         elif self.shapeType == SHAPETYPE_POINT or self.shapeType == SHAPETYPE_POLYGON:  
             dc.DrawCircle(rect.GetX() + rect.GetWidth()/2,  
                           rect.GetY() + rect.GetHeight()/2,  
                           (min(rect.GetWidth(), rect.GetHeight())  
                            - value.GetStrokeWidth())/2)  
1099    
1100          dc.DestroyClippingRegion()          dc.DestroyClippingRegion()
1101    
   

Legend:
Removed from v.400  
changed lines
  Added in v.509

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26