/[thuban]/trunk/thuban/Thuban/UI/classifier.py
ViewVC logotype

Annotation of /trunk/thuban/Thuban/UI/classifier.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 415 - (hide annotations)
Wed Feb 19 16:52:51 2003 UTC (22 years ago) by jonathan
File MIME type: text/x-python
File size: 17811 byte(s)
(ClassGrid): New class to represent a
        custom grid.
(ClassTable): Support for editing Values and Labels and for
        changing what type (point or range) of data is stored in each
        property based on how the user enters the data.
(Classifier): Support for saving the new classifications and
        launching the dialog to edit a property.
(SelectPropertiesDialog): New class for editing the visual
        properties of a classification (stroke color, width, and fill color)
(ClassPreviewer): Took the Draw method from ClassRenderer and
        made most of it into this new class. Intend to use this class in
        the SelectPropertiesDialog for previewing changes.

1 jonathan 372 # Copyright (c) 2001 by Intevation GmbH
2     # Authors:
3     # Jonathan Coles <[email protected]>
4     #
5     # This program is free software under the GPL (>=v2)
6     # Read the file COPYING coming with Thuban for details.
7    
8     """Dialog for classifying how layers are displayed"""
9    
10     __version__ = "$Revision$"
11    
12 jonathan 376 import copy
13    
14 jonathan 372 from wxPython.wx import *
15     from wxPython.grid import *
16    
17 jan 374 from Thuban import _
18 jonathan 415 from Thuban.common import *
19 jan 374
20 jonathan 415 from Thuban.Model.classification import * #Classification, ClassData
21 jonathan 392
22 jonathan 415 from Thuban.Model.color import Color
23    
24 jonathan 392 from Thuban.Model.layer import SHAPETYPE_ARC, SHAPETYPE_POLYGON, SHAPETYPE_POINT
25    
26 jonathan 372 ID_PROPERTY_SELECT = 4010
27     ID_CLASS_TABLE = 40011
28    
29     ID_CLASSIFY_OK = 4001
30     ID_CLASSIFY_CANCEL = 4002
31 jonathan 415 ID_CLASSIFY_ADD = 4003
32     ID_CLASSIFY_GENRANGE = 4004
33 jonathan 372
34 jonathan 415 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 jonathan 376 class ClassTable(wxPyGridTableBase):
63    
64 jonathan 415 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 jonathan 376 wxPyGridTableBase.__init__(self)
73 jonathan 415 self.SetView(view)
74     self.tdata = []
75 jonathan 376
76 jonathan 415 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 jonathan 376 self.tdata = []
88    
89 jonathan 415 if clazz is None:
90     clazz = Classification()
91 jonathan 392
92 jonathan 415 p = clazz.GetDefaultData()
93     np = ClassDataPoint(classData = p)
94     self.tdata.append([np, 'DEFAULT', np.GetLabel()])
95 jonathan 376
96 jonathan 415 for p in clazz.points.values():
97     np = ClassDataPoint(p.GetValue(), classData = p)
98     self.tdata.append([np, np.GetValue(), np.GetLabel()])
99 jonathan 376
100 jonathan 415 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 jonathan 376 def GetNumberRows(self):
137     return len(self.tdata)
138    
139     def GetNumberCols(self):
140 jonathan 415 return self.NUM_COLS
141 jonathan 376
142     def IsEmptyCell(self, row, col):
143 jonathan 415 return 0
144 jonathan 376
145     def GetValue(self, row, col):
146 jonathan 415 return self.GetValueAsCustom(row, col, "")
147 jonathan 376
148     def SetValue(self, row, col, value):
149 jonathan 415 self.SetValueAsCustom(row, col, "", value)
150     self.__Modified()
151    
152 jonathan 392 def GetValueAsCustom(self, row, col, typeName):
153     return self.tdata[row][col]
154 jonathan 376
155 jonathan 415 def __ParseInput(self, value):
156     """Try to determine what kind of input value is
157     (a single number or a range)
158     """
159 jonathan 392
160 jonathan 415 #
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     # i just want it to redraw!
215     self.GetView().BeginBatch()
216     self.GetView().EndBatch()
217    
218     elif col == COL_LABEL:
219     data.SetLabel(value)
220     self.tdata[row][COL_LABEL] = data.GetLabel()
221     else:
222     raise ValueError(_("Invalid column request"))
223    
224     self.__Modified()
225    
226     def GetAttr(self, row, col, someExtraParameter):
227     attr = wxGridCellAttr()
228     #attr = wxPyGridTableBase.GetAttr(self, row, col, someExtraParameter)
229    
230     if col == COL_VISUAL:
231     attr.SetRenderer(ClassRenderer(self.shapeType))
232     attr.SetReadOnly()
233    
234     return attr
235    
236     def GetClassData(self, row):
237     return self.tdata[row][COL_VISUAL]
238    
239     def __Modified(self):
240     self.modified = 1
241    
242     def IsModified(self):
243     return self.modified
244    
245     def AddNewDataRow(self):
246     np = ClassDataPoint()
247     self.tdata.append([np, np.GetValue(), np.GetLabel()])
248     msg = wxGridTableMessage(self, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, 1)
249     self.GetView().ProcessTableMessage(msg)
250     self.GetView().BeginBatch()
251     self.GetView().EndBatch()
252    
253 jonathan 372 class Classifier(wxDialog):
254    
255     def __init__(self, parent, layer):
256 jan 374 wxDialog.__init__(self, parent, -1, _("Classify"),
257 jonathan 372 style = wxRESIZE_BORDER)
258    
259 jonathan 415 self.layer = layer
260    
261 jonathan 372 topBox = wxBoxSizer(wxVERTICAL)
262    
263 jonathan 415 topBox.Add(wxStaticText(self, -1, _("Layer: %s") % layer.Title()),
264     0, wxALIGN_LEFT | wxBOTTOM, 4)
265     topBox.Add(wxStaticText(self, -1, _("Type: %s") % layer.ShapeType()),
266     0, wxALIGN_LEFT | wxBOTTOM, 4)
267    
268 jonathan 372 propertyBox = wxBoxSizer(wxHORIZONTAL)
269 jonathan 415 propertyBox.Add(wxStaticText(self, -1, _("Property: ")),
270 jonathan 372 0, wxALIGN_CENTER | wxALL, 4)
271    
272     self.properties = wxComboBox(self, ID_PROPERTY_SELECT, "",
273     style = wxCB_READONLY)
274    
275     self.num_cols = layer.table.field_count()
276 jonathan 415 self.__cur_prop = -1
277     field = layer.GetClassification().GetField()
278 jonathan 372 for i in range(self.num_cols):
279     type, name, len, decc = layer.table.field_info(i)
280 jonathan 415 if name == field:
281     self.__cur_prop = i
282 jonathan 372 self.properties.Append(name)
283 jonathan 415 self.properties.SetClientData(i, None)
284 jonathan 372
285 jonathan 415 self.properties.SetSelection(self.__cur_prop)
286     propertyBox.Add(self.properties, 1, wxGROW|wxALL, 0)
287 jonathan 372 EVT_COMBOBOX(self, ID_PROPERTY_SELECT, self.OnPropertySelect)
288    
289 jonathan 376 topBox.Add(propertyBox, 0, wxGROW, 4)
290 jonathan 372
291     #
292     # Classification data table
293     #
294    
295 jonathan 415 controlBox = wxBoxSizer(wxHORIZONTAL)
296     self.classGrid = ClassGrid(self, layer)
297 jonathan 376
298 jonathan 415 controlBox.Add(self.classGrid, 1, wxGROW, 0)
299 jonathan 376
300 jonathan 415 controlButtonBox = wxBoxSizer(wxVERTICAL)
301     controlButtonBox.Add(wxButton(self, ID_CLASSIFY_ADD,
302     _("Add")), 0, wxGROW | wxALL, 4)
303     controlButtonBox.Add(wxButton(self, ID_CLASSIFY_GENRANGE,
304     _("Generate Ranges")), 0, wxGROW | wxALL, 4)
305 jonathan 372
306 jonathan 415 controlBox.Add(controlButtonBox, 0, wxGROW, 10)
307     topBox.Add(controlBox, 1, wxGROW, 10)
308    
309     EVT_BUTTON(self, ID_CLASSIFY_ADD, self.OnAdd)
310     EVT_BUTTON(self, ID_CLASSIFY_GENRANGE, self.OnGenRange)
311     EVT_GRID_CELL_LEFT_DCLICK(self.classGrid, self.OnCellDClick)
312    
313 jonathan 372 #
314     # Control buttons:
315     #
316     buttonBox = wxBoxSizer(wxHORIZONTAL)
317 jan 374 buttonBox.Add(wxButton(self, ID_CLASSIFY_OK, _("OK")),
318 jonathan 372 0, wxALL, 4)
319 jan 374 buttonBox.Add(wxButton(self, ID_CLASSIFY_CANCEL, _("Cancel")),
320 jonathan 372 0, wxALL, 4)
321     topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)
322    
323     EVT_BUTTON(self, ID_CLASSIFY_OK, self.OnOK)
324     EVT_BUTTON(self, ID_CLASSIFY_CANCEL, self.OnCancel)
325    
326     self.SetAutoLayout(true)
327     self.SetSizer(topBox)
328     topBox.Fit(self)
329     topBox.SetSizeHints(self)
330    
331 jonathan 415 def __BuildClassification(self, prop):
332    
333     clazz = Classification()
334     clazz.SetField(self.properties.GetStringSelection())
335    
336     numRows = self.classGrid.GetNumberRows()
337    
338     if numRows > 0:
339     table = self.classGrid.GetTable()
340     clazz.SetDefaultData(table.GetClassData(0))
341    
342     for i in range(1, numRows):
343     clazz.AddClassData(table.GetClassData(i))
344    
345     return clazz
346    
347     def OnPropertySelect(self, event):
348     self.properties.SetClientData(
349     self.__cur_prop, self.__BuildClassification(self.__cur_prop))
350    
351     self.__cur_prop = self.properties.GetSelection()
352     clazz = self.properties.GetClientData(self.__cur_prop)
353     table = self.classGrid.GetTable()
354    
355     table.Reset(clazz, self.layer.ShapeType())
356    
357     def OnOK(self, event):
358     """Put the data from the table into a new Classification and hand
359     it to the layer.
360     """
361    
362     clazz = self.properties.GetClientData(self.__cur_prop)
363    
364     #
365     # only build the classification if there wasn't one to
366     # to begin with or it has been modified
367     #
368     if clazz is None or self.classGrid.GetTable().IsModified():
369     clazz = self.__BuildClassification(self.__cur_prop)
370    
371     clazz.SetLayer(self.layer)
372    
373     self.layer.SetClassification(clazz)
374    
375     self.EndModal(wxID_OK)
376    
377     def OnCancel(self, event):
378     """Do nothing. The layer's current classification stays the same."""
379     self.EndModal(wxID_CANCEL)
380    
381    
382     def OnAdd(self, event):
383     self.classGrid.GetTable().AddNewDataRow()
384     print "Classifier.OnAdd()"
385    
386     def OnGenRange(self, event):
387     print "Classifier.OnGenRange()"
388    
389     def OnCellDClick(self, event):
390     r = event.GetRow()
391     c = event.GetCol()
392     if c == COL_VISUAL:
393     prop = self.classGrid.GetTable().GetValueAsCustom(r, c, None)
394     propDlg = SelectPropertiesDialog(NULL, prop, self.layer.ShapeType())
395     if propDlg.ShowModal() == wxID_OK:
396     new_prop = propDlg.GetClassData()
397     prop.SetStroke(new_prop.GetStroke())
398     prop.SetStrokeWidth(new_prop.GetStrokeWidth())
399     prop.SetFill(new_prop.GetFill())
400     self.classGrid.BeginBatch()
401     self.classGrid.EndBatch()
402     propDlg.Destroy()
403    
404    
405     ID_SELPROP_OK = 4001
406     ID_SELPROP_CANCEL = 4002
407     ID_SELPROP_SPINCTRL = 4002
408    
409     class SelectPropertiesDialog(wxDialog):
410    
411     def __init__(self, parent, prop, shapeType):
412     wxDialog.__init__(self, parent, -1, _("Select Properties"),
413     style = wxRESIZE_BORDER)
414    
415     topBox = wxBoxSizer(wxVERTICAL)
416 jonathan 372
417 jonathan 415 self.prop = ClassData(classData = prop)
418    
419     topBox.Add(wxStaticText(self, -1, _("Stroke Color: ")),
420     0, wxALIGN_LEFT | wxALL, 4)
421    
422     spinBox = wxBoxSizer(wxHORIZONTAL)
423     spinBox.Add(wxStaticText(self, -1, _("Stroke Width: ")),
424     0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxBOTTOM, 4)
425     self.spinCtrl = wxSpinCtrl(self, ID_SELPROP_SPINCTRL,
426     min=1, max=10,
427     value=str(prop.GetStrokeWidth()),
428     initial=prop.GetStrokeWidth())
429    
430     EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL, self.OnSpin)
431    
432     spinBox.Add(self.spinCtrl, 0, wxALIGN_LEFT | wxALL, 4)
433    
434     topBox.Add(spinBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)
435    
436     if shapeType != SHAPETYPE_ARC:
437     topBox.Add(wxStaticText(self, -1, _("Fill Color: ")),
438     0, wxALIGN_LEFT | wxBOTTOM, 4)
439    
440     #
441     # Control buttons:
442     #
443     buttonBox = wxBoxSizer(wxHORIZONTAL)
444     buttonBox.Add(wxButton(self, ID_CLASSIFY_OK, _("OK")),
445     0, wxALL, 4)
446     buttonBox.Add(wxButton(self, ID_CLASSIFY_CANCEL, _("Cancel")),
447     0, wxALL, 4)
448     topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)
449    
450     EVT_BUTTON(self, ID_SELPROP_OK, self.OnOK)
451     EVT_BUTTON(self, ID_SELPROP_CANCEL, self.OnCancel)
452    
453     self.SetAutoLayout(true)
454     self.SetSizer(topBox)
455     topBox.Fit(self)
456     topBox.SetSizeHints(self)
457    
458 jonathan 372 def OnOK(self, event):
459     self.EndModal(wxID_OK)
460    
461     def OnCancel(self, event):
462     self.EndModal(wxID_CANCEL)
463    
464 jonathan 415 def OnSpin(self, event):
465     self.prop.SetStrokeWidth(self.spinCtrl.GetValue())
466 jonathan 392
467 jonathan 415 def GetClassData(self):
468     return self.prop
469 jonathan 392
470    
471 jonathan 415 class ClassDataPreviewer:
472    
473     def Draw(self, dc, rect, data, shapeType):
474    
475     stroke = data.GetStroke()
476     if stroke is Color.None:
477 jonathan 392 pen = wxTRANSPARENT_PEN
478     else:
479     pen = wxPen(wxColour(stroke.red * 255,
480     stroke.green * 255,
481     stroke.blue * 255),
482 jonathan 415 data.GetStrokeWidth(),
483 jonathan 392 wxSOLID)
484    
485 jonathan 415 stroke = data.GetFill()
486     if stroke is Color.None:
487 jonathan 392 brush = wxTRANSPARENT_BRUSH
488     else:
489     brush = wxBrush(wxColour(stroke.red * 255,
490     stroke.green * 255,
491     stroke.blue * 255), wxSOLID)
492    
493     dc.SetPen(pen)
494     dc.SetBrush(brush)
495    
496 jonathan 415 if shapeType == SHAPETYPE_ARC:
497 jonathan 392 dc.DrawSpline([wxPoint(rect.GetX(), rect.GetY() + rect.GetHeight()),
498     wxPoint(rect.GetX() + rect.GetWidth()/2,
499     rect.GetY() + rect.GetHeight()/4),
500     wxPoint(rect.GetX() + rect.GetWidth()/2,
501     rect.GetY() + rect.GetHeight()/4*3),
502     wxPoint(rect.GetX() + rect.GetWidth(), rect.GetY())])
503    
504 jonathan 415 elif shapeType == SHAPETYPE_POINT or \
505     shapeType == SHAPETYPE_POLYGON:
506    
507 jonathan 392 dc.DrawCircle(rect.GetX() + rect.GetWidth()/2,
508     rect.GetY() + rect.GetHeight()/2,
509     (min(rect.GetWidth(), rect.GetHeight())
510 jonathan 415 - data.GetStrokeWidth())/2)
511 jonathan 392
512 jonathan 415 class ClassRenderer(wxPyGridCellRenderer):
513    
514     def __init__(self, shapeType):
515     wxPyGridCellRenderer.__init__(self)
516     self.shapeType = shapeType
517    
518     def Draw(self, grid, attr, dc, rect, row, col, isSelected):
519     data = grid.GetTable().GetValueAsCustom(row, col, "")
520    
521    
522     dc.SetClippingRegion(rect.GetX(), rect.GetY(),
523     rect.GetWidth(), rect.GetHeight())
524     dc.SetPen(wxPen(wxLIGHT_GREY))
525     dc.SetBrush(wxBrush(wxLIGHT_GREY, wxSOLID))
526     dc.DrawRectangle(rect.GetX(), rect.GetY(),
527     rect.GetWidth(), rect.GetHeight())
528    
529     ClassDataPreviewer().Draw(dc, rect, data, self.shapeType)
530    
531     if isSelected:
532     dc.SetPen(wxPen(wxColour(0 * 255, 0 * 255, 0 * 255),
533     4, wxSOLID))
534     dc.SetBrush(wxTRANSPARENT_BRUSH)
535     dc.DrawRectangle(rect.GetX(), rect.GetY(),
536     rect.GetWidth(), rect.GetHeight())
537    
538 jonathan 392 dc.DestroyClippingRegion()
539    

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26