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

Legend:
Removed from v.376  
changed lines
  Added in v.473

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26