/[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 374 by jan, Mon Jan 27 14:20:02 2003 UTC revision 430 by jonathan, Mon Feb 24 18:47:06 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 _  from Thuban import _
18    from Thuban.common import *
19    
20    from Thuban.Model.classification import * #Classification, ClassData
21    
22    from Thuban.Model.color import Color
23    
24    from Thuban.Model.layer import SHAPETYPE_ARC, SHAPETYPE_POLYGON, SHAPETYPE_POINT
25    
26  ID_PROPERTY_SELECT = 4010  ID_PROPERTY_SELECT = 4010
27  ID_CLASS_TABLE = 40011  ID_CLASS_TABLE = 40011
28    
29  ID_CLASSIFY_OK = 4001  ID_CLASSIFY_OK = 4001
30  ID_CLASSIFY_CANCEL = 4002  ID_CLASSIFY_CANCEL = 4002
31    ID_CLASSIFY_ADD = 4003
32    ID_CLASSIFY_GENRANGE = 4004
33    
34    COL_VISUAL = 0
35    COL_VALUE  = 1
36    COL_LABEL  = 2
37    
38    #
39    # this is a silly work around to ensure that the table that is
40    # passed into SetTable is the same that is returned by GetTable
41    #
42    import weakref
43    class ClassGrid(wxGrid):
44    
45        def __init__(self, parent, layer):
46            wxGrid.__init__(self, parent, ID_CLASS_TABLE, size = (300, 150))
47            self.SetTable(
48                ClassTable(layer.GetClassification(), layer.ShapeType(), self),
49                true)
50    
51        def SetCellRenderer(self, row, col):
52            raise ValueError(_("Must not allow setting of renderer in ClassGrid!"))
53    
54        def SetTable(self, object, *attributes):
55            self.tableRef = weakref.ref(object)
56            return wxGrid.SetTable(self, object, *attributes)
57    
58        def GetTable(self):
59            return self.tableRef()
60    
61    
62    class ClassTable(wxPyGridTableBase):
63    
64        NUM_COLS = 3
65    
66        __col_labels = [_("Visual"), _("Value"), _("Label")]
67    
68        # this is tied to the values of classification.ClassData
69        __row_labels = [_("Default"), _("Point"), _("Range"), _("Map")]
70    
71        def __init__(self, clazz, shapeType, view = None):
72            wxPyGridTableBase.__init__(self)
73            self.SetView(view)
74            self.tdata = []
75    
76            self.Reset(clazz, shapeType)
77    
78        def Reset(self, clazz, shapeType):
79    
80            self.GetView().BeginBatch()
81    
82            self.shapeType = shapeType
83            self.renderer = ClassRenderer(self.shapeType)
84    
85            old_tdata = self.tdata
86    
87            self.tdata = []
88    
89            if clazz is None:
90                clazz = Classification()
91    
92            p = clazz.GetDefaultData()
93            np = ClassDataDefault(classData = p)
94            self.tdata.append([np, 'DEFAULT', np.GetLabel()])
95    
96            for p in clazz.points.values():
97                np = ClassDataPoint(p.GetValue(), classData = p)
98                self.tdata.append([np, np.GetValue(), np.GetLabel()])
99    
100            for p in clazz.ranges:
101                np = ClassDataRange(p.GetMin(), p.GetMax(), classData = p)
102                self.tdata.append([np,
103                                   '%s - %s' % (np.GetMin(), np.GetMax()),
104                                   np.GetLabel()])
105    
106            self.modified = 0
107    
108            #
109            # silly message processing for updates to the number of
110            # rows and columns
111            #
112            curRows = len(old_tdata)
113            newRows = len(self.tdata)
114            if newRows > curRows:
115                msg = wxGridTableMessage(self,
116                            wxGRIDTABLE_NOTIFY_ROWS_APPENDED,
117                            newRows - curRows)    # how many
118                self.GetView().ProcessTableMessage(msg)
119            elif newRows < curRows:
120                msg = wxGridTableMessage(self,
121                            wxGRIDTABLE_NOTIFY_ROWS_DELETED,
122                            curRows - newRows,    # position
123                            curRows - newRows)    # how many
124                self.GetView().ProcessTableMessage(msg)
125    
126            self.GetView().EndBatch()
127    
128        def GetColLabelValue(self, col):
129            return self.__col_labels[col]
130    
131        def GetRowLabelValue(self, row):
132            data = self.tdata[row][COL_VISUAL]
133            type = data.GetType()
134            return self.__row_labels[type]
135    
136        def GetNumberRows(self):
137            return len(self.tdata)
138    
139        def GetNumberCols(self):
140            return self.NUM_COLS
141    
142        def IsEmptyCell(self, row, col):
143            return 0
144    
145        def GetValue(self, row, col):
146            return self.GetValueAsCustom(row, col, "")
147    
148        def SetValue(self, row, col, value):
149            self.SetValueAsCustom(row, col, "", value)
150            self.__Modified()
151          
152        def GetValueAsCustom(self, row, col, typeName):
153            return self.tdata[row][col]
154    
155        def __ParseInput(self, value):
156            """Try to determine what kind of input value is
157               (a single number or a range)
158            """
159    
160            #
161            # first try to take the input as a single number
162            # if there's an exception try to break it into
163            # a range seperated by a '-'. take care to ignore
164            # a leading '-' as that could be for a negative number.
165            # then try to parse the individual parts. if there
166            # is an exception here, let it pass up to the calling
167            # function.
168            #
169            try:
170                return (ClassData.POINT, Str2Num(value))
171            except:
172                i = value.find('-')
173                if i == 0:
174                    i = value.find('-', 1)
175    
176                return (ClassData.RANGE,
177                        Str2Num(value[:i]),
178                        Str2Num(value[i+1:]))
179                
180    
181        def SetValueAsCustom(self, row, col, typeName, value):
182            data = self.tdata[row][COL_VISUAL]
183    
184            if col == COL_VISUAL:
185                self.tdata[row][COL_VISUAL] = value
186            elif col == COL_VALUE:
187                if row != 0: # DefaultData row
188                    type = data.GetType()
189    
190                    if type == ClassData.MAP:
191                        # something special
192                        pass
193                    else: # POINT, RANGE
194                        try:
195                            dataInfo = self.__ParseInput(value)
196                        except: pass
197                            # bad input, ignore the request
198                        else:
199    
200                            if dataInfo[0] == ClassData.POINT:
201                                if type != ClassData.POINT:
202                                    data = ClassDataPoint(classData = data)
203                                data.SetValue(dataInfo[1])
204                                self.tdata[row][COL_VALUE] = data.GetValue()
205                            elif dataInfo[0] == ClassData.RANGE:
206                                if type != ClassData.RANGE:
207                                    data = ClassDataRange(classData = data)
208                                data.SetRange(dataInfo[1], dataInfo[2])
209                                self.tdata[row][COL_VALUE] = \
210                                    "%s - %s" % (data.GetMin(), data.GetMax())
211    
212                            self.tdata[row][COL_VISUAL] = data
213    
214                            self.GetView().Refresh()
215    
216            elif col == COL_LABEL:
217                data.SetLabel(value)
218                self.tdata[row][COL_LABEL] = data.GetLabel()
219            else:
220                raise ValueError(_("Invalid column request"))
221    
222            self.__Modified()
223    
224        def GetAttr(self, row, col, someExtraParameter):
225            attr = wxGridCellAttr()
226            #attr = wxPyGridTableBase.GetAttr(self, row, col, someExtraParameter)
227    
228            if col == COL_VISUAL:
229                attr.SetRenderer(ClassRenderer(self.shapeType))
230                attr.SetReadOnly()
231    
232            return attr
233    
234        def GetClassData(self, row):
235            return self.tdata[row][COL_VISUAL]
236    
237        def __Modified(self):
238            self.modified = 1
239    
240        def IsModified(self):
241            return self.modified
242    
243        def AddNewDataRow(self):
244            np = ClassDataPoint()
245            self.tdata.append([np, np.GetValue(), np.GetLabel()])
246            msg = wxGridTableMessage(self, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, 1)
247            self.GetView().ProcessTableMessage(msg)
248            self.GetView().Refresh()
249    
250  class Classifier(wxDialog):  class Classifier(wxDialog):
251            
# Line 26  class Classifier(wxDialog): Line 253  class Classifier(wxDialog):
253          wxDialog.__init__(self, parent, -1, _("Classify"),          wxDialog.__init__(self, parent, -1, _("Classify"),
254                            style = wxRESIZE_BORDER)                            style = wxRESIZE_BORDER)
255    
256            self.layer = layer
257    
258          topBox = wxBoxSizer(wxVERTICAL)          topBox = wxBoxSizer(wxVERTICAL)
259    
260            topBox.Add(wxStaticText(self, -1, _("Layer: %s") % layer.Title()),
261                0, wxALIGN_LEFT | wxBOTTOM, 4)
262            topBox.Add(wxStaticText(self, -1, _("Type: %s") % layer.ShapeType()),
263                0, wxALIGN_LEFT | wxBOTTOM, 4)
264    
265          propertyBox = wxBoxSizer(wxHORIZONTAL)          propertyBox = wxBoxSizer(wxHORIZONTAL)
266          propertyBox.Add(wxStaticText(self, -1, _("Property")),          propertyBox.Add(wxStaticText(self, -1, _("Property: ")),
267              0, wxALIGN_CENTER | wxALL, 4)              0, wxALIGN_CENTER | wxALL, 4)
268    
269          self.properties = wxComboBox(self, ID_PROPERTY_SELECT, "",          self.properties = wxComboBox(self, ID_PROPERTY_SELECT, "",
270                                       style = wxCB_READONLY)                                       style = wxCB_READONLY)
271    
272          self.num_cols = layer.table.field_count()          self.num_cols = layer.table.field_count()
273            self.__cur_prop = -1
274            field = layer.GetClassification().GetField()
275          for i in range(self.num_cols):          for i in range(self.num_cols):
276              type, name, len, decc = layer.table.field_info(i)              type, name, len, decc = layer.table.field_info(i)
277                if name == field:
278                    self.__cur_prop = i
279              self.properties.Append(name)              self.properties.Append(name)
280                self.properties.SetClientData(i, None)
281    
282          propertyBox.Add(self.properties, 0, wxALL, 4)          self.properties.SetSelection(self.__cur_prop)
283            propertyBox.Add(self.properties, 1, wxGROW|wxALL, 0)
284          EVT_COMBOBOX(self, ID_PROPERTY_SELECT, self.OnPropertySelect)          EVT_COMBOBOX(self, ID_PROPERTY_SELECT, self.OnPropertySelect)
285    
286          topBox.Add(propertyBox, 0, 0)          topBox.Add(propertyBox, 0, wxGROW, 4)
287    
288          #          #
289          # Classification data table          # Classification data table
290          #          #
291    
292          table = wxPyGridTableBase()          controlBox = wxBoxSizer(wxHORIZONTAL)
293          tableBox = wxGridSizer(25)          self.classGrid = ClassGrid(self, layer)
         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"))  
294    
295          tableBox.Add(self.classTable, 0, wxALL, 4)          controlBox.Add(self.classGrid, 1, wxGROW, 0)
296            
297          topBox.Add(self.classTable, 0, 0)          controlButtonBox = wxBoxSizer(wxVERTICAL)
298            controlButtonBox.Add(wxButton(self, ID_CLASSIFY_ADD,
299                _("Add")), 0, wxGROW | wxALL, 4)
300            controlButtonBox.Add(wxButton(self, ID_CLASSIFY_GENRANGE,
301                _("Generate Ranges")), 0, wxGROW | wxALL, 4)
302    
303            controlBox.Add(controlButtonBox, 0, wxGROW, 10)
304            topBox.Add(controlBox, 1, wxGROW, 10)
305    
306            EVT_BUTTON(self, ID_CLASSIFY_ADD, self.OnAdd)
307            EVT_BUTTON(self, ID_CLASSIFY_GENRANGE, self.OnGenRange)
308            EVT_GRID_CELL_LEFT_DCLICK(self.classGrid, self.OnCellDClick)
309    
310          #          #
311          # Control buttons:          # Control buttons:
# Line 84  class Classifier(wxDialog): Line 325  class Classifier(wxDialog):
325          topBox.Fit(self)          topBox.Fit(self)
326          topBox.SetSizeHints(self)          topBox.SetSizeHints(self)
327    
328      def OnPropertySelect(self, event): pass      def __BuildClassification(self, prop):
329    
330            clazz = Classification()
331            clazz.SetField(self.properties.GetStringSelection())
332    
333            numRows = self.classGrid.GetNumberRows()
334    
335            if numRows > 0:
336                table = self.classGrid.GetTable()
337                clazz.SetDefaultData(table.GetClassData(0))
338    
339                for i in range(1, numRows):
340                    clazz.AddClassData(table.GetClassData(i))
341    
342            return clazz
343    
344        def OnPropertySelect(self, event):
345            self.properties.SetClientData(
346                self.__cur_prop, self.__BuildClassification(self.__cur_prop))
347    
348            self.__cur_prop = self.properties.GetSelection()
349            clazz = self.properties.GetClientData(self.__cur_prop)
350            table = self.classGrid.GetTable()
351    
352            table.Reset(clazz, self.layer.ShapeType())
353    
354      def OnOK(self, event):      def OnOK(self, event):
355            """Put the data from the table into a new Classification and hand
356               it to the layer.
357            """
358    
359            clazz = self.properties.GetClientData(self.__cur_prop)
360    
361            #
362            # only build the classification if there wasn't one to
363            # to begin with or it has been modified
364            #
365            if clazz is None or self.classGrid.GetTable().IsModified():
366                clazz = self.__BuildClassification(self.__cur_prop)
367    
368            clazz.SetLayer(self.layer)
369    
370            self.layer.SetClassification(clazz)
371    
372          self.EndModal(wxID_OK)          self.EndModal(wxID_OK)
373    
374      def OnCancel(self, event):      def OnCancel(self, event):
375            """Do nothing. The layer's current classification stays the same."""
376          self.EndModal(wxID_CANCEL)          self.EndModal(wxID_CANCEL)
377    
378    
379        def OnAdd(self, event):
380            self.classGrid.GetTable().AddNewDataRow()
381            print "Classifier.OnAdd()"
382    
383        def OnGenRange(self, event):
384            print "Classifier.OnGenRange()"
385    
386        def OnCellDClick(self, event):
387            r = event.GetRow()
388            c = event.GetCol()
389            if c == COL_VISUAL:
390                prop = self.classGrid.GetTable().GetValueAsCustom(r, c, None)
391                propDlg = SelectPropertiesDialog(NULL, prop, self.layer.ShapeType())
392                if propDlg.ShowModal() == wxID_OK:
393                    new_prop = propDlg.GetClassData()
394                    prop.SetStroke(new_prop.GetStroke())
395                    prop.SetStrokeWidth(new_prop.GetStrokeWidth())
396                    prop.SetFill(new_prop.GetFill())
397                    self.classGrid.Refresh()
398                propDlg.Destroy()
399    
400    
401    ID_SELPROP_OK = 4001
402    ID_SELPROP_CANCEL = 4002
403    ID_SELPROP_SPINCTRL = 4002
404    ID_SELPROP_PREVIEW = 4003
405    ID_SELPROP_STROKECLR = 4004
406    ID_SELPROP_FILLCLR = 4005
407    
408    class SelectPropertiesDialog(wxDialog):
409    
410        def __init__(self, parent, prop, shapeType):
411            wxDialog.__init__(self, parent, -1, _("Select Properties"),
412                              style = wxRESIZE_BORDER)
413    
414            self.prop = ClassData(classData = prop)
415    
416            topBox = wxBoxSizer(wxVERTICAL)
417    
418            itemBox = wxBoxSizer(wxHORIZONTAL)
419    
420            # preview box
421            previewBox = wxBoxSizer(wxVERTICAL)
422            previewBox.Add(wxStaticText(self, -1, _("Preview:")),
423                0, wxALIGN_LEFT | wxALL, 4)
424            self.previewer = ClassDataPreviewer(None, self.prop, shapeType,
425                                                self, ID_SELPROP_PREVIEW, (40, 40))
426            previewBox.Add(self.previewer, 1, wxGROW, 15)
427    
428            itemBox.Add(previewBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
429    
430            # control box
431            ctrlBox = wxBoxSizer(wxVERTICAL)
432            ctrlBox.Add(
433                wxButton(self, ID_SELPROP_STROKECLR, "Change Stroke Color"),
434                0, wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
435            EVT_BUTTON(self, ID_SELPROP_STROKECLR, self.OnChangeStrokeColor)
436    
437            if shapeType != SHAPETYPE_ARC:
438                ctrlBox.Add(
439                    wxButton(self, ID_SELPROP_FILLCLR, "Change Fill Color"),
440                    0, wxALIGN_LEFT | wxALL | wxGROW, 4)
441                EVT_BUTTON(self, ID_SELPROP_FILLCLR, self.OnChangeFillColor)
442    
443            spinBox = wxBoxSizer(wxHORIZONTAL)
444            spinBox.Add(wxStaticText(self, -1, _("Stroke Width: ")),
445                    0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
446            self.spinCtrl = wxSpinCtrl(self, ID_SELPROP_SPINCTRL,
447                                       min=1, max=10,
448                                       value=str(prop.GetStrokeWidth()),
449                                       initial=prop.GetStrokeWidth())
450    
451            EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL, self.OnSpin)
452    
453            spinBox.Add(self.spinCtrl, 0, wxALIGN_LEFT | wxALL, 4)
454    
455            ctrlBox.Add(spinBox, 0, wxALIGN_RIGHT | wxALL, 0)
456            itemBox.Add(ctrlBox, 0, wxALIGN_RIGHT | wxALL | wxGROW, 0)
457            topBox.Add(itemBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
458    
459    
460            #
461            # Control buttons:
462            #
463            buttonBox = wxBoxSizer(wxHORIZONTAL)
464            buttonBox.Add(wxButton(self, ID_CLASSIFY_OK, _("OK")),
465                          0, wxALL, 4)
466            buttonBox.Add(wxButton(self, ID_CLASSIFY_CANCEL, _("Cancel")),
467                          0, wxALL, 4)
468            topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)
469                                                                                    
470            EVT_BUTTON(self, ID_SELPROP_OK, self.OnOK)
471            EVT_BUTTON(self, ID_SELPROP_CANCEL, self.OnCancel)
472                                                                                    
473            self.SetAutoLayout(true)
474            self.SetSizer(topBox)
475            topBox.Fit(self)
476            topBox.SetSizeHints(self)
477    
478        def OnOK(self, event):
479            self.EndModal(wxID_OK)
480    
481        def OnCancel(self, event):
482            self.EndModal(wxID_CANCEL)
483    
484        def OnSpin(self, event):
485            self.prop.SetStrokeWidth(self.spinCtrl.GetValue())
486            self.previewer.Refresh()
487    
488        def __GetColor(self, cur):
489            dialog = wxColourDialog(self)
490            dialog.GetColourData().SetColour(Color2wxColour(cur))
491            ret = None
492            if dialog.ShowModal() == wxID_OK:
493                ret = wxColour2Color(dialog.GetColourData().GetColour())
494    
495            dialog.Destroy()
496    
497            return ret
498            
499        def OnChangeStrokeColor(self, event):
500            clr = self.__GetColor(self.prop.GetStroke())
501            if clr is not None:
502                self.prop.SetStroke(clr)
503            self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
504    
505        def OnChangeFillColor(self, event):
506            clr = self.__GetColor(self.prop.GetFill())
507            if clr is not None:
508                self.prop.SetFill(clr)
509            self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
510    
511        def GetClassData(self):
512            return self.prop
513    
514    
515    class ClassDataPreviewer(wxWindow):
516    
517        def __init__(self, rect, data, shapeType,
518                           parent = None, id = -1, size = wxDefaultSize):
519            if parent is not None:
520                wxWindow.__init__(self, parent, id, size=size)
521                EVT_PAINT(self, self.OnPaint)
522    
523            self.rect = rect
524            self.data = data
525            self.shapeType = shapeType
526    
527        def OnPaint(self, event):
528            dc = wxPaintDC(self)
529    
530            # XXX: this doesn't seem to be having an effect:
531            dc.DestroyClippingRegion()
532    
533            self.Draw(dc, None)
534    
535        def Draw(self, dc, rect, data = None, shapeType = None):
536    
537            if data is None: data = self.data
538            if shapeType is None: shapeType = self.shapeType
539    
540            if rect is None:
541                x = y = 0
542                w, h = self.GetClientSizeTuple()
543            else:
544                x = rect.GetX()
545                y = rect.GetY()
546                w = rect.GetWidth()
547                h = rect.GetHeight()
548    
549            stroke = data.GetStroke()
550            if stroke is Color.None:
551                pen = wxTRANSPARENT_PEN
552            else:
553                pen = wxPen(Color2wxColour(stroke),
554                            data.GetStrokeWidth(),
555                            wxSOLID)
556    
557            stroke = data.GetFill()
558            if stroke is Color.None:
559                brush = wxTRANSPARENT_BRUSH
560            else:
561                brush = wxBrush(Color2wxColour(stroke), wxSOLID)
562    
563            dc.SetPen(pen)
564            dc.SetBrush(brush)
565    
566            if shapeType == SHAPETYPE_ARC:
567                dc.DrawSpline([wxPoint(x, y + h),
568                               wxPoint(x + w/2, y + h/4),
569                               wxPoint(x + w/2, y + h/4*3),
570                               wxPoint(x + w, y)])
571    
572            elif shapeType == SHAPETYPE_POINT or \
573                 shapeType == SHAPETYPE_POLYGON:
574    
575                dc.DrawCircle(x + w/2, y + h/2,
576                              (min(w, h) - data.GetStrokeWidth())/2)
577    
578    class ClassRenderer(wxPyGridCellRenderer):
579    
580        def __init__(self, shapeType):
581            wxPyGridCellRenderer.__init__(self)
582            self.previewer = ClassDataPreviewer(None, None, shapeType)
583    
584        def Draw(self, grid, attr, dc, rect, row, col, isSelected):
585            data = grid.GetTable().GetValueAsCustom(row, col, "")
586    
587    
588            dc.SetClippingRegion(rect.GetX(), rect.GetY(),
589                                 rect.GetWidth(), rect.GetHeight())
590            dc.SetPen(wxPen(wxLIGHT_GREY))
591            dc.SetBrush(wxBrush(wxLIGHT_GREY, wxSOLID))
592            dc.DrawRectangle(rect.GetX(), rect.GetY(),
593                             rect.GetWidth(), rect.GetHeight())
594    
595            self.previewer.Draw(dc, rect, data)
596    
597            if isSelected:
598                dc.SetPen(wxPen(wxColour(0 * 255, 0 * 255, 0 * 255),
599                          4, wxSOLID))
600                dc.SetBrush(wxTRANSPARENT_BRUSH)
601                dc.DrawRectangle(rect.GetX(), rect.GetY(),
602                                 rect.GetWidth(), rect.GetHeight())
603    
604            dc.DestroyClippingRegion()
605    

Legend:
Removed from v.374  
changed lines
  Added in v.430

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26