/[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 498 - (show annotations)
Mon Mar 10 11:03:22 2003 UTC (21 years, 11 months ago) by jonathan
File MIME type: text/x-python
File size: 35810 byte(s)
(Classifier): SelectField() needed
        to know the old field index as well as the new one.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26