/[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 496 - (show annotations)
Mon Mar 10 10:54:50 2003 UTC (22 years ago) by jonathan
File MIME type: text/x-python
File size: 35710 byte(s)
(Classifier): Use __SelectField()
        to correctly set the table information and call this from
        __init__ and from _OnFieldSelect so that all the information
        is up to date when the dialog opens and when a field is changed.

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, fieldIndex):
778 clazz = self.__BuildClassification(fieldIndex)
779 self.fields.SetClientData(fieldIndex, clazz)
780
781 self.__cur_field = self.fields.GetSelection()
782 self.__SetGridTable(fieldIndex)
783
784 enabled = fieldIndex != 0
785
786 for b in self.controlButtons:
787 b.Enable(enabled)
788
789 self.__SetFieldTypeText(fieldIndex)
790
791
792 def _OnFieldSelect(self, event):
793 self.__SelectField(self.__cur_field)
794
795 def _OnApply(self, event):
796 """Put the data from the table into a new Classification and hand
797 it to the layer.
798 """
799
800 clazz = self.fields.GetClientData(self.__cur_field)
801
802 #
803 # only build the classification if there wasn't one to
804 # to begin with or it has been modified
805 #
806 if clazz is None or self.classGrid.GetTable().IsModified():
807 clazz = self.__BuildClassification(self.__cur_field)
808
809 self.layer.SetClassification(clazz)
810
811 def _OnOK(self, event):
812 self._OnApply(event)
813 self.OnClose(event)
814
815 def _OnCancel(self, event):
816 """The layer's current classification stays the same."""
817 self.layer.SetClassification(self.originalClass)
818 self.OnClose(event)
819
820 def _OnAdd(self, event):
821 self.classGrid.AppendRows()
822
823 def _OnRemove(self, event):
824 self.classGrid.DeleteSelectedRows()
825
826 def _OnGenRange(self, event):
827 print "Classifier._OnGenRange()"
828
829 def _OnMoveUp(self, event):
830 sel = self.classGrid.GetCurrentSelection()
831
832 if len(sel) == 1:
833 i = sel[0]
834 if i > 1:
835 table = self.classGrid.GetTable()
836 x = table.GetClassGroup(i - 1)
837 y = table.GetClassGroup(i)
838 table.SetClassGroup(i - 1, y)
839 table.SetClassGroup(i, x)
840 self.classGrid.ClearSelection()
841 self.classGrid.SelectRow(i - 1)
842
843 def _OnMoveDown(self, event):
844 sel = self.classGrid.GetCurrentSelection()
845
846 if len(sel) == 1:
847 i = sel[0]
848 table = self.classGrid.GetTable()
849 if 0 < i < table.GetNumberRows() - 1:
850 x = table.GetClassGroup(i)
851 y = table.GetClassGroup(i + 1)
852 table.SetClassGroup(i, y)
853 table.SetClassGroup(i + 1, x)
854 self.classGrid.ClearSelection()
855 self.classGrid.SelectRow(i + 1)
856
857
858 ID_SELPROP_OK = 4001
859 ID_SELPROP_CANCEL = 4002
860 ID_SELPROP_SPINCTRL = 4002
861 ID_SELPROP_PREVIEW = 4003
862 ID_SELPROP_STROKECLR = 4004
863 ID_SELPROP_FILLCLR = 4005
864 ID_SELPROP_STROKECLRTRANS = 4006
865 ID_SELPROP_FILLCLRTRANS = 4007
866
867 class SelectPropertiesDialog(wxDialog):
868
869 def __init__(self, parent, prop, shapeType):
870 wxDialog.__init__(self, parent, -1, _("Select Properties"),
871 style = wxRESIZE_BORDER)
872
873 self.prop = ClassGroupProperties(prop)
874
875 topBox = wxBoxSizer(wxVERTICAL)
876
877 itemBox = wxBoxSizer(wxHORIZONTAL)
878
879 # preview box
880 previewBox = wxBoxSizer(wxVERTICAL)
881 previewBox.Add(wxStaticText(self, -1, _("Preview:")),
882 0, wxALIGN_LEFT | wxALL, 4)
883 self.previewer = ClassDataPreviewer(None, self.prop, shapeType,
884 self, ID_SELPROP_PREVIEW, (40, 40))
885 previewBox.Add(self.previewer, 1, wxGROW, 15)
886
887 itemBox.Add(previewBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
888
889 # control box
890 ctrlBox = wxBoxSizer(wxVERTICAL)
891
892 lineColorBox = wxBoxSizer(wxHORIZONTAL)
893 lineColorBox.Add(
894 wxButton(self, ID_SELPROP_STROKECLR, "Change Line Color"),
895 1, wxALL | wxGROW, 4)
896 EVT_BUTTON(self, ID_SELPROP_STROKECLR, self._OnChangeLineColor)
897
898 lineColorBox.Add(
899 wxButton(self, ID_SELPROP_STROKECLRTRANS, "Transparent"),
900 1, wxALL | wxGROW, 4)
901 EVT_BUTTON(self, ID_SELPROP_STROKECLRTRANS,
902 self._OnChangeLineColorTrans)
903
904 ctrlBox.Add(lineColorBox, 0,
905 wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
906
907 if shapeType != SHAPETYPE_ARC:
908 fillColorBox = wxBoxSizer(wxHORIZONTAL)
909 fillColorBox.Add(
910 wxButton(self, ID_SELPROP_FILLCLR, "Change Fill Color"),
911 1, wxALL | wxGROW, 4)
912 EVT_BUTTON(self, ID_SELPROP_FILLCLR, self._OnChangeFillColor)
913 fillColorBox.Add(
914 wxButton(self, ID_SELPROP_FILLCLRTRANS, "Transparent"),
915 1, wxALL | wxGROW, 4)
916 EVT_BUTTON(self, ID_SELPROP_FILLCLRTRANS,
917 self._OnChangeFillColorTrans)
918 ctrlBox.Add(fillColorBox, 0,
919 wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
920
921 spinBox = wxBoxSizer(wxHORIZONTAL)
922 spinBox.Add(wxStaticText(self, -1, _("Line Width: ")),
923 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
924 self.spinCtrl = wxSpinCtrl(self, ID_SELPROP_SPINCTRL,
925 min=1, max=10,
926 value=str(prop.GetLineWidth()),
927 initial=prop.GetLineWidth())
928
929 EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL, self._OnSpin)
930
931 spinBox.Add(self.spinCtrl, 0, wxALIGN_LEFT | wxALL, 4)
932
933 ctrlBox.Add(spinBox, 0, wxALIGN_RIGHT | wxALL, 0)
934 itemBox.Add(ctrlBox, 0, wxALIGN_RIGHT | wxALL | wxGROW, 0)
935 topBox.Add(itemBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
936
937 #
938 # Control buttons:
939 #
940 buttonBox = wxBoxSizer(wxHORIZONTAL)
941 buttonBox.Add(wxButton(self, ID_SELPROP_OK, _("OK")),
942 0, wxALL, 4)
943 buttonBox.Add(wxButton(self, ID_SELPROP_CANCEL, _("Cancel")),
944 0, wxALL, 4)
945 topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)
946
947 EVT_BUTTON(self, ID_SELPROP_OK, self._OnOK)
948 EVT_BUTTON(self, ID_SELPROP_CANCEL, self._OnCancel)
949
950 self.SetAutoLayout(true)
951 self.SetSizer(topBox)
952 topBox.Fit(self)
953 topBox.SetSizeHints(self)
954
955 def _OnOK(self, event):
956 self.EndModal(wxID_OK)
957
958 def _OnCancel(self, event):
959 self.EndModal(wxID_CANCEL)
960
961 def _OnSpin(self, event):
962 self.prop.SetLineWidth(self.spinCtrl.GetValue())
963 self.previewer.Refresh()
964
965 def __GetColor(self, cur):
966 dialog = wxColourDialog(self)
967 dialog.GetColourData().SetColour(Color2wxColour(cur))
968 ret = None
969 if dialog.ShowModal() == wxID_OK:
970 ret = wxColour2Color(dialog.GetColourData().GetColour())
971
972 dialog.Destroy()
973
974 return ret
975
976 def _OnChangeLineColor(self, event):
977 clr = self.__GetColor(self.prop.GetLineColor())
978 if clr is not None:
979 self.prop.SetLineColor(clr)
980 self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
981
982 def _OnChangeLineColorTrans(self, event):
983 self.prop.SetLineColor(Color.None)
984 self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
985
986 def _OnChangeFillColor(self, event):
987 clr = self.__GetColor(self.prop.GetFill())
988 if clr is not None:
989 self.prop.SetFill(clr)
990 self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
991
992 def _OnChangeFillColorTrans(self, event):
993 self.prop.SetFill(Color.None)
994 self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
995
996 def GetClassGroupProperties(self):
997 return self.prop
998
999
1000 class ClassDataPreviewer(wxWindow):
1001
1002 def __init__(self, rect, prop, shapeType,
1003 parent = None, id = -1, size = wxDefaultSize):
1004 if parent is not None:
1005 wxWindow.__init__(self, parent, id, size=size)
1006 EVT_PAINT(self, self._OnPaint)
1007
1008 self.rect = rect
1009 self.prop = prop
1010 self.shapeType = shapeType
1011
1012 def _OnPaint(self, event):
1013 dc = wxPaintDC(self)
1014
1015 # XXX: this doesn't seem to be having an effect:
1016 dc.DestroyClippingRegion()
1017
1018 self.Draw(dc, None)
1019
1020 def Draw(self, dc, rect, prop = None, shapeType = None):
1021
1022 if prop is None: prop = self.prop
1023 if shapeType is None: shapeType = self.shapeType
1024
1025 if rect is None:
1026 x = y = 0
1027 w, h = self.GetClientSizeTuple()
1028 else:
1029 x = rect.GetX()
1030 y = rect.GetY()
1031 w = rect.GetWidth()
1032 h = rect.GetHeight()
1033
1034 stroke = prop.GetLineColor()
1035 if stroke is Color.None:
1036 pen = wxTRANSPARENT_PEN
1037 else:
1038 pen = wxPen(Color2wxColour(stroke),
1039 prop.GetLineWidth(),
1040 wxSOLID)
1041
1042 stroke = prop.GetFill()
1043 if stroke is Color.None:
1044 brush = wxTRANSPARENT_BRUSH
1045 else:
1046 brush = wxBrush(Color2wxColour(stroke), wxSOLID)
1047
1048 dc.SetPen(pen)
1049 dc.SetBrush(brush)
1050
1051 if shapeType == SHAPETYPE_ARC:
1052 dc.DrawSpline([wxPoint(x, y + h),
1053 wxPoint(x + w/2, y + h/4),
1054 wxPoint(x + w/2, y + h/4*3),
1055 wxPoint(x + w, y)])
1056
1057 elif shapeType == SHAPETYPE_POINT or \
1058 shapeType == SHAPETYPE_POLYGON:
1059
1060 dc.DrawCircle(x + w/2, y + h/2,
1061 (min(w, h) - prop.GetLineWidth())/2)
1062
1063 class ClassRenderer(wxPyGridCellRenderer):
1064
1065 def __init__(self, shapeType):
1066 wxPyGridCellRenderer.__init__(self)
1067 self.previewer = ClassDataPreviewer(None, None, shapeType)
1068
1069 def Draw(self, grid, attr, dc, rect, row, col, isSelected):
1070 data = grid.GetTable().GetClassGroup(row)
1071
1072 dc.SetClippingRegion(rect.GetX(), rect.GetY(),
1073 rect.GetWidth(), rect.GetHeight())
1074 dc.SetPen(wxPen(wxLIGHT_GREY))
1075 dc.SetBrush(wxBrush(wxLIGHT_GREY, wxSOLID))
1076 dc.DrawRectangle(rect.GetX(), rect.GetY(),
1077 rect.GetWidth(), rect.GetHeight())
1078
1079 if not isinstance(data, ClassGroupMap):
1080 self.previewer.Draw(dc, rect, data.GetProperties())
1081
1082 if isSelected:
1083 dc.SetPen(wxPen(wxColour(0 * 255, 0 * 255, 0 * 255),
1084 4, wxSOLID))
1085 dc.SetBrush(wxTRANSPARENT_BRUSH)
1086 dc.DrawRectangle(rect.GetX(), rect.GetY(),
1087 rect.GetWidth(), rect.GetHeight())
1088
1089 dc.DestroyClippingRegion()
1090

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26