/[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 473 - (hide annotations)
Wed Mar 5 18:39:45 2003 UTC (22 years ago) by jonathan
File MIME type: text/x-python
File size: 31255 byte(s)
import FIELDTYPE constants from table

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26