/[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 398 by jonathan, Tue Feb 11 14:23:45 2003 UTC revision 560 by jonathan, Wed Mar 26 11:05: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.classification import Classification  from Thuban.Model.color import Color
27    
28  from Thuban.Model.layer import SHAPETYPE_ARC, SHAPETYPE_POLYGON, SHAPETYPE_POINT  from Thuban.Model.layer import Layer, SHAPETYPE_ARC, SHAPETYPE_POLYGON, SHAPETYPE_POINT
29    
30    from 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            assert(isinstance(clazz, Classification))
263    
264          for value, data in clinfo.points.items():          self.GetView().BeginBatch()
             self.tdata.append([data, value])  
265    
266          for range in self.clinfo.ranges:          self.fieldType = clazz.GetFieldType()
267              self.tdata.append([range[2], '%s-%s' % range[0], range[1]])          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                self.GetView().FitInside()
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                self.GetView().FitInside()
307    
308        def __SetRow(self, row, group):
309            """Set a row's data to that of the group.
310    
311            The table is considered modified after this operation.
312    
313            row -- if row is -1 or greater than the current number of rows
314                   then group is appended to the end.
315            """
316    
317            # either append or replace
318            if row == -1 or row >= self.GetNumberRows():
319                self.tdata.append(group)
320            else:
321                self.tdata[row] = group
322    
323            self.__Modified()
324    
325        def GetColLabelValue(self, col):
326            """Return the label for the given column."""
327            return self.__col_labels[col]
328    
329        def GetRowLabelValue(self, row):
330            """Return the label for the given row."""
331    
332            group = self.tdata[row]
333            if isinstance(group, ClassGroupDefault):   return _("Default")
334            if isinstance(group, ClassGroupSingleton): return _("Singleton")
335            if isinstance(group, ClassGroupRange):     return _("Range")
336            if isinstance(group, ClassGroupMap):       return _("Map")
337    
338            assert(False) # shouldn't get here
339            return _("")
340    
341      def GetNumberRows(self):      def GetNumberRows(self):
342            """Return the number of rows."""
343          return len(self.tdata)          return len(self.tdata)
344    
345      def GetNumberCols(self):      def GetNumberCols(self):
346          return 2          """Return the number of columns."""
347            return self.NUM_COLS
348    
349      def IsEmptyCell(self, row, col):      def IsEmptyCell(self, row, col):
350          return false          """Determine if a cell is empty. This is always false."""
351            return False
352    
353      def GetValue(self, row, col):      def GetValue(self, row, col):
354          return self.tdata[row][col]          """Return the object that is used to represent the given
355               cell coordinates. This may not be a string."""
356            return self.GetValueAsCustom(row, col, None)
357    
358      def SetValue(self, row, col, value):      def SetValue(self, row, col, value):
359          self.tdata[row][col] = value          """Assign 'value' to the cell specified by 'row' and 'col'.
360    
361            The table is considered modified after this operation.
362            """
363    
364            self.SetValueAsCustom(row, col, None, value)
365            self.__Modified()
366          
367      def GetValueAsCustom(self, row, col, typeName):      def GetValueAsCustom(self, row, col, typeName):
368          return self.tdata[row][col]          """Return the object that is used to represent the given
369               cell coordinates. This may not be a string.
370    
371            typeName -- unused, but needed to overload wxPyGridTableBase
372            """
373    
374            group = self.tdata[row]
375    
376            if col == COL_SYMBOL:
377                return group.GetProperties()
378    
379            if col == COL_LABEL:
380                return group.GetLabel()
381    
382            # col must be COL_VALUE
383            assert(col == COL_VALUE)
384    
385            if isinstance(group, ClassGroupDefault):
386                return _("DEFAULT")
387            elif isinstance(group, ClassGroupSingleton):
388                return group.GetValue()
389            elif isinstance(group, ClassGroupRange):
390                return _("%s - %s") % (group.GetMin(), group.GetMax())
391    
392            assert(False) # shouldn't get here
393            return None
394    
395        def __ParseInput(self, value):
396            """Try to determine what kind of input value is
397               (string, number, or range)
398    
399            Returns a tuple of length one if there is a single
400            value, or of length two if it is a range.
401            """
402    
403            type = self.fieldType
404    
405            if type == FIELDTYPE_STRING:
406                return (value,)
407            elif type == FIELDTYPE_INT or type == FIELDTYPE_DOUBLE:
408    
409                if type == FIELDTYPE_INT:
410                    conv = lambda p: int(float(p))
411                else:
412                    conv = lambda p: p
413    
414                #
415                # first try to take the input as a single number
416                # if there's an exception try to break it into
417                # a range seperated by a '-'. take care to ignore
418                # a leading '-' as that could be for a negative number.
419                # then try to parse the individual parts. if there
420                # is an exception here, let it pass up to the calling
421                # function.
422                #
423                try:
424                    return (conv(Str2Num(value)),)
425                except ValueError:
426                    i = value.find('-')
427                    if i == 0:
428                        i = value.find('-', 1)
429    
430                    return (conv(Str2Num(value[:i])), conv(Str2Num(value[i+1:])))
431    
432            assert(False) # shouldn't get here
433            return (0,)
434                
435    
436        def SetValueAsCustom(self, row, col, typeName, value):
437            """Set the cell specified by 'row' and 'col' to 'value'.
438    
439            If column represents the value column, the input is parsed
440            to determine if a string, number, or range was entered.
441            A new ClassGroup may be created if the type of data changes.
442    
443            The table is considered modified after this operation.
444    
445            typeName -- unused, but needed to overload wxPyGridTableBase
446            """
447    
448            assert(col >= 0 and col < self.GetNumberCols())
449            assert(row >= 0 and row < self.GetNumberRows())
450    
451            group = self.tdata[row]
452    
453            mod = True # assume the data will change
454    
455            if col == COL_SYMBOL:
456                group.SetProperties(value)
457            elif col == COL_LABEL:
458                group.SetLabel(value)
459            elif col == COL_VALUE:
460                if isinstance(group, ClassGroupDefault):
461                    # not allowed to modify the default value
462                    pass
463                elif isinstance(group, ClassGroupMap):
464                    # something special
465                    pass
466                else: # SINGLETON, RANGE
467                    try:
468                        dataInfo = self.__ParseInput(value)
469                    except ValueError:
470                        # bad input, ignore the request
471                        mod = False
472                    else:
473    
474                        changed = False
475                        ngroup = group
476                        props = group.GetProperties()
477    
478                        #
479                        # try to update the values, which may include
480                        # changing the underlying group type if the
481                        # group was a singleton and a range was entered
482                        #
483                        if len(dataInfo) == 1:
484                            if not isinstance(group, ClassGroupSingleton):
485                                ngroup = ClassGroupSingleton(prop = props)
486                                changed = True
487                            ngroup.SetValue(dataInfo[0])
488                        elif len(dataInfo) == 2:
489                            if not isinstance(group, ClassGroupRange):
490                                ngroup = ClassGroupRange(prop = props)
491                                changed = True
492                            ngroup.SetRange(dataInfo[0], dataInfo[1])
493                        else:
494                            assert(False)
495                            pass
496    
497                        if changed:
498                            ngroup.SetLabel(group.GetLabel())
499                            self.SetClassGroup(row, ngroup)
500            else:
501                assert(False) # shouldn't be here
502                pass
503    
504            if mod:
505                self.__Modified()
506                self.GetView().Refresh()
507    
508  class Classifier(wxDialog):      def GetAttr(self, row, col, someExtraParameter):
509            """Returns the cell attributes"""
510    
511            attr = wxGridCellAttr()
512            #attr = wxPyGridTableBase.GetAttr(self, row, col, someExtraParameter)
513    
514            if col == COL_SYMBOL:
515                # we need to create a new renderer each time, because
516                # SetRenderer takes control of the parameter
517                attr.SetRenderer(ClassRenderer(self.shapeType))
518                attr.SetReadOnly()
519    
520            return attr
521    
522        def GetClassGroup(self, row):
523            """Return the ClassGroup object representing row 'row'."""
524    
525            return self.tdata[row] # self.GetValueAsCustom(row, COL_SYMBOL, None)
526    
527        def SetClassGroup(self, row, group):
528            self.__SetRow(row, group)
529            self.GetView().Refresh()
530    
531        def __Modified(self, mod = True):
532            """Adjust the modified flag.
533    
534            mod -- if -1 set the modified flag to False, otherwise perform
535                   an 'or' operation with the current value of the flag and
536                   'mod'
537            """
538    
539            if mod == -1:
540                self.modified = False
541            else:
542                self.modified = mod or self.modified
543    
544        def IsModified(self):
545            """True if this table is considered modified."""
546            return self.modified
547    
548        def DeleteRows(self, pos, numRows = 1):
549            """Deletes 'numRows' beginning at row 'pos'.
550    
551            The row representing the default group is not removed.
552    
553            The table is considered modified if any rows are removed.
554            """
555    
556            assert(pos >= 0)
557            old_len = len(self.tdata)
558            for row in range(pos, pos - numRows, -1):
559                group = self.GetClassGroup(row)
560                if not isinstance(group, ClassGroupDefault):
561                    self.tdata.pop(row)
562                    self.__Modified()
563            
564      def __init__(self, parent, layer):          if self.IsModified():
565          wxDialog.__init__(self, parent, -1, _("Classify"),              self.__NotifyRowChanges(old_len, len(self.tdata))
566                            style = wxRESIZE_BORDER)  
567        def AppendRows(self, numRows = 1):
568            """Append 'numRows' empty rows to the end of the table.
569    
570            The table is considered modified if any rows are appended.
571            """
572    
573            old_len = len(self.tdata)
574            for i in range(numRows):
575                np = ClassGroupSingleton()
576                self.__SetRow(-1, np)
577    
578            if self.IsModified():
579                self.__NotifyRowChanges(old_len, len(self.tdata))
580    
581    
582    class Classifier(NonModalDialog):
583    
584        def __init__(self, parent, name, layer):
585            NonModalDialog.__init__(self, parent, name,
586                                    _("Classifier: %s") % layer.Title())
587    
588            panel = wxPanel(self, -1, size=(100, 100))
589    
590            self.layer = layer
591    
592            self.originalClass = self.layer.GetClassification()
593            field = self.originalClass.GetField()
594            fieldType = self.originalClass.GetFieldType()
595    
596          topBox = wxBoxSizer(wxVERTICAL)          topBox = wxBoxSizer(wxVERTICAL)
597            panelBox = wxBoxSizer(wxVERTICAL)
598    
599            #panelBox.Add(wxStaticText(panel, -1, _("Layer: %s") % layer.Title()),
600                #0, wxALIGN_LEFT | wxALL, 4)
601            panelBox.Add(wxStaticText(panel, -1,
602                                    _("Layer Type: %s") % layer.ShapeType()),
603                0, wxALIGN_LEFT | wxALL, 4)
604    
         propertyBox = wxBoxSizer(wxHORIZONTAL)  
         propertyBox.Add(wxStaticText(self, -1, _("Property")),  
             0, wxALIGN_CENTER | wxALL, 4)  
605    
606          self.properties = wxComboBox(self, ID_PROPERTY_SELECT, "",          #
607            # make field combo box
608            #
609            self.fields = wxComboBox(panel, ID_PROPERTY_SELECT, "",
610                                       style = wxCB_READONLY)                                       style = wxCB_READONLY)
611    
612          self.num_cols = layer.table.field_count()          self.num_cols = layer.table.field_count()
613          cur_hilight = 0          # just assume the first field in case one hasn't been
614            # specified in the file.
615            self.__cur_field = 0
616    
617            self.fields.Append("<None>")
618            self.fields.SetClientData(0, None)
619    
620          for i in range(self.num_cols):          for i in range(self.num_cols):
621              type, name, len, decc = layer.table.field_info(i)              type, name, len, decc = layer.table.field_info(i)
622              if name == layer.classification.field:              self.fields.Append(name)
623                  cur_hilight = i  
624              self.properties.Append(name)              if name == field:
625                    self.__cur_field = i + 1
626          self.properties.SetSelection(cur_hilight)                  self.fields.SetClientData(i + 1, self.originalClass)
627          propertyBox.Add(self.properties, 1, wxGROW, 4)              else:
628          EVT_COMBOBOX(self, ID_PROPERTY_SELECT, self.OnPropertySelect)                  self.fields.SetClientData(i + 1, None)
629    
630    
631            ###########
632    
633          topBox.Add(propertyBox, 0, wxGROW, 4)          self.fieldTypeText = wxStaticText(panel, -1, "")
634            panelBox.Add(self.fieldTypeText, 0,
635                         wxGROW | wxALIGN_LEFT | wxALL | wxADJUST_MINSIZE, 4)
636    
637            propertyBox = wxBoxSizer(wxHORIZONTAL)
638            propertyBox.Add(wxStaticText(panel, -1, _("Field: ")),
639                0, wxALIGN_LEFT | wxALL, 4)
640            propertyBox.Add(self.fields, 1, wxGROW|wxALL, 4)
641            EVT_COMBOBOX(self, ID_PROPERTY_SELECT, self._OnFieldSelect)
642    
643            panelBox.Add(propertyBox, 0, wxGROW, 4)
644    
645            ###########
646          #          #
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          table = ClassTable(layer.classification)          self.classGrid = ClassGrid(panel)
653          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)  
654    
655          topBox.Add(self.classTable, 1, wxGROW, 0)          controlBox.Add(self.classGrid, 1, wxGROW, 0)
656    
657            ###########
658          #          #
659          # Control buttons:          # Control buttons:
660          #          #
661            self.controlButtons = []
662    
663            controlButtonBox = wxBoxSizer(wxVERTICAL)
664    
665            button = wxButton(panel, ID_CLASSIFY_ADD, _("Add"))
666            controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
667            self.controlButtons.append(button)
668    
669            #button = wxButton(panel, ID_CLASSIFY_GENRANGE, _("Generate Ranges"))
670            #controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
671            #self.controlButtons.append(button)
672    
673            button = wxButton(panel, ID_CLASSIFY_MOVEUP, _("Move Up"))
674            controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
675            self.controlButtons.append(button)
676    
677            button = wxButton(panel, ID_CLASSIFY_MOVEDOWN, _("Move Down"))
678            controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
679            self.controlButtons.append(button)
680    
681            controlButtonBox.Add(60, 20, 0, wxGROW | wxALL | wxALIGN_BOTTOM, 4)
682    
683            button = wxButton(panel, ID_CLASSIFY_REMOVE, _("Remove"))
684            controlButtonBox.Add(button, 0, wxGROW | wxALL | wxALIGN_BOTTOM, 4)
685            self.controlButtons.append(button)
686    
687            controlBox.Add(controlButtonBox, 0, wxGROW, 10)
688            panelBox.Add(controlBox, 1, wxGROW, 10)
689    
690            EVT_BUTTON(self, ID_CLASSIFY_ADD, self._OnAdd)
691            EVT_BUTTON(self, ID_CLASSIFY_REMOVE, self._OnRemove)
692            EVT_BUTTON(self, ID_CLASSIFY_GENRANGE, self._OnGenRange)
693            EVT_BUTTON(self, ID_CLASSIFY_MOVEUP, self._OnMoveUp)
694            EVT_BUTTON(self, ID_CLASSIFY_MOVEDOWN, self._OnMoveDown)
695    
696            ###########
697    
698          buttonBox = wxBoxSizer(wxHORIZONTAL)          buttonBox = wxBoxSizer(wxHORIZONTAL)
699          buttonBox.Add(wxButton(self, ID_CLASSIFY_OK, _("OK")),          buttonBox.Add(wxButton(panel, ID_CLASSIFY_OK, _("OK")),
700                        0, wxALL, 4)                        0, wxALL, 4)
701          buttonBox.Add(wxButton(self, ID_CLASSIFY_CANCEL, _("Cancel")),          buttonBox.Add(60, 20, 0, wxALL, 4)
702            buttonBox.Add(wxButton(panel, ID_CLASSIFY_APPLY, _("Apply")),
703                        0, wxALL, 4)                        0, wxALL, 4)
704          topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)          buttonBox.Add(60, 20, 0, wxALL, 4)
705            buttonBox.Add(wxButton(panel, ID_CLASSIFY_CANCEL, _("Cancel")),
706                          0, wxALL, 4)
707            panelBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 0)
708    
709            EVT_BUTTON(self, ID_CLASSIFY_OK, self._OnOK)
710            EVT_BUTTON(self, ID_CLASSIFY_APPLY, self._OnApply)
711            EVT_BUTTON(self, ID_CLASSIFY_CANCEL, self._OnCancel)
712    
713            ###########
714    
715            self.fields.SetSelection(self.__cur_field)
716            self.__SelectField(self.__cur_field)
717    
718          EVT_BUTTON(self, ID_CLASSIFY_OK, self.OnOK)          panel.SetAutoLayout(True)
719          EVT_BUTTON(self, ID_CLASSIFY_CANCEL, self.OnCancel)          panel.SetSizer(panelBox)
720            panelBox.SetSizeHints(panel)
721    
722          self.SetAutoLayout(true)          topBox.Add(panel, 1, wxGROW, 0)
723            panelBox.SetSizeHints(self)
724            self.SetAutoLayout(True)
725          self.SetSizer(topBox)          self.SetSizer(topBox)
         topBox.Fit(self)  
         topBox.SetSizeHints(self)  
726    
727      def OnPropertySelect(self, event): pass          ######################
728    
729            self.haveApplied = False
730    
731        def __BuildClassification(self, fieldIndex):
732    
733            numRows = self.classGrid.GetNumberRows()
734            assert(numRows > 0) # there should always be a default row
735    
736            clazz = Classification()
737            if fieldIndex == 0:
738                fieldName = None
739                fieldType = None
740            else:
741                fieldName = self.fields.GetString(fieldIndex)
742                fieldType = self.layer.GetFieldType(fieldName)
743    
744            clazz.SetField(fieldName)
745            clazz.SetFieldType(fieldType)
746    
747    
748            table = self.classGrid.GetTable()
749            clazz.SetDefaultGroup(table.GetClassGroup(0))
750    
751            for i in range(1, numRows):
752                clazz.AddGroup(table.GetClassGroup(i))
753    
754            return clazz
755    
756        def __SetGridTable(self, fieldIndex):
757    
758            clazz = self.fields.GetClientData(fieldIndex)
759    
760            if clazz is None:
761                clazz = Classification()
762                clazz.SetDefaultGroup(
763                    ClassGroupDefault(
764                        self.layer.GetClassification().
765                                   GetDefaultGroup().GetProperties()))
766    
767                fieldName = self.fields.GetString(fieldIndex)
768                fieldType = self.layer.GetFieldType(fieldName)
769                clazz.SetFieldType(fieldType)
770                    
771            self.classGrid.CreateTable(clazz, self.layer.ShapeType())
772    
773    
774    
775        type2string = {None:             _("None"),
776                       FIELDTYPE_STRING: _("Text"),
777                       FIELDTYPE_INT:    _("Integer"),
778                       FIELDTYPE_DOUBLE: _("Decimal")}
779    
780        def __SetFieldTypeText(self, fieldIndex):
781            fieldName = self.fields.GetString(fieldIndex)
782            fieldType = self.layer.GetFieldType(fieldName)
783    
784            assert(Classifier.type2string.has_key(fieldType))
785    
786            text = Classifier.type2string[fieldType]
787    
788            self.fieldTypeText.SetLabel(_("Field Type: %s") % text)
789    
790        def __SelectField(self, newIndex, oldIndex = -1):
791    
792            assert(oldIndex >= -1)
793    
794            if oldIndex != -1:
795                clazz = self.__BuildClassification(oldIndex)
796                self.fields.SetClientData(oldIndex, clazz)
797    
798            self.__SetGridTable(newIndex)
799    
800            enabled = newIndex != 0
801    
802            for b in self.controlButtons:
803                b.Enable(enabled)
804    
805            self.__SetFieldTypeText(newIndex)
806    
807    
808      def OnOK(self, event):      def _OnFieldSelect(self, event):
809            index = self.fields.GetSelection()
810            self.__SelectField(index, self.__cur_field)
811            self.__cur_field = index
812    
813        def _OnApply(self, event):
814            """Put the data from the table into a new Classification and hand
815               it to the layer.
816            """
817    
818            clazz = self.fields.GetClientData(self.__cur_field)
819    
820            #
821            # only build the classification if there wasn't one to
822            # to begin with or it has been modified
823            #
824            if clazz is None or self.classGrid.GetTable().IsModified():
825                clazz = self.__BuildClassification(self.__cur_field)
826    
827            self.layer.SetClassification(clazz)
828    
829            self.haveApplied = True
830    
831        def _OnOK(self, event):
832            self._OnApply(event)
833            self.OnClose(event)
834    
835        def _OnCancel(self, event):
836            """The layer's current classification stays the same."""
837            if self.haveApplied:
838                self.layer.SetClassification(self.originalClass)
839    
840            self.OnClose(event)
841    
842        def _OnAdd(self, event):
843            self.classGrid.AppendRows()
844    
845        def _OnRemove(self, event):
846            self.classGrid.DeleteSelectedRows()
847    
848        def _OnGenRange(self, event):
849            print "Classifier._OnGenRange()"
850    
851        def _OnMoveUp(self, event):
852            sel = self.classGrid.GetCurrentSelection()
853    
854            if len(sel) == 1:
855                i = sel[0]
856                if i > 1:
857                    table = self.classGrid.GetTable()
858                    x = table.GetClassGroup(i - 1)
859                    y = table.GetClassGroup(i)
860                    table.SetClassGroup(i - 1, y)
861                    table.SetClassGroup(i, x)
862                    self.classGrid.ClearSelection()
863                    self.classGrid.SelectRow(i - 1)
864    
865        def _OnMoveDown(self, event):
866            sel = self.classGrid.GetCurrentSelection()
867    
868            if len(sel) == 1:
869                i = sel[0]
870                table = self.classGrid.GetTable()
871                if 0 < i < table.GetNumberRows() - 1:
872                    x = table.GetClassGroup(i)
873                    y = table.GetClassGroup(i + 1)
874                    table.SetClassGroup(i, y)
875                    table.SetClassGroup(i + 1, x)
876                    self.classGrid.ClearSelection()
877                    self.classGrid.SelectRow(i + 1)
878    
879    
880    ID_SELPROP_OK = 4001
881    ID_SELPROP_CANCEL = 4002
882    ID_SELPROP_SPINCTRL = 4002
883    ID_SELPROP_PREVIEW = 4003
884    ID_SELPROP_STROKECLR = 4004
885    ID_SELPROP_FILLCLR = 4005
886    ID_SELPROP_STROKECLRTRANS = 4006
887    ID_SELPROP_FILLCLRTRANS = 4007
888    
889    class SelectPropertiesDialog(wxDialog):
890    
891        def __init__(self, parent, prop, shapeType):
892            wxDialog.__init__(self, parent, -1, _("Select Properties"),
893                              style = wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
894    
895            self.prop = ClassGroupProperties(prop)
896    
897            topBox = wxBoxSizer(wxVERTICAL)
898    
899            itemBox = wxBoxSizer(wxHORIZONTAL)
900    
901            # preview box
902            previewBox = wxBoxSizer(wxVERTICAL)
903            previewBox.Add(wxStaticText(self, -1, _("Preview:")),
904                0, wxALIGN_LEFT | wxALL, 4)
905            self.previewWin = ClassDataPreviewWindow(None, self.prop, shapeType,
906                                                self, ID_SELPROP_PREVIEW, (40, 40))
907            previewBox.Add(self.previewWin, 1, wxGROW, 15)
908    
909            itemBox.Add(previewBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
910    
911            # control box
912            ctrlBox = wxBoxSizer(wxVERTICAL)
913    
914            lineColorBox = wxBoxSizer(wxHORIZONTAL)
915            lineColorBox.Add(
916                wxButton(self, ID_SELPROP_STROKECLR, _("Change Line Color")),
917                1, wxALL | wxGROW, 4)
918            EVT_BUTTON(self, ID_SELPROP_STROKECLR, self._OnChangeLineColor)
919    
920            lineColorBox.Add(
921                wxButton(self, ID_SELPROP_STROKECLRTRANS, _("Transparent")),
922                1, wxALL | wxGROW, 4)
923            EVT_BUTTON(self, ID_SELPROP_STROKECLRTRANS,
924                       self._OnChangeLineColorTrans)
925    
926            ctrlBox.Add(lineColorBox, 0,
927                        wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
928    
929            if shapeType != SHAPETYPE_ARC:
930                fillColorBox = wxBoxSizer(wxHORIZONTAL)
931                fillColorBox.Add(
932                    wxButton(self, ID_SELPROP_FILLCLR, _("Change Fill Color")),
933                    1, wxALL | wxGROW, 4)
934                EVT_BUTTON(self, ID_SELPROP_FILLCLR, self._OnChangeFillColor)
935                fillColorBox.Add(
936                    wxButton(self, ID_SELPROP_FILLCLRTRANS, _("Transparent")),
937                    1, wxALL | wxGROW, 4)
938                EVT_BUTTON(self, ID_SELPROP_FILLCLRTRANS,
939                           self._OnChangeFillColorTrans)
940                ctrlBox.Add(fillColorBox, 0,
941                            wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
942    
943            spinBox = wxBoxSizer(wxHORIZONTAL)
944            spinBox.Add(wxStaticText(self, -1, _("Line Width: ")),
945                    0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
946            self.spinCtrl = wxSpinCtrl(self, ID_SELPROP_SPINCTRL,
947                                       min=1, max=10,
948                                       value=str(prop.GetLineWidth()),
949                                       initial=prop.GetLineWidth())
950    
951            EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL, self._OnSpin)
952    
953            spinBox.Add(self.spinCtrl, 0, wxALIGN_LEFT | wxALL, 4)
954    
955            ctrlBox.Add(spinBox, 0, wxALIGN_RIGHT | wxALL, 0)
956            itemBox.Add(ctrlBox, 0, wxALIGN_RIGHT | wxALL | wxGROW, 0)
957            topBox.Add(itemBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
958    
959            #
960            # Control buttons:
961            #
962            buttonBox = wxBoxSizer(wxHORIZONTAL)
963            buttonBox.Add(wxButton(self, ID_SELPROP_OK, _("OK")),
964                          0, wxALL, 4)
965            buttonBox.Add(wxButton(self, ID_SELPROP_CANCEL, _("Cancel")),
966                          0, wxALL, 4)
967            topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)
968                                                                                    
969            EVT_BUTTON(self, ID_SELPROP_OK, self._OnOK)
970            EVT_BUTTON(self, ID_SELPROP_CANCEL, self._OnCancel)
971                                                                                    
972            self.SetAutoLayout(True)
973            self.SetSizer(topBox)
974            topBox.Fit(self)
975            topBox.SetSizeHints(self)
976    
977        def _OnOK(self, event):
978          self.EndModal(wxID_OK)          self.EndModal(wxID_OK)
979    
980      def OnCancel(self, event):      def _OnCancel(self, event):
981          self.EndModal(wxID_CANCEL)          self.EndModal(wxID_CANCEL)
982    
983        def _OnSpin(self, event):
984            self.prop.SetLineWidth(self.spinCtrl.GetValue())
985            self.previewWin.Refresh()
986    
987        def __GetColor(self, cur):
988            dialog = wxColourDialog(self)
989            dialog.GetColourData().SetColour(Color2wxColour(cur))
990            ret = None
991            if dialog.ShowModal() == wxID_OK:
992                ret = wxColour2Color(dialog.GetColourData().GetColour())
993    
994            dialog.Destroy()
995    
996            return ret
997            
998        def _OnChangeLineColor(self, event):
999            clr = self.__GetColor(self.prop.GetLineColor())
1000            if clr is not None:
1001                self.prop.SetLineColor(clr)
1002            self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1003    
1004        def _OnChangeLineColorTrans(self, event):
1005            self.prop.SetLineColor(Color.None)
1006            self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1007            
1008        def _OnChangeFillColor(self, event):
1009            clr = self.__GetColor(self.prop.GetFill())
1010            if clr is not None:
1011                self.prop.SetFill(clr)
1012            self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1013    
1014        def _OnChangeFillColorTrans(self, event):
1015            self.prop.SetFill(Color.None)
1016            self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1017    
1018        def GetClassGroupProperties(self):
1019            return self.prop
1020    
1021    
1022    class ClassDataPreviewWindow(wxWindow):
1023    
1024        def __init__(self, rect, prop, shapeType,
1025                           parent = None, id = -1, size = wxDefaultSize):
1026            if parent is not None:
1027                wxWindow.__init__(self, parent, id, (0, 0), size)
1028                EVT_PAINT(self, self._OnPaint)
1029    
1030  class ClassRenderer(wxPyGridCellRenderer):          self.rect = rect
1031    
1032      def __init__(self, shapeType):          self.prop = prop
         wxPyGridCellRenderer.__init__(self)  
1033          self.shapeType = shapeType          self.shapeType = shapeType
1034            self.previewer = ClassDataPreviewer()
1035    
1036      def Draw(self, grid, attr, dc, rect, row, col, isSelected):      def _OnPaint(self, event):
1037          value = grid.GetTable().GetValueAsCustom(row, col, "")          dc = wxPaintDC(self)
1038          # XXX: check if value is a dictionary  
1039          stroke = value.GetStroke()          # XXX: this doesn't seem to be having an effect:
1040          if stroke is None:          dc.DestroyClippingRegion()
1041    
1042            if self.rect is None:
1043                w, h = self.GetSize()
1044                rect = wxRect(0, 0, w, h)
1045            else:
1046                rect = self.rect
1047    
1048            self.previewer.Draw(dc, rect, self.prop, self.shapeType)
1049    
1050    class ClassDataPreviewer:
1051    
1052        def Draw(self, dc, rect, prop, shapeType):
1053    
1054            assert(dc is not None)
1055            assert(isinstance(prop, ClassGroupProperties))
1056    
1057            if rect is None:
1058                x = 0
1059                y = 0
1060                w, h = dc.GetSize()
1061            else:
1062                x = rect.GetX()
1063                y = rect.GetY()
1064                w = rect.GetWidth()
1065                h = rect.GetHeight()
1066    
1067            stroke = prop.GetLineColor()
1068            if stroke is Color.None:
1069              pen = wxTRANSPARENT_PEN              pen = wxTRANSPARENT_PEN
1070          else:          else:
1071              pen = wxPen(wxColour(stroke.red * 255,              pen = wxPen(Color2wxColour(stroke),
1072                                   stroke.green * 255,                          prop.GetLineWidth(),
                                  stroke.blue * 255),  
                         value.GetStrokeWidth(),  
1073                          wxSOLID)                          wxSOLID)
1074    
1075          stroke = value.GetFill()          stroke = prop.GetFill()
1076          if stroke is None:          if stroke is Color.None:
1077              brush = wxTRANSPARENT_BRUSH              brush = wxTRANSPARENT_BRUSH
1078          else:          else:
1079              brush = wxBrush(wxColour(stroke.red * 255,              brush = wxBrush(Color2wxColour(stroke), wxSOLID)
1080                                       stroke.green * 255,  
1081                                       stroke.blue * 255), wxSOLID)          dc.SetPen(pen)
1082            dc.SetBrush(brush)
1083    
1084            if shapeType == SHAPETYPE_ARC:
1085                dc.DrawSpline([wxPoint(x, y + h),
1086                               wxPoint(x + w/2, y + h/4),
1087                               wxPoint(x + w/2, y + h/4*3),
1088                               wxPoint(x + w, y)])
1089    
1090            elif shapeType == SHAPETYPE_POINT or \
1091                 shapeType == SHAPETYPE_POLYGON:
1092    
1093                dc.DrawCircle(x + w/2, y + h/2,
1094                              (min(w, h) - prop.GetLineWidth())/2)
1095    
1096    class ClassRenderer(wxPyGridCellRenderer):
1097    
1098        def __init__(self, shapeType):
1099            wxPyGridCellRenderer.__init__(self)
1100            self.shapeType = shapeType
1101            self.previewer = ClassDataPreviewer()
1102    
1103        def Draw(self, grid, attr, dc, rect, row, col, isSelected):
1104            data = grid.GetTable().GetClassGroup(row)
1105    
1106          dc.SetClippingRegion(rect.GetX(), rect.GetY(),          dc.SetClippingRegion(rect.GetX(), rect.GetY(),
1107                               rect.GetWidth(), rect.GetHeight())                               rect.GetWidth(), rect.GetHeight())
# Line 167  class ClassRenderer(wxPyGridCellRenderer Line 1110  class ClassRenderer(wxPyGridCellRenderer
1110          dc.DrawRectangle(rect.GetX(), rect.GetY(),          dc.DrawRectangle(rect.GetX(), rect.GetY(),
1111                           rect.GetWidth(), rect.GetHeight())                           rect.GetWidth(), rect.GetHeight())
1112    
1113          dc.SetPen(pen)          if not isinstance(data, ClassGroupMap):
1114          dc.SetBrush(brush)              self.previewer.Draw(dc, rect, data.GetProperties(), self.shapeType)
1115    
1116          if self.shapeType == SHAPETYPE_ARC:          if isSelected:
1117              dc.DrawSpline([wxPoint(rect.GetX(), rect.GetY() + rect.GetHeight()),              dc.SetPen(wxPen(wxColour(0 * 255, 0 * 255, 0 * 255),
1118                             wxPoint(rect.GetX() + rect.GetWidth()/2,                        4, wxSOLID))
1119                                     rect.GetY() + rect.GetHeight()/4),              dc.SetBrush(wxTRANSPARENT_BRUSH)
1120                             wxPoint(rect.GetX() + rect.GetWidth()/2,              dc.DrawRectangle(rect.GetX(), rect.GetY(),
1121                                     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)  
1122    
1123          dc.DestroyClippingRegion()          dc.DestroyClippingRegion()
1124    
   

Legend:
Removed from v.398  
changed lines
  Added in v.560

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26