/[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 506 - (show annotations)
Mon Mar 10 15:49:22 2003 UTC (21 years, 11 months ago) by jonathan
File MIME type: text/x-python
File size: 35726 byte(s)
(Classifier.__init__): Make the
        field type label grow so that when the text changes the
        size is updated correctly. This may be a wxWindows bug.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26