/[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 506 - (hide annotations)
Mon Mar 10 15:49:22 2003 UTC (21 years, 11 months ago) by jonathan
File MIME type: text/x-python
File size: 35726 byte(s)
(Classifier.__init__): Make the
        field type label grow so that when the text changes the
        size is updated correctly. This may be a wxWindows bug.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26