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

Legend:
Removed from v.372  
changed lines
  Added in v.460

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26