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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 519 - (show annotations)
Tue Mar 11 22:27:35 2003 UTC (21 years, 11 months ago) by jonathan
File MIME type: text/x-python
File size: 36134 byte(s)
Undo last changes so that we now need wxWindows2.4

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26