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

Legend:
Removed from v.392  
changed lines
  Added in v.489

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26