/[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 500 - (show annotations)
Mon Mar 10 15:11:24 2003 UTC (21 years, 11 months ago) by jonathan
File MIME type: text/x-python
File size: 35820 byte(s)
Wrap text with _().
(ClassGrid.CreateTable): Set dimensions and size hints here,
        instead of in Reset, so we only set the size once.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26