/[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 509 - (show annotations)
Tue Mar 11 09:45:59 2003 UTC (21 years, 11 months ago) by jonathan
File MIME type: text/x-python
File size: 35928 byte(s)
Working to get wxPanel to behave correctly.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26