/[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 460 - (hide annotations)
Wed Mar 5 18:16:28 2003 UTC (22 years ago) by jonathan
File MIME type: text/x-python
File size: 31165 byte(s)
Added class documentation.
        Fixed RTbug #1713, #1714. Added Move[Up|Down] buttons.
        Store just the groups in the table and generate the other
        column information when it is requested. Add "None" field
        to pull-down to select no classification.

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 jonathan 441 from Thuban.UI.common import *
20 jan 374
21 jonathan 451 from Thuban.Model.classification import *
22 jonathan 392
23 jonathan 415 from Thuban.Model.color import Color
24    
25 jonathan 460 from Thuban.Model.layer import Layer, SHAPETYPE_ARC, SHAPETYPE_POLYGON, SHAPETYPE_POINT
26 jonathan 392
27 jonathan 460 # widget id's
28 jonathan 372 ID_PROPERTY_SELECT = 4010
29     ID_CLASS_TABLE = 40011
30    
31     ID_CLASSIFY_OK = 4001
32     ID_CLASSIFY_CANCEL = 4002
33 jonathan 415 ID_CLASSIFY_ADD = 4003
34     ID_CLASSIFY_GENRANGE = 4004
35 jonathan 451 ID_CLASSIFY_REMOVE = 4005
36 jonathan 460 ID_CLASSIFY_MOVEUP = 4006
37     ID_CLASSIFY_MOVEDOWN = 4007
38 jonathan 372
39 jonathan 460 # table columns
40     COL_SYMBOL = 0
41 jonathan 415 COL_VALUE = 1
42     COL_LABEL = 2
43    
44 jonathan 460 # indices into the client data lists in Classifier.fields
45 jonathan 451 FIELD_CLASS = 0
46     FIELD_TYPE = 1
47     FIELD_NAME = 2
48    
49 jonathan 415 #
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 jonathan 460 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 jonathan 451 wxGrid.__init__(self, parent, ID_CLASS_TABLE, size = (340, 160))
66 bh 455 self.SetTable(ClassTable(fieldData, layer.ShapeType(), self), true)
67 jonathan 451 self.SetSelectionMode(wxGrid.wxGridSelectRows)
68 jonathan 415
69 jonathan 460 EVT_GRID_CELL_LEFT_DCLICK(self, self._OnCellDClick)
70 jonathan 451 EVT_GRID_RANGE_SELECT(self, self._OnSelectedRange)
71     EVT_GRID_SELECT_CELL(self, self._OnSelectedCell)
72    
73     self.currentSelection = []
74    
75 jonathan 460 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 jonathan 451 def GetCurrentSelection(self):
89 jonathan 460 """Return the currently highlighted rows as an increasing list
90     of row numbers."""
91 jonathan 451 sel = copy.copy(self.currentSelection)
92     sel.sort()
93     return sel
94    
95 jonathan 415 def SetCellRenderer(self, row, col):
96     raise ValueError(_("Must not allow setting of renderer in ClassGrid!"))
97    
98 jonathan 460 #
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 jonathan 415 def SetTable(self, object, *attributes):
105     self.tableRef = weakref.ref(object)
106     return wxGrid.SetTable(self, object, *attributes)
107    
108     def GetTable(self):
109 jonathan 460 try:
110     return self.tableRef()
111     except:
112     return None
113 jonathan 415
114 jonathan 451 def DeleteSelectedRows(self):
115 jonathan 460 """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 jonathan 451 sel = self.GetCurrentSelection()
121 jonathan 415
122 jonathan 460 # 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 jonathan 451 if len(sel) == 1:
128 jonathan 460 group = self.GetTable().GetValueAsCustom(sel[0], COL_SYMBOL, None)
129 jonathan 451 if isinstance(group, ClassGroupDefault):
130     wxMessageDialog(self,
131     "The Default group cannot be removed.",
132     style = wxOK | wxICON_EXCLAMATION).ShowModal()
133     return
134    
135 jonathan 460
136 jonathan 451 self.ClearSelection()
137    
138 jonathan 460 # 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 jonathan 451 sel.reverse()
141 jonathan 460
142     #
143     # actually remove the rows
144     #
145 jonathan 451 table = self.GetTable()
146     for row in sel:
147     table.DeleteRows(row)
148    
149 jonathan 460 #
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 jonathan 451 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 jonathan 460 def _OnCellDClick(self, event):
172     """Handle a double on a cell."""
173    
174 jonathan 451 r = event.GetRow()
175     c = event.GetCol()
176 jonathan 460 if c == COL_SYMBOL:
177 jonathan 451 group = self.GetTable().GetValueAsCustom(r, c, None)
178     prop = group.GetProperties()
179 jonathan 460
180     # get a new ClassGroupProperties object and copy the
181     # values over to our current object
182     propDlg = SelectPropertiesDialog(NULL, prop, self.shapeType)
183 jonathan 451 if propDlg.ShowModal() == wxID_OK:
184     new_prop = propDlg.GetClassGroupProperties()
185 jonathan 460 prop.SetProperties(new_prop)
186 jonathan 451 self.Refresh()
187     propDlg.Destroy()
188    
189     #
190     # _OnSelectedRange() and _OnSelectedCell() were borrowed
191 jonathan 460 # from http://wiki.wxpython.org to keep track of which
192     # cells are currently highlighted
193 jonathan 451 #
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 jonathan 376 class ClassTable(wxPyGridTableBase):
215 jonathan 460 """Represents the underlying data structure for the grid."""
216 jonathan 376
217 jonathan 415 NUM_COLS = 3
218    
219 jonathan 451 __col_labels = [_("Symbol"), _("Value"), _("Label")]
220 jonathan 415
221 jonathan 460 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 jonathan 376 wxPyGridTableBase.__init__(self)
230 jonathan 415 self.SetView(view)
231     self.tdata = []
232 jonathan 376
233 jonathan 460 self.Reset(clazz, shapeType)
234 jonathan 415
235 jonathan 460 def Reset(self, clazz, shapeType):
236     """Reset the table with the given data.
237 jonathan 415
238 jonathan 460 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 jonathan 415 self.GetView().BeginBatch()
252    
253 jonathan 460 self.clazz = clazz
254 jonathan 415 self.shapeType = shapeType
255     self.renderer = ClassRenderer(self.shapeType)
256    
257 jonathan 451 old_len = len(self.tdata)
258 jonathan 415
259 jonathan 376 self.tdata = []
260    
261 jonathan 460 #
262     # copy the data out of the classification and into our
263     # array
264     #
265     for p in self.clazz:
266 jonathan 441 np = copy.copy(p)
267 jonathan 460 self.__SetRow(-1, np)
268 jonathan 441
269    
270 jonathan 460 self.__Modified(False)
271 jonathan 415
272 jonathan 451 self.__NotifyRowChanges(old_len, len(self.tdata))
273     self.GetView().EndBatch()
274    
275     def __NotifyRowChanges(self, curRows, newRows):
276 jonathan 415 #
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 jonathan 441 def __SetRow(self, row, group):
293 jonathan 460 """Set a row's data to that of the group.
294 jonathan 441
295 jonathan 460 row -- if row is -1 or greater than the current number of rows
296     then group is appended to the end.
297     """
298 jonathan 441
299 jonathan 460 # either append or replace
300     if row == -1 or row >= self.GetNumberRows():
301     self.tdata.append(group)
302 jonathan 441 else:
303 jonathan 460 self.tdata[row] = group
304 jonathan 441
305 jonathan 460 self.__Modified()
306    
307 jonathan 415 def GetColLabelValue(self, col):
308 jonathan 460 """Return the label for the given column."""
309 jonathan 415 return self.__col_labels[col]
310    
311     def GetRowLabelValue(self, row):
312 jonathan 460 """Return the label for the given row."""
313 jonathan 415
314 jonathan 460 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 jonathan 376 def GetNumberRows(self):
324 jonathan 460 """Return the number of rows."""
325 jonathan 376 return len(self.tdata)
326    
327     def GetNumberCols(self):
328 jonathan 460 """Return the number of columns."""
329 jonathan 415 return self.NUM_COLS
330 jonathan 376
331     def IsEmptyCell(self, row, col):
332 jonathan 460 """Determine if a cell is empty. This is always false."""
333     return False
334 jonathan 376
335     def GetValue(self, row, col):
336 jonathan 460 """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 jonathan 376
340     def SetValue(self, row, col, value):
341 jonathan 460 """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 jonathan 415 self.__Modified()
348    
349 jonathan 392 def GetValueAsCustom(self, row, col, typeName):
350 jonathan 460 """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 jonathan 376
356 jonathan 460 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 jonathan 415 def __ParseInput(self, value):
378     """Try to determine what kind of input value is
379 jonathan 460 (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 jonathan 415 """
384 jonathan 392
385 jonathan 460 type = self.clazz.GetFieldType()
386 jonathan 415
387 jonathan 460 if type == FIELDTYPE_STRING:
388 jonathan 451 return (value,)
389 jonathan 460 elif type == FIELDTYPE_INT or type == FIELDTYPE_DOUBLE:
390 jonathan 451
391 jonathan 460 if type == FIELDTYPE_INT:
392     conv = lambda p: int(float(p))
393     else:
394     conv = lambda p: p
395    
396 jonathan 451 #
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 jonathan 460 return (conv(Str2Num(value)),)
407     except ValueError:
408 jonathan 451 i = value.find('-')
409     if i == 0:
410     i = value.find('-', 1)
411    
412 jonathan 460 return (conv(Str2Num(value[:i])), conv(Str2Num(value[i+1:])))
413    
414     assert(False) # shouldn't get here
415 jonathan 415
416    
417     def SetValueAsCustom(self, row, col, typeName, value):
418 jonathan 460 """Set the cell specified by 'row' and 'col' to 'value'.
419 jonathan 415
420 jonathan 460 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 jonathan 415 elif col == COL_VALUE:
440 jonathan 451 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 jonathan 460 except ValueError:
450 jonathan 451 # bad input, ignore the request
451     pass
452     else:
453 jonathan 415
454 jonathan 451 ngroup = group
455     props = group.GetProperties()
456 jonathan 460
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 jonathan 451 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 jonathan 415 else:
471 jonathan 451 assert(False)
472 jonathan 415
473 jonathan 451 ngroup.SetLabel(group.GetLabel())
474 jonathan 460
475 jonathan 451 self.__SetRow(row, ngroup)
476 jonathan 415
477 jonathan 460 mod = True
478 jonathan 415
479 jonathan 460
480 jonathan 415 elif col == COL_LABEL:
481 jonathan 451 group.SetLabel(value)
482 jonathan 460 mod = True
483 jonathan 415
484 jonathan 460 if mod:
485     self.__Modified()
486     self.GetView().Refresh()
487 jonathan 415
488     def GetAttr(self, row, col, someExtraParameter):
489 jonathan 460 """Returns the cell attributes"""
490    
491 jonathan 415 attr = wxGridCellAttr()
492     #attr = wxPyGridTableBase.GetAttr(self, row, col, someExtraParameter)
493    
494 jonathan 460 if col == COL_SYMBOL:
495 jonathan 415 attr.SetRenderer(ClassRenderer(self.shapeType))
496     attr.SetReadOnly()
497    
498     return attr
499    
500 jonathan 441 def GetClassGroup(self, row):
501 jonathan 460 """Return the ClassGroup object representing row 'row'."""
502 jonathan 415
503 jonathan 460 return self.GetValueAsCustom(row, COL_SYMBOL, None)
504 jonathan 415
505 jonathan 460 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 jonathan 415 def IsModified(self):
513 jonathan 460 """True if this table is considered modified."""
514 jonathan 415 return self.modified
515    
516 jonathan 451 def DeleteRows(self, pos, numRows = 1):
517 jonathan 460 """Deletes 'numRows' beginning at row 'pos'.
518    
519     The table is considered modified after this operation.
520     """
521    
522 jonathan 451 assert(pos >= 0)
523     old_len = len(self.tdata)
524     for row in range(pos, pos - numRows, -1):
525 jonathan 460 group = self.GetValueAsCustom(row, COL_SYMBOL, None)
526 jonathan 451 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 jonathan 415
533 jonathan 451 def AppendRows(self, numRows = 1):
534 jonathan 460 """Append 'numRows' empty rows to the end of the table."""
535    
536 jonathan 451 old_len = len(self.tdata)
537     for i in range(numRows):
538     np = ClassGroupSingleton()
539 jonathan 460 self.__SetRow(-1, np)
540     #self.tdata.append([np, np.GetValue(), np.GetLabel()])
541 jonathan 451 self.__Modified()
542    
543     if self.IsModified():
544     self.__NotifyRowChanges(old_len, len(self.tdata))
545    
546    
547 jonathan 372 class Classifier(wxDialog):
548    
549     def __init__(self, parent, layer):
550 jan 374 wxDialog.__init__(self, parent, -1, _("Classify"),
551 jonathan 451 style = wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
552 jonathan 372
553 jonathan 451
554 jonathan 415 self.layer = layer
555    
556 jonathan 372 topBox = wxBoxSizer(wxVERTICAL)
557    
558 jonathan 415 topBox.Add(wxStaticText(self, -1, _("Layer: %s") % layer.Title()),
559 jonathan 451 0, wxALIGN_LEFT | wxALL, 4)
560 jonathan 415 topBox.Add(wxStaticText(self, -1, _("Type: %s") % layer.ShapeType()),
561 jonathan 451 0, wxALIGN_LEFT | wxALL, 4)
562 jonathan 415
563 jonathan 372 propertyBox = wxBoxSizer(wxHORIZONTAL)
564 jonathan 451 propertyBox.Add(wxStaticText(self, -1, _("Field: ")),
565 jonathan 372 0, wxALIGN_CENTER | wxALL, 4)
566    
567 jonathan 451 self.fields = wxComboBox(self, ID_PROPERTY_SELECT, "",
568 jonathan 372 style = wxCB_READONLY)
569    
570     self.num_cols = layer.table.field_count()
571 jonathan 441 # just assume the first field in case one hasn't been
572     # specified in the file.
573 jonathan 451 self.__cur_field = 0
574     clazz = layer.GetClassification()
575     field = clazz.GetField()
576 jonathan 460
577     self.fields.Append("<None>")
578     self.fields.SetClientData(0, None)
579    
580 jonathan 372 for i in range(self.num_cols):
581     type, name, len, decc = layer.table.field_info(i)
582 jonathan 451 self.fields.Append(name)
583    
584 jonathan 415 if name == field:
585 jonathan 460 self.__cur_field = i + 1
586     self.fields.SetClientData(i + 1, clazz)
587 jonathan 451 else:
588 jonathan 460 self.fields.SetClientData(i + 1, None)
589 jonathan 372
590 jonathan 451 self.fields.SetSelection(self.__cur_field)
591 jonathan 372
592 jonathan 451 propertyBox.Add(self.fields, 1, wxGROW|wxALL, 4)
593 jonathan 460 EVT_COMBOBOX(self, ID_PROPERTY_SELECT, self._OnFieldSelect)
594 jonathan 451
595 jonathan 376 topBox.Add(propertyBox, 0, wxGROW, 4)
596 jonathan 372
597     #
598     # Classification data table
599     #
600    
601 jonathan 415 controlBox = wxBoxSizer(wxHORIZONTAL)
602 jonathan 460 self.classGrid = ClassGrid(self)
603 jonathan 376
604 jonathan 460 self.__SetGridTable(self.__cur_field)
605    
606 jonathan 415 controlBox.Add(self.classGrid, 1, wxGROW, 0)
607 jonathan 376
608 jonathan 415 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 jonathan 372
614 jonathan 460 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 jonathan 451 controlButtonBox.Add(wxButton(self, ID_CLASSIFY_REMOVE,
620     _("Remove")), 0, wxGROW | wxALL | wxALIGN_BOTTOM, 4)
621    
622 jonathan 415 controlBox.Add(controlButtonBox, 0, wxGROW, 10)
623     topBox.Add(controlBox, 1, wxGROW, 10)
624    
625 jonathan 460 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 jonathan 415
631 jonathan 372 #
632     # Control buttons:
633     #
634     buttonBox = wxBoxSizer(wxHORIZONTAL)
635 jan 374 buttonBox.Add(wxButton(self, ID_CLASSIFY_OK, _("OK")),
636 jonathan 372 0, wxALL, 4)
637 jan 374 buttonBox.Add(wxButton(self, ID_CLASSIFY_CANCEL, _("Cancel")),
638 jonathan 372 0, wxALL, 4)
639     topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)
640    
641 jonathan 460 EVT_BUTTON(self, ID_CLASSIFY_OK, self._OnOK)
642     EVT_BUTTON(self, ID_CLASSIFY_CANCEL, self._OnCancel)
643 jonathan 372
644 jonathan 451
645    
646 jonathan 372 self.SetAutoLayout(true)
647     self.SetSizer(topBox)
648     topBox.Fit(self)
649     topBox.SetSizeHints(self)
650    
651 jonathan 460 def __BuildClassification(self, fieldIndex):
652 jonathan 415
653     clazz = Classification()
654 jonathan 460 fieldName = self.fields.GetString(fieldIndex)
655     fieldType = self.layer.GetFieldType(fieldName)
656 jonathan 415
657 jonathan 460 clazz.SetField(fieldName)
658     clazz.SetFieldType(fieldType)
659    
660 jonathan 415 numRows = self.classGrid.GetNumberRows()
661    
662 jonathan 460 assert(numRows > 0) # there should always be a default row
663 jonathan 415
664 jonathan 460 table = self.classGrid.GetTable()
665     clazz.SetDefaultGroup(table.GetClassGroup(0))
666 jonathan 415
667 jonathan 460 for i in range(1, numRows):
668     clazz.AddGroup(table.GetClassGroup(i))
669    
670 jonathan 415 return clazz
671    
672 jonathan 460 def __SetGridTable(self, fieldIndex):
673 jonathan 415
674 jonathan 460 clazz = self.fields.GetClientData(fieldIndex)
675 jonathan 415
676 jonathan 460 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 jonathan 451 self.__cur_field = self.fields.GetSelection()
693 jonathan 460 self.__SetGridTable(self.__cur_field)
694 jonathan 415
695 jonathan 460 def _OnOK(self, event):
696 jonathan 415 """Put the data from the table into a new Classification and hand
697     it to the layer.
698     """
699    
700 jonathan 460 clazz = self.fields.GetClientData(self.__cur_field)
701 jonathan 415
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 jonathan 451 clazz = self.__BuildClassification(self.__cur_field)
708 jonathan 415
709     clazz.SetLayer(self.layer)
710    
711     self.layer.SetClassification(clazz)
712    
713     self.EndModal(wxID_OK)
714    
715 jonathan 460 def _OnCancel(self, event):
716 jonathan 415 """Do nothing. The layer's current classification stays the same."""
717     self.EndModal(wxID_CANCEL)
718    
719 jonathan 460 def _OnAdd(self, event):
720 jonathan 451 self.classGrid.AppendRows()
721 jonathan 415
722 jonathan 460 def _OnRemove(self, event):
723 jonathan 451 self.classGrid.DeleteSelectedRows()
724    
725 jonathan 460 def _OnGenRange(self, event):
726     print "Classifier._OnGenRange()"
727 jonathan 415
728 jonathan 460 def _OnMoveUp(self, event):
729     sel = self.classGrid.GetCurrentSelection()
730 jonathan 415
731 jonathan 460 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 jonathan 415 ID_SELPROP_OK = 4001
758     ID_SELPROP_CANCEL = 4002
759     ID_SELPROP_SPINCTRL = 4002
760 jonathan 430 ID_SELPROP_PREVIEW = 4003
761     ID_SELPROP_STROKECLR = 4004
762     ID_SELPROP_FILLCLR = 4005
763 jonathan 415
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 jonathan 441 self.prop = ClassGroupProperties(prop)
771 jonathan 415
772 jonathan 430 topBox = wxBoxSizer(wxVERTICAL)
773 jonathan 415
774 jonathan 430 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 jonathan 460 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 jonathan 430
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 jonathan 460 EVT_BUTTON(self, ID_SELPROP_FILLCLR, self._OnChangeFillColor)
798 jonathan 430
799 jonathan 415 spinBox = wxBoxSizer(wxHORIZONTAL)
800 jonathan 460 spinBox.Add(wxStaticText(self, -1, _("Line Width: ")),
801 jonathan 430 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
802 jonathan 415 self.spinCtrl = wxSpinCtrl(self, ID_SELPROP_SPINCTRL,
803     min=1, max=10,
804 jonathan 460 value=str(prop.GetLineWidth()),
805     initial=prop.GetLineWidth())
806 jonathan 415
807 jonathan 460 EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL, self._OnSpin)
808 jonathan 415
809     spinBox.Add(self.spinCtrl, 0, wxALIGN_LEFT | wxALL, 4)
810    
811 jonathan 430 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 jonathan 415
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 jonathan 460 EVT_BUTTON(self, ID_SELPROP_OK, self._OnOK)
827     EVT_BUTTON(self, ID_SELPROP_CANCEL, self._OnCancel)
828 jonathan 415
829     self.SetAutoLayout(true)
830     self.SetSizer(topBox)
831     topBox.Fit(self)
832     topBox.SetSizeHints(self)
833    
834 jonathan 460 def _OnOK(self, event):
835 jonathan 372 self.EndModal(wxID_OK)
836    
837 jonathan 460 def _OnCancel(self, event):
838 jonathan 372 self.EndModal(wxID_CANCEL)
839    
840 jonathan 460 def _OnSpin(self, event):
841     self.prop.SetLineWidth(self.spinCtrl.GetValue())
842 jonathan 430 self.previewer.Refresh()
843 jonathan 392
844 jonathan 430 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 jonathan 460 def _OnChangeLineColor(self, event):
856     clr = self.__GetColor(self.prop.GetLineColor())
857 jonathan 430 if clr is not None:
858 jonathan 460 self.prop.SetLineColor(clr)
859 jonathan 430 self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
860    
861 jonathan 460 def _OnChangeFillColor(self, event):
862 jonathan 430 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 jonathan 441 def GetClassGroupProperties(self):
868 jonathan 415 return self.prop
869 jonathan 392
870    
871 jonathan 430 class ClassDataPreviewer(wxWindow):
872 jonathan 415
873 jonathan 441 def __init__(self, rect, prop, shapeType,
874 jonathan 430 parent = None, id = -1, size = wxDefaultSize):
875     if parent is not None:
876     wxWindow.__init__(self, parent, id, size=size)
877 jonathan 460 EVT_PAINT(self, self._OnPaint)
878 jonathan 415
879 jonathan 430 self.rect = rect
880 jonathan 441 self.prop = prop
881 jonathan 430 self.shapeType = shapeType
882    
883 jonathan 460 def _OnPaint(self, event):
884 jonathan 430 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 jonathan 441 def Draw(self, dc, rect, prop = None, shapeType = None):
892 jonathan 430
893 jonathan 441 if prop is None: prop = self.prop
894 jonathan 430 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 jonathan 460 stroke = prop.GetLineColor()
906 jonathan 415 if stroke is Color.None:
907 jonathan 392 pen = wxTRANSPARENT_PEN
908     else:
909 jonathan 430 pen = wxPen(Color2wxColour(stroke),
910 jonathan 460 prop.GetLineWidth(),
911 jonathan 392 wxSOLID)
912    
913 jonathan 441 stroke = prop.GetFill()
914 jonathan 415 if stroke is Color.None:
915 jonathan 392 brush = wxTRANSPARENT_BRUSH
916     else:
917 jonathan 430 brush = wxBrush(Color2wxColour(stroke), wxSOLID)
918 jonathan 392
919     dc.SetPen(pen)
920     dc.SetBrush(brush)
921    
922 jonathan 415 if shapeType == SHAPETYPE_ARC:
923 jonathan 430 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 jonathan 392
928 jonathan 415 elif shapeType == SHAPETYPE_POINT or \
929     shapeType == SHAPETYPE_POLYGON:
930    
931 jonathan 430 dc.DrawCircle(x + w/2, y + h/2,
932 jonathan 460 (min(w, h) - prop.GetLineWidth())/2)
933 jonathan 392
934 jonathan 415 class ClassRenderer(wxPyGridCellRenderer):
935    
936     def __init__(self, shapeType):
937     wxPyGridCellRenderer.__init__(self)
938 jonathan 430 self.previewer = ClassDataPreviewer(None, None, shapeType)
939 jonathan 415
940     def Draw(self, grid, attr, dc, rect, row, col, isSelected):
941 jonathan 451 data = grid.GetTable().GetValueAsCustom(row, col, None)
942 jonathan 415
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 jonathan 441 if not isinstance(data, ClassGroupMap):
951     self.previewer.Draw(dc, rect, data.GetProperties())
952 jonathan 415
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 jonathan 392 dc.DestroyClippingRegion()
961    

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26