/[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 489 - (show annotations)
Fri Mar 7 18:22:47 2003 UTC (22 years ago) by jonathan
File MIME type: text/x-python
File size: 35552 byte(s)
removed print statement

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 self.fields.SetSelection(self.__cur_field)
627
628 #
629 #
630 #
631
632 self.fieldTypeText = wxStaticText(self, -1, "")
633 self.__SetFieldTypeText(self.__cur_field)
634
635 topBox.Add(self.fieldTypeText, 0, wxALIGN_LEFT | wxALL, 4)
636 #self.fieldTypeText.SetLabel("asdfadsfs")
637
638 propertyBox = wxBoxSizer(wxHORIZONTAL)
639 propertyBox.Add(wxStaticText(self, -1, _("Field: ")),
640 0, wxALIGN_CENTER | wxALL, 4)
641 propertyBox.Add(self.fields, 1, wxGROW|wxALL, 4)
642 EVT_COMBOBOX(self, ID_PROPERTY_SELECT, self._OnFieldSelect)
643
644 topBox.Add(propertyBox, 0, wxGROW, 4)
645
646 #
647 # Classification data table
648 #
649
650 controlBox = wxBoxSizer(wxHORIZONTAL)
651
652 self.classGrid = ClassGrid(self)
653 self.__SetGridTable(self.__cur_field)
654
655 controlBox.Add(self.classGrid, 1, wxGROW, 0)
656
657 controlButtonBox = wxBoxSizer(wxVERTICAL)
658
659 #
660 # Control buttons:
661 #
662 self.controlButtons = []
663
664 button = wxButton(self, ID_CLASSIFY_ADD, _("Add"))
665 controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
666 self.controlButtons.append(button)
667
668 #button = wxButton(self, ID_CLASSIFY_GENRANGE, _("Generate Ranges"))
669 #controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
670 #self.controlButtons.append(button)
671
672 button = wxButton(self, ID_CLASSIFY_MOVEUP, _("Move Up"))
673 controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
674 self.controlButtons.append(button)
675
676 button = wxButton(self, 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(self, 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 topBox.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 buttonBox = wxBoxSizer(wxHORIZONTAL)
696 buttonBox.Add(wxButton(self, ID_CLASSIFY_OK, _("OK")),
697 0, wxALL, 4)
698 buttonBox.Add(60, 20, 0, wxALL, 4)
699 buttonBox.Add(wxButton(self, ID_CLASSIFY_APPLY, _("Apply")),
700 0, wxALL, 4)
701 buttonBox.Add(60, 20, 0, wxALL, 4)
702 buttonBox.Add(wxButton(self, ID_CLASSIFY_CANCEL, _("Cancel")),
703 0, wxALL, 4)
704 topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)
705
706 EVT_BUTTON(self, ID_CLASSIFY_OK, self._OnOK)
707 EVT_BUTTON(self, ID_CLASSIFY_APPLY, self._OnApply)
708 EVT_BUTTON(self, ID_CLASSIFY_CANCEL, self._OnCancel)
709
710 self.SetAutoLayout(true)
711 self.SetSizer(topBox)
712 topBox.Fit(self)
713 topBox.SetSizeHints(self)
714
715
716 def __BuildClassification(self, fieldIndex):
717
718 clazz = Classification()
719 fieldName = self.fields.GetString(fieldIndex)
720 fieldType = self.layer.GetFieldType(fieldName)
721
722 clazz.SetField(fieldName)
723 clazz.SetFieldType(fieldType)
724
725 numRows = self.classGrid.GetNumberRows()
726
727 assert(numRows > 0) # there should always be a default row
728
729 table = self.classGrid.GetTable()
730 clazz.SetDefaultGroup(table.GetClassGroup(0))
731
732 for i in range(1, numRows):
733 clazz.AddGroup(table.GetClassGroup(i))
734
735 return clazz
736
737 def __SetGridTable(self, fieldIndex):
738
739 clazz = self.fields.GetClientData(fieldIndex)
740
741 if clazz is None:
742 clazz = Classification()
743 clazz.SetDefaultGroup(
744 ClassGroupDefault(
745 self.layer.GetClassification().
746 GetDefaultGroup().GetProperties()))
747
748 fieldName = self.fields.GetString(fieldIndex)
749 fieldType = self.layer.GetFieldType(fieldName)
750 clazz.SetFieldType(fieldType)
751
752 self.classGrid.CreateTable(clazz, self.layer.ShapeType())
753
754 def __SetFieldTypeText(self, fieldIndex):
755 fieldName = self.fields.GetString(fieldIndex)
756 fieldType = self.layer.GetFieldType(fieldName)
757
758 if fieldType is None:
759 text = "None"
760 elif fieldType == FIELDTYPE_STRING:
761 text = "Text"
762 elif fieldType == FIELDTYPE_INT:
763 text = "Integer"
764 elif fieldType == FIELDTYPE_DOUBLE:
765 text = "Decimal" # Rational?
766 else:
767 assert(False)
768 text = "UNKNOWN"
769
770 self.fieldTypeText.SetLabel(_("Field Type: %s") % text)
771
772 def _OnFieldSelect(self, event):
773 clazz = self.__BuildClassification(self.__cur_field)
774 self.fields.SetClientData(self.__cur_field, clazz)
775
776 self.__cur_field = self.fields.GetSelection()
777 self.__SetGridTable(self.__cur_field)
778
779 enabled = self.__cur_field != 0
780
781 for b in self.controlButtons:
782 b.Enable(enabled)
783
784 self.__SetFieldTypeText(self.__cur_field)
785
786
787 def _OnApply(self, event):
788 """Put the data from the table into a new Classification and hand
789 it to the layer.
790 """
791
792 clazz = self.fields.GetClientData(self.__cur_field)
793
794 #
795 # only build the classification if there wasn't one to
796 # to begin with or it has been modified
797 #
798 if clazz is None or self.classGrid.GetTable().IsModified():
799 clazz = self.__BuildClassification(self.__cur_field)
800
801 self.layer.SetClassification(clazz)
802
803 def _OnOK(self, event):
804 self._OnApply(event)
805 self.OnClose(event)
806
807 def _OnCancel(self, event):
808 """The layer's current classification stays the same."""
809 self.layer.SetClassification(self.originalClass)
810 self.OnClose(event)
811
812 def _OnAdd(self, event):
813 self.classGrid.AppendRows()
814
815 def _OnRemove(self, event):
816 self.classGrid.DeleteSelectedRows()
817
818 def _OnGenRange(self, event):
819 print "Classifier._OnGenRange()"
820
821 def _OnMoveUp(self, event):
822 sel = self.classGrid.GetCurrentSelection()
823
824 if len(sel) == 1:
825 i = sel[0]
826 if i > 1:
827 table = self.classGrid.GetTable()
828 x = table.GetClassGroup(i - 1)
829 y = table.GetClassGroup(i)
830 table.SetClassGroup(i - 1, y)
831 table.SetClassGroup(i, x)
832 self.classGrid.ClearSelection()
833 self.classGrid.SelectRow(i - 1)
834
835 def _OnMoveDown(self, event):
836 sel = self.classGrid.GetCurrentSelection()
837
838 if len(sel) == 1:
839 i = sel[0]
840 table = self.classGrid.GetTable()
841 if 0 < i < table.GetNumberRows() - 1:
842 x = table.GetClassGroup(i)
843 y = table.GetClassGroup(i + 1)
844 table.SetClassGroup(i, y)
845 table.SetClassGroup(i + 1, x)
846 self.classGrid.ClearSelection()
847 self.classGrid.SelectRow(i + 1)
848
849
850 ID_SELPROP_OK = 4001
851 ID_SELPROP_CANCEL = 4002
852 ID_SELPROP_SPINCTRL = 4002
853 ID_SELPROP_PREVIEW = 4003
854 ID_SELPROP_STROKECLR = 4004
855 ID_SELPROP_FILLCLR = 4005
856 ID_SELPROP_STROKECLRTRANS = 4006
857 ID_SELPROP_FILLCLRTRANS = 4007
858
859 class SelectPropertiesDialog(wxDialog):
860
861 def __init__(self, parent, prop, shapeType):
862 wxDialog.__init__(self, parent, -1, _("Select Properties"),
863 style = wxRESIZE_BORDER)
864
865 self.prop = ClassGroupProperties(prop)
866
867 topBox = wxBoxSizer(wxVERTICAL)
868
869 itemBox = wxBoxSizer(wxHORIZONTAL)
870
871 # preview box
872 previewBox = wxBoxSizer(wxVERTICAL)
873 previewBox.Add(wxStaticText(self, -1, _("Preview:")),
874 0, wxALIGN_LEFT | wxALL, 4)
875 self.previewer = ClassDataPreviewer(None, self.prop, shapeType,
876 self, ID_SELPROP_PREVIEW, (40, 40))
877 previewBox.Add(self.previewer, 1, wxGROW, 15)
878
879 itemBox.Add(previewBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
880
881 # control box
882 ctrlBox = wxBoxSizer(wxVERTICAL)
883
884 lineColorBox = wxBoxSizer(wxHORIZONTAL)
885 lineColorBox.Add(
886 wxButton(self, ID_SELPROP_STROKECLR, "Change Line Color"),
887 1, wxALL | wxGROW, 4)
888 EVT_BUTTON(self, ID_SELPROP_STROKECLR, self._OnChangeLineColor)
889
890 lineColorBox.Add(
891 wxButton(self, ID_SELPROP_STROKECLRTRANS, "Transparent"),
892 1, wxALL | wxGROW, 4)
893 EVT_BUTTON(self, ID_SELPROP_STROKECLRTRANS,
894 self._OnChangeLineColorTrans)
895
896 ctrlBox.Add(lineColorBox, 0,
897 wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
898
899 if shapeType != SHAPETYPE_ARC:
900 fillColorBox = wxBoxSizer(wxHORIZONTAL)
901 fillColorBox.Add(
902 wxButton(self, ID_SELPROP_FILLCLR, "Change Fill Color"),
903 1, wxALL | wxGROW, 4)
904 EVT_BUTTON(self, ID_SELPROP_FILLCLR, self._OnChangeFillColor)
905 fillColorBox.Add(
906 wxButton(self, ID_SELPROP_FILLCLRTRANS, "Transparent"),
907 1, wxALL | wxGROW, 4)
908 EVT_BUTTON(self, ID_SELPROP_FILLCLRTRANS,
909 self._OnChangeFillColorTrans)
910 ctrlBox.Add(fillColorBox, 0,
911 wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
912
913 spinBox = wxBoxSizer(wxHORIZONTAL)
914 spinBox.Add(wxStaticText(self, -1, _("Line Width: ")),
915 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
916 self.spinCtrl = wxSpinCtrl(self, ID_SELPROP_SPINCTRL,
917 min=1, max=10,
918 value=str(prop.GetLineWidth()),
919 initial=prop.GetLineWidth())
920
921 EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL, self._OnSpin)
922
923 spinBox.Add(self.spinCtrl, 0, wxALIGN_LEFT | wxALL, 4)
924
925 ctrlBox.Add(spinBox, 0, wxALIGN_RIGHT | wxALL, 0)
926 itemBox.Add(ctrlBox, 0, wxALIGN_RIGHT | wxALL | wxGROW, 0)
927 topBox.Add(itemBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
928
929 #
930 # Control buttons:
931 #
932 buttonBox = wxBoxSizer(wxHORIZONTAL)
933 buttonBox.Add(wxButton(self, ID_SELPROP_OK, _("OK")),
934 0, wxALL, 4)
935 buttonBox.Add(wxButton(self, ID_SELPROP_CANCEL, _("Cancel")),
936 0, wxALL, 4)
937 topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)
938
939 EVT_BUTTON(self, ID_SELPROP_OK, self._OnOK)
940 EVT_BUTTON(self, ID_SELPROP_CANCEL, self._OnCancel)
941
942 self.SetAutoLayout(true)
943 self.SetSizer(topBox)
944 topBox.Fit(self)
945 topBox.SetSizeHints(self)
946
947 def _OnOK(self, event):
948 self.EndModal(wxID_OK)
949
950 def _OnCancel(self, event):
951 self.EndModal(wxID_CANCEL)
952
953 def _OnSpin(self, event):
954 self.prop.SetLineWidth(self.spinCtrl.GetValue())
955 self.previewer.Refresh()
956
957 def __GetColor(self, cur):
958 dialog = wxColourDialog(self)
959 dialog.GetColourData().SetColour(Color2wxColour(cur))
960 ret = None
961 if dialog.ShowModal() == wxID_OK:
962 ret = wxColour2Color(dialog.GetColourData().GetColour())
963
964 dialog.Destroy()
965
966 return ret
967
968 def _OnChangeLineColor(self, event):
969 clr = self.__GetColor(self.prop.GetLineColor())
970 if clr is not None:
971 self.prop.SetLineColor(clr)
972 self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
973
974 def _OnChangeLineColorTrans(self, event):
975 self.prop.SetLineColor(Color.None)
976 self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
977
978 def _OnChangeFillColor(self, event):
979 clr = self.__GetColor(self.prop.GetFill())
980 if clr is not None:
981 self.prop.SetFill(clr)
982 self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
983
984 def _OnChangeFillColorTrans(self, event):
985 self.prop.SetFill(Color.None)
986 self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
987
988 def GetClassGroupProperties(self):
989 return self.prop
990
991
992 class ClassDataPreviewer(wxWindow):
993
994 def __init__(self, rect, prop, shapeType,
995 parent = None, id = -1, size = wxDefaultSize):
996 if parent is not None:
997 wxWindow.__init__(self, parent, id, size=size)
998 EVT_PAINT(self, self._OnPaint)
999
1000 self.rect = rect
1001 self.prop = prop
1002 self.shapeType = shapeType
1003
1004 def _OnPaint(self, event):
1005 dc = wxPaintDC(self)
1006
1007 # XXX: this doesn't seem to be having an effect:
1008 dc.DestroyClippingRegion()
1009
1010 self.Draw(dc, None)
1011
1012 def Draw(self, dc, rect, prop = None, shapeType = None):
1013
1014 if prop is None: prop = self.prop
1015 if shapeType is None: shapeType = self.shapeType
1016
1017 if rect is None:
1018 x = y = 0
1019 w, h = self.GetClientSizeTuple()
1020 else:
1021 x = rect.GetX()
1022 y = rect.GetY()
1023 w = rect.GetWidth()
1024 h = rect.GetHeight()
1025
1026 stroke = prop.GetLineColor()
1027 if stroke is Color.None:
1028 pen = wxTRANSPARENT_PEN
1029 else:
1030 pen = wxPen(Color2wxColour(stroke),
1031 prop.GetLineWidth(),
1032 wxSOLID)
1033
1034 stroke = prop.GetFill()
1035 if stroke is Color.None:
1036 brush = wxTRANSPARENT_BRUSH
1037 else:
1038 brush = wxBrush(Color2wxColour(stroke), wxSOLID)
1039
1040 dc.SetPen(pen)
1041 dc.SetBrush(brush)
1042
1043 if shapeType == SHAPETYPE_ARC:
1044 dc.DrawSpline([wxPoint(x, y + h),
1045 wxPoint(x + w/2, y + h/4),
1046 wxPoint(x + w/2, y + h/4*3),
1047 wxPoint(x + w, y)])
1048
1049 elif shapeType == SHAPETYPE_POINT or \
1050 shapeType == SHAPETYPE_POLYGON:
1051
1052 dc.DrawCircle(x + w/2, y + h/2,
1053 (min(w, h) - prop.GetLineWidth())/2)
1054
1055 class ClassRenderer(wxPyGridCellRenderer):
1056
1057 def __init__(self, shapeType):
1058 wxPyGridCellRenderer.__init__(self)
1059 self.previewer = ClassDataPreviewer(None, None, shapeType)
1060
1061 def Draw(self, grid, attr, dc, rect, row, col, isSelected):
1062 data = grid.GetTable().GetClassGroup(row)
1063
1064 dc.SetClippingRegion(rect.GetX(), rect.GetY(),
1065 rect.GetWidth(), rect.GetHeight())
1066 dc.SetPen(wxPen(wxLIGHT_GREY))
1067 dc.SetBrush(wxBrush(wxLIGHT_GREY, wxSOLID))
1068 dc.DrawRectangle(rect.GetX(), rect.GetY(),
1069 rect.GetWidth(), rect.GetHeight())
1070
1071 if not isinstance(data, ClassGroupMap):
1072 self.previewer.Draw(dc, rect, data.GetProperties())
1073
1074 if isSelected:
1075 dc.SetPen(wxPen(wxColour(0 * 255, 0 * 255, 0 * 255),
1076 4, wxSOLID))
1077 dc.SetBrush(wxTRANSPARENT_BRUSH)
1078 dc.DrawRectangle(rect.GetX(), rect.GetY(),
1079 rect.GetWidth(), rect.GetHeight())
1080
1081 dc.DestroyClippingRegion()
1082

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26