/[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 560 - (show annotations)
Wed Mar 26 11:05:47 2003 UTC (21 years, 11 months ago) by jonathan
File MIME type: text/x-python
File size: 36558 byte(s)
(Classifier.__init__): Use wxADJUST_MINSIZE
        as one of the style properties for the fieldTypeText item to
        be sure that its size is correct when the text changes.

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 self.GetView().FitInside()
300 elif newRows < curRows:
301 msg = wxGridTableMessage(self,
302 wxGRIDTABLE_NOTIFY_ROWS_DELETED,
303 curRows - newRows, # position
304 curRows - newRows) # how many
305 self.GetView().ProcessTableMessage(msg)
306 self.GetView().FitInside()
307
308 def __SetRow(self, row, group):
309 """Set a row's data to that of the group.
310
311 The table is considered modified after this operation.
312
313 row -- if row is -1 or greater than the current number of rows
314 then group is appended to the end.
315 """
316
317 # either append or replace
318 if row == -1 or row >= self.GetNumberRows():
319 self.tdata.append(group)
320 else:
321 self.tdata[row] = group
322
323 self.__Modified()
324
325 def GetColLabelValue(self, col):
326 """Return the label for the given column."""
327 return self.__col_labels[col]
328
329 def GetRowLabelValue(self, row):
330 """Return the label for the given row."""
331
332 group = self.tdata[row]
333 if isinstance(group, ClassGroupDefault): return _("Default")
334 if isinstance(group, ClassGroupSingleton): return _("Singleton")
335 if isinstance(group, ClassGroupRange): return _("Range")
336 if isinstance(group, ClassGroupMap): return _("Map")
337
338 assert(False) # shouldn't get here
339 return _("")
340
341 def GetNumberRows(self):
342 """Return the number of rows."""
343 return len(self.tdata)
344
345 def GetNumberCols(self):
346 """Return the number of columns."""
347 return self.NUM_COLS
348
349 def IsEmptyCell(self, row, col):
350 """Determine if a cell is empty. This is always false."""
351 return False
352
353 def GetValue(self, row, col):
354 """Return the object that is used to represent the given
355 cell coordinates. This may not be a string."""
356 return self.GetValueAsCustom(row, col, None)
357
358 def SetValue(self, row, col, value):
359 """Assign 'value' to the cell specified by 'row' and 'col'.
360
361 The table is considered modified after this operation.
362 """
363
364 self.SetValueAsCustom(row, col, None, value)
365 self.__Modified()
366
367 def GetValueAsCustom(self, row, col, typeName):
368 """Return the object that is used to represent the given
369 cell coordinates. This may not be a string.
370
371 typeName -- unused, but needed to overload wxPyGridTableBase
372 """
373
374 group = self.tdata[row]
375
376 if col == COL_SYMBOL:
377 return group.GetProperties()
378
379 if col == COL_LABEL:
380 return group.GetLabel()
381
382 # col must be COL_VALUE
383 assert(col == COL_VALUE)
384
385 if isinstance(group, ClassGroupDefault):
386 return _("DEFAULT")
387 elif isinstance(group, ClassGroupSingleton):
388 return group.GetValue()
389 elif isinstance(group, ClassGroupRange):
390 return _("%s - %s") % (group.GetMin(), group.GetMax())
391
392 assert(False) # shouldn't get here
393 return None
394
395 def __ParseInput(self, value):
396 """Try to determine what kind of input value is
397 (string, number, or range)
398
399 Returns a tuple of length one if there is a single
400 value, or of length two if it is a range.
401 """
402
403 type = self.fieldType
404
405 if type == FIELDTYPE_STRING:
406 return (value,)
407 elif type == FIELDTYPE_INT or type == FIELDTYPE_DOUBLE:
408
409 if type == FIELDTYPE_INT:
410 conv = lambda p: int(float(p))
411 else:
412 conv = lambda p: p
413
414 #
415 # first try to take the input as a single number
416 # if there's an exception try to break it into
417 # a range seperated by a '-'. take care to ignore
418 # a leading '-' as that could be for a negative number.
419 # then try to parse the individual parts. if there
420 # is an exception here, let it pass up to the calling
421 # function.
422 #
423 try:
424 return (conv(Str2Num(value)),)
425 except ValueError:
426 i = value.find('-')
427 if i == 0:
428 i = value.find('-', 1)
429
430 return (conv(Str2Num(value[:i])), conv(Str2Num(value[i+1:])))
431
432 assert(False) # shouldn't get here
433 return (0,)
434
435
436 def SetValueAsCustom(self, row, col, typeName, value):
437 """Set the cell specified by 'row' and 'col' to 'value'.
438
439 If column represents the value column, the input is parsed
440 to determine if a string, number, or range was entered.
441 A new ClassGroup may be created if the type of data changes.
442
443 The table is considered modified after this operation.
444
445 typeName -- unused, but needed to overload wxPyGridTableBase
446 """
447
448 assert(col >= 0 and col < self.GetNumberCols())
449 assert(row >= 0 and row < self.GetNumberRows())
450
451 group = self.tdata[row]
452
453 mod = True # assume the data will change
454
455 if col == COL_SYMBOL:
456 group.SetProperties(value)
457 elif col == COL_LABEL:
458 group.SetLabel(value)
459 elif col == COL_VALUE:
460 if isinstance(group, ClassGroupDefault):
461 # not allowed to modify the default value
462 pass
463 elif isinstance(group, ClassGroupMap):
464 # something special
465 pass
466 else: # SINGLETON, RANGE
467 try:
468 dataInfo = self.__ParseInput(value)
469 except ValueError:
470 # bad input, ignore the request
471 mod = False
472 else:
473
474 changed = False
475 ngroup = group
476 props = group.GetProperties()
477
478 #
479 # try to update the values, which may include
480 # changing the underlying group type if the
481 # group was a singleton and a range was entered
482 #
483 if len(dataInfo) == 1:
484 if not isinstance(group, ClassGroupSingleton):
485 ngroup = ClassGroupSingleton(prop = props)
486 changed = True
487 ngroup.SetValue(dataInfo[0])
488 elif len(dataInfo) == 2:
489 if not isinstance(group, ClassGroupRange):
490 ngroup = ClassGroupRange(prop = props)
491 changed = True
492 ngroup.SetRange(dataInfo[0], dataInfo[1])
493 else:
494 assert(False)
495 pass
496
497 if changed:
498 ngroup.SetLabel(group.GetLabel())
499 self.SetClassGroup(row, ngroup)
500 else:
501 assert(False) # shouldn't be here
502 pass
503
504 if mod:
505 self.__Modified()
506 self.GetView().Refresh()
507
508 def GetAttr(self, row, col, someExtraParameter):
509 """Returns the cell attributes"""
510
511 attr = wxGridCellAttr()
512 #attr = wxPyGridTableBase.GetAttr(self, row, col, someExtraParameter)
513
514 if col == COL_SYMBOL:
515 # we need to create a new renderer each time, because
516 # SetRenderer takes control of the parameter
517 attr.SetRenderer(ClassRenderer(self.shapeType))
518 attr.SetReadOnly()
519
520 return attr
521
522 def GetClassGroup(self, row):
523 """Return the ClassGroup object representing row 'row'."""
524
525 return self.tdata[row] # self.GetValueAsCustom(row, COL_SYMBOL, None)
526
527 def SetClassGroup(self, row, group):
528 self.__SetRow(row, group)
529 self.GetView().Refresh()
530
531 def __Modified(self, mod = True):
532 """Adjust the modified flag.
533
534 mod -- if -1 set the modified flag to False, otherwise perform
535 an 'or' operation with the current value of the flag and
536 'mod'
537 """
538
539 if mod == -1:
540 self.modified = False
541 else:
542 self.modified = mod or self.modified
543
544 def IsModified(self):
545 """True if this table is considered modified."""
546 return self.modified
547
548 def DeleteRows(self, pos, numRows = 1):
549 """Deletes 'numRows' beginning at row 'pos'.
550
551 The row representing the default group is not removed.
552
553 The table is considered modified if any rows are removed.
554 """
555
556 assert(pos >= 0)
557 old_len = len(self.tdata)
558 for row in range(pos, pos - numRows, -1):
559 group = self.GetClassGroup(row)
560 if not isinstance(group, ClassGroupDefault):
561 self.tdata.pop(row)
562 self.__Modified()
563
564 if self.IsModified():
565 self.__NotifyRowChanges(old_len, len(self.tdata))
566
567 def AppendRows(self, numRows = 1):
568 """Append 'numRows' empty rows to the end of the table.
569
570 The table is considered modified if any rows are appended.
571 """
572
573 old_len = len(self.tdata)
574 for i in range(numRows):
575 np = ClassGroupSingleton()
576 self.__SetRow(-1, np)
577
578 if self.IsModified():
579 self.__NotifyRowChanges(old_len, len(self.tdata))
580
581
582 class Classifier(NonModalDialog):
583
584 def __init__(self, parent, name, layer):
585 NonModalDialog.__init__(self, parent, name,
586 _("Classifier: %s") % layer.Title())
587
588 panel = wxPanel(self, -1, size=(100, 100))
589
590 self.layer = layer
591
592 self.originalClass = self.layer.GetClassification()
593 field = self.originalClass.GetField()
594 fieldType = self.originalClass.GetFieldType()
595
596 topBox = wxBoxSizer(wxVERTICAL)
597 panelBox = wxBoxSizer(wxVERTICAL)
598
599 #panelBox.Add(wxStaticText(panel, -1, _("Layer: %s") % layer.Title()),
600 #0, wxALIGN_LEFT | wxALL, 4)
601 panelBox.Add(wxStaticText(panel, -1,
602 _("Layer Type: %s") % layer.ShapeType()),
603 0, wxALIGN_LEFT | wxALL, 4)
604
605
606 #
607 # make field combo box
608 #
609 self.fields = wxComboBox(panel, ID_PROPERTY_SELECT, "",
610 style = wxCB_READONLY)
611
612 self.num_cols = layer.table.field_count()
613 # just assume the first field in case one hasn't been
614 # specified in the file.
615 self.__cur_field = 0
616
617 self.fields.Append("<None>")
618 self.fields.SetClientData(0, None)
619
620 for i in range(self.num_cols):
621 type, name, len, decc = layer.table.field_info(i)
622 self.fields.Append(name)
623
624 if name == field:
625 self.__cur_field = i + 1
626 self.fields.SetClientData(i + 1, self.originalClass)
627 else:
628 self.fields.SetClientData(i + 1, None)
629
630
631 ###########
632
633 self.fieldTypeText = wxStaticText(panel, -1, "")
634 panelBox.Add(self.fieldTypeText, 0,
635 wxGROW | wxALIGN_LEFT | wxALL | wxADJUST_MINSIZE, 4)
636
637 propertyBox = wxBoxSizer(wxHORIZONTAL)
638 propertyBox.Add(wxStaticText(panel, -1, _("Field: ")),
639 0, wxALIGN_LEFT | wxALL, 4)
640 propertyBox.Add(self.fields, 1, wxGROW|wxALL, 4)
641 EVT_COMBOBOX(self, ID_PROPERTY_SELECT, self._OnFieldSelect)
642
643 panelBox.Add(propertyBox, 0, wxGROW, 4)
644
645 ###########
646 #
647 # Classification data table
648 #
649
650 controlBox = wxBoxSizer(wxHORIZONTAL)
651
652 self.classGrid = ClassGrid(panel)
653 self.__SetGridTable(self.__cur_field)
654
655 controlBox.Add(self.classGrid, 1, wxGROW, 0)
656
657 ###########
658 #
659 # Control buttons:
660 #
661 self.controlButtons = []
662
663 controlButtonBox = wxBoxSizer(wxVERTICAL)
664
665 button = wxButton(panel, ID_CLASSIFY_ADD, _("Add"))
666 controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
667 self.controlButtons.append(button)
668
669 #button = wxButton(panel, ID_CLASSIFY_GENRANGE, _("Generate Ranges"))
670 #controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
671 #self.controlButtons.append(button)
672
673 button = wxButton(panel, ID_CLASSIFY_MOVEUP, _("Move Up"))
674 controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
675 self.controlButtons.append(button)
676
677 button = wxButton(panel, ID_CLASSIFY_MOVEDOWN, _("Move Down"))
678 controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
679 self.controlButtons.append(button)
680
681 controlButtonBox.Add(60, 20, 0, wxGROW | wxALL | wxALIGN_BOTTOM, 4)
682
683 button = wxButton(panel, ID_CLASSIFY_REMOVE, _("Remove"))
684 controlButtonBox.Add(button, 0, wxGROW | wxALL | wxALIGN_BOTTOM, 4)
685 self.controlButtons.append(button)
686
687 controlBox.Add(controlButtonBox, 0, wxGROW, 10)
688 panelBox.Add(controlBox, 1, wxGROW, 10)
689
690 EVT_BUTTON(self, ID_CLASSIFY_ADD, self._OnAdd)
691 EVT_BUTTON(self, ID_CLASSIFY_REMOVE, self._OnRemove)
692 EVT_BUTTON(self, ID_CLASSIFY_GENRANGE, self._OnGenRange)
693 EVT_BUTTON(self, ID_CLASSIFY_MOVEUP, self._OnMoveUp)
694 EVT_BUTTON(self, ID_CLASSIFY_MOVEDOWN, self._OnMoveDown)
695
696 ###########
697
698 buttonBox = wxBoxSizer(wxHORIZONTAL)
699 buttonBox.Add(wxButton(panel, ID_CLASSIFY_OK, _("OK")),
700 0, wxALL, 4)
701 buttonBox.Add(60, 20, 0, wxALL, 4)
702 buttonBox.Add(wxButton(panel, ID_CLASSIFY_APPLY, _("Apply")),
703 0, wxALL, 4)
704 buttonBox.Add(60, 20, 0, wxALL, 4)
705 buttonBox.Add(wxButton(panel, ID_CLASSIFY_CANCEL, _("Cancel")),
706 0, wxALL, 4)
707 panelBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 0)
708
709 EVT_BUTTON(self, ID_CLASSIFY_OK, self._OnOK)
710 EVT_BUTTON(self, ID_CLASSIFY_APPLY, self._OnApply)
711 EVT_BUTTON(self, ID_CLASSIFY_CANCEL, self._OnCancel)
712
713 ###########
714
715 self.fields.SetSelection(self.__cur_field)
716 self.__SelectField(self.__cur_field)
717
718 panel.SetAutoLayout(True)
719 panel.SetSizer(panelBox)
720 panelBox.SetSizeHints(panel)
721
722 topBox.Add(panel, 1, wxGROW, 0)
723 panelBox.SetSizeHints(self)
724 self.SetAutoLayout(True)
725 self.SetSizer(topBox)
726
727 ######################
728
729 self.haveApplied = False
730
731 def __BuildClassification(self, fieldIndex):
732
733 numRows = self.classGrid.GetNumberRows()
734 assert(numRows > 0) # there should always be a default row
735
736 clazz = Classification()
737 if fieldIndex == 0:
738 fieldName = None
739 fieldType = None
740 else:
741 fieldName = self.fields.GetString(fieldIndex)
742 fieldType = self.layer.GetFieldType(fieldName)
743
744 clazz.SetField(fieldName)
745 clazz.SetFieldType(fieldType)
746
747
748 table = self.classGrid.GetTable()
749 clazz.SetDefaultGroup(table.GetClassGroup(0))
750
751 for i in range(1, numRows):
752 clazz.AddGroup(table.GetClassGroup(i))
753
754 return clazz
755
756 def __SetGridTable(self, fieldIndex):
757
758 clazz = self.fields.GetClientData(fieldIndex)
759
760 if clazz is None:
761 clazz = Classification()
762 clazz.SetDefaultGroup(
763 ClassGroupDefault(
764 self.layer.GetClassification().
765 GetDefaultGroup().GetProperties()))
766
767 fieldName = self.fields.GetString(fieldIndex)
768 fieldType = self.layer.GetFieldType(fieldName)
769 clazz.SetFieldType(fieldType)
770
771 self.classGrid.CreateTable(clazz, self.layer.ShapeType())
772
773
774
775 type2string = {None: _("None"),
776 FIELDTYPE_STRING: _("Text"),
777 FIELDTYPE_INT: _("Integer"),
778 FIELDTYPE_DOUBLE: _("Decimal")}
779
780 def __SetFieldTypeText(self, fieldIndex):
781 fieldName = self.fields.GetString(fieldIndex)
782 fieldType = self.layer.GetFieldType(fieldName)
783
784 assert(Classifier.type2string.has_key(fieldType))
785
786 text = Classifier.type2string[fieldType]
787
788 self.fieldTypeText.SetLabel(_("Field Type: %s") % text)
789
790 def __SelectField(self, newIndex, oldIndex = -1):
791
792 assert(oldIndex >= -1)
793
794 if oldIndex != -1:
795 clazz = self.__BuildClassification(oldIndex)
796 self.fields.SetClientData(oldIndex, clazz)
797
798 self.__SetGridTable(newIndex)
799
800 enabled = newIndex != 0
801
802 for b in self.controlButtons:
803 b.Enable(enabled)
804
805 self.__SetFieldTypeText(newIndex)
806
807
808 def _OnFieldSelect(self, event):
809 index = self.fields.GetSelection()
810 self.__SelectField(index, self.__cur_field)
811 self.__cur_field = index
812
813 def _OnApply(self, event):
814 """Put the data from the table into a new Classification and hand
815 it to the layer.
816 """
817
818 clazz = self.fields.GetClientData(self.__cur_field)
819
820 #
821 # only build the classification if there wasn't one to
822 # to begin with or it has been modified
823 #
824 if clazz is None or self.classGrid.GetTable().IsModified():
825 clazz = self.__BuildClassification(self.__cur_field)
826
827 self.layer.SetClassification(clazz)
828
829 self.haveApplied = True
830
831 def _OnOK(self, event):
832 self._OnApply(event)
833 self.OnClose(event)
834
835 def _OnCancel(self, event):
836 """The layer's current classification stays the same."""
837 if self.haveApplied:
838 self.layer.SetClassification(self.originalClass)
839
840 self.OnClose(event)
841
842 def _OnAdd(self, event):
843 self.classGrid.AppendRows()
844
845 def _OnRemove(self, event):
846 self.classGrid.DeleteSelectedRows()
847
848 def _OnGenRange(self, event):
849 print "Classifier._OnGenRange()"
850
851 def _OnMoveUp(self, event):
852 sel = self.classGrid.GetCurrentSelection()
853
854 if len(sel) == 1:
855 i = sel[0]
856 if i > 1:
857 table = self.classGrid.GetTable()
858 x = table.GetClassGroup(i - 1)
859 y = table.GetClassGroup(i)
860 table.SetClassGroup(i - 1, y)
861 table.SetClassGroup(i, x)
862 self.classGrid.ClearSelection()
863 self.classGrid.SelectRow(i - 1)
864
865 def _OnMoveDown(self, event):
866 sel = self.classGrid.GetCurrentSelection()
867
868 if len(sel) == 1:
869 i = sel[0]
870 table = self.classGrid.GetTable()
871 if 0 < i < table.GetNumberRows() - 1:
872 x = table.GetClassGroup(i)
873 y = table.GetClassGroup(i + 1)
874 table.SetClassGroup(i, y)
875 table.SetClassGroup(i + 1, x)
876 self.classGrid.ClearSelection()
877 self.classGrid.SelectRow(i + 1)
878
879
880 ID_SELPROP_OK = 4001
881 ID_SELPROP_CANCEL = 4002
882 ID_SELPROP_SPINCTRL = 4002
883 ID_SELPROP_PREVIEW = 4003
884 ID_SELPROP_STROKECLR = 4004
885 ID_SELPROP_FILLCLR = 4005
886 ID_SELPROP_STROKECLRTRANS = 4006
887 ID_SELPROP_FILLCLRTRANS = 4007
888
889 class SelectPropertiesDialog(wxDialog):
890
891 def __init__(self, parent, prop, shapeType):
892 wxDialog.__init__(self, parent, -1, _("Select Properties"),
893 style = wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
894
895 self.prop = ClassGroupProperties(prop)
896
897 topBox = wxBoxSizer(wxVERTICAL)
898
899 itemBox = wxBoxSizer(wxHORIZONTAL)
900
901 # preview box
902 previewBox = wxBoxSizer(wxVERTICAL)
903 previewBox.Add(wxStaticText(self, -1, _("Preview:")),
904 0, wxALIGN_LEFT | wxALL, 4)
905 self.previewWin = ClassDataPreviewWindow(None, self.prop, shapeType,
906 self, ID_SELPROP_PREVIEW, (40, 40))
907 previewBox.Add(self.previewWin, 1, wxGROW, 15)
908
909 itemBox.Add(previewBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
910
911 # control box
912 ctrlBox = wxBoxSizer(wxVERTICAL)
913
914 lineColorBox = wxBoxSizer(wxHORIZONTAL)
915 lineColorBox.Add(
916 wxButton(self, ID_SELPROP_STROKECLR, _("Change Line Color")),
917 1, wxALL | wxGROW, 4)
918 EVT_BUTTON(self, ID_SELPROP_STROKECLR, self._OnChangeLineColor)
919
920 lineColorBox.Add(
921 wxButton(self, ID_SELPROP_STROKECLRTRANS, _("Transparent")),
922 1, wxALL | wxGROW, 4)
923 EVT_BUTTON(self, ID_SELPROP_STROKECLRTRANS,
924 self._OnChangeLineColorTrans)
925
926 ctrlBox.Add(lineColorBox, 0,
927 wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
928
929 if shapeType != SHAPETYPE_ARC:
930 fillColorBox = wxBoxSizer(wxHORIZONTAL)
931 fillColorBox.Add(
932 wxButton(self, ID_SELPROP_FILLCLR, _("Change Fill Color")),
933 1, wxALL | wxGROW, 4)
934 EVT_BUTTON(self, ID_SELPROP_FILLCLR, self._OnChangeFillColor)
935 fillColorBox.Add(
936 wxButton(self, ID_SELPROP_FILLCLRTRANS, _("Transparent")),
937 1, wxALL | wxGROW, 4)
938 EVT_BUTTON(self, ID_SELPROP_FILLCLRTRANS,
939 self._OnChangeFillColorTrans)
940 ctrlBox.Add(fillColorBox, 0,
941 wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
942
943 spinBox = wxBoxSizer(wxHORIZONTAL)
944 spinBox.Add(wxStaticText(self, -1, _("Line Width: ")),
945 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
946 self.spinCtrl = wxSpinCtrl(self, ID_SELPROP_SPINCTRL,
947 min=1, max=10,
948 value=str(prop.GetLineWidth()),
949 initial=prop.GetLineWidth())
950
951 EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL, self._OnSpin)
952
953 spinBox.Add(self.spinCtrl, 0, wxALIGN_LEFT | wxALL, 4)
954
955 ctrlBox.Add(spinBox, 0, wxALIGN_RIGHT | wxALL, 0)
956 itemBox.Add(ctrlBox, 0, wxALIGN_RIGHT | wxALL | wxGROW, 0)
957 topBox.Add(itemBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
958
959 #
960 # Control buttons:
961 #
962 buttonBox = wxBoxSizer(wxHORIZONTAL)
963 buttonBox.Add(wxButton(self, ID_SELPROP_OK, _("OK")),
964 0, wxALL, 4)
965 buttonBox.Add(wxButton(self, ID_SELPROP_CANCEL, _("Cancel")),
966 0, wxALL, 4)
967 topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)
968
969 EVT_BUTTON(self, ID_SELPROP_OK, self._OnOK)
970 EVT_BUTTON(self, ID_SELPROP_CANCEL, self._OnCancel)
971
972 self.SetAutoLayout(True)
973 self.SetSizer(topBox)
974 topBox.Fit(self)
975 topBox.SetSizeHints(self)
976
977 def _OnOK(self, event):
978 self.EndModal(wxID_OK)
979
980 def _OnCancel(self, event):
981 self.EndModal(wxID_CANCEL)
982
983 def _OnSpin(self, event):
984 self.prop.SetLineWidth(self.spinCtrl.GetValue())
985 self.previewWin.Refresh()
986
987 def __GetColor(self, cur):
988 dialog = wxColourDialog(self)
989 dialog.GetColourData().SetColour(Color2wxColour(cur))
990 ret = None
991 if dialog.ShowModal() == wxID_OK:
992 ret = wxColour2Color(dialog.GetColourData().GetColour())
993
994 dialog.Destroy()
995
996 return ret
997
998 def _OnChangeLineColor(self, event):
999 clr = self.__GetColor(self.prop.GetLineColor())
1000 if clr is not None:
1001 self.prop.SetLineColor(clr)
1002 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1003
1004 def _OnChangeLineColorTrans(self, event):
1005 self.prop.SetLineColor(Color.None)
1006 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1007
1008 def _OnChangeFillColor(self, event):
1009 clr = self.__GetColor(self.prop.GetFill())
1010 if clr is not None:
1011 self.prop.SetFill(clr)
1012 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1013
1014 def _OnChangeFillColorTrans(self, event):
1015 self.prop.SetFill(Color.None)
1016 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1017
1018 def GetClassGroupProperties(self):
1019 return self.prop
1020
1021
1022 class ClassDataPreviewWindow(wxWindow):
1023
1024 def __init__(self, rect, prop, shapeType,
1025 parent = None, id = -1, size = wxDefaultSize):
1026 if parent is not None:
1027 wxWindow.__init__(self, parent, id, (0, 0), size)
1028 EVT_PAINT(self, self._OnPaint)
1029
1030 self.rect = rect
1031
1032 self.prop = prop
1033 self.shapeType = shapeType
1034 self.previewer = ClassDataPreviewer()
1035
1036 def _OnPaint(self, event):
1037 dc = wxPaintDC(self)
1038
1039 # XXX: this doesn't seem to be having an effect:
1040 dc.DestroyClippingRegion()
1041
1042 if self.rect is None:
1043 w, h = self.GetSize()
1044 rect = wxRect(0, 0, w, h)
1045 else:
1046 rect = self.rect
1047
1048 self.previewer.Draw(dc, rect, self.prop, self.shapeType)
1049
1050 class ClassDataPreviewer:
1051
1052 def Draw(self, dc, rect, prop, shapeType):
1053
1054 assert(dc is not None)
1055 assert(isinstance(prop, ClassGroupProperties))
1056
1057 if rect is None:
1058 x = 0
1059 y = 0
1060 w, h = dc.GetSize()
1061 else:
1062 x = rect.GetX()
1063 y = rect.GetY()
1064 w = rect.GetWidth()
1065 h = rect.GetHeight()
1066
1067 stroke = prop.GetLineColor()
1068 if stroke is Color.None:
1069 pen = wxTRANSPARENT_PEN
1070 else:
1071 pen = wxPen(Color2wxColour(stroke),
1072 prop.GetLineWidth(),
1073 wxSOLID)
1074
1075 stroke = prop.GetFill()
1076 if stroke is Color.None:
1077 brush = wxTRANSPARENT_BRUSH
1078 else:
1079 brush = wxBrush(Color2wxColour(stroke), wxSOLID)
1080
1081 dc.SetPen(pen)
1082 dc.SetBrush(brush)
1083
1084 if shapeType == SHAPETYPE_ARC:
1085 dc.DrawSpline([wxPoint(x, y + h),
1086 wxPoint(x + w/2, y + h/4),
1087 wxPoint(x + w/2, y + h/4*3),
1088 wxPoint(x + w, y)])
1089
1090 elif shapeType == SHAPETYPE_POINT or \
1091 shapeType == SHAPETYPE_POLYGON:
1092
1093 dc.DrawCircle(x + w/2, y + h/2,
1094 (min(w, h) - prop.GetLineWidth())/2)
1095
1096 class ClassRenderer(wxPyGridCellRenderer):
1097
1098 def __init__(self, shapeType):
1099 wxPyGridCellRenderer.__init__(self)
1100 self.shapeType = shapeType
1101 self.previewer = ClassDataPreviewer()
1102
1103 def Draw(self, grid, attr, dc, rect, row, col, isSelected):
1104 data = grid.GetTable().GetClassGroup(row)
1105
1106 dc.SetClippingRegion(rect.GetX(), rect.GetY(),
1107 rect.GetWidth(), rect.GetHeight())
1108 dc.SetPen(wxPen(wxLIGHT_GREY))
1109 dc.SetBrush(wxBrush(wxLIGHT_GREY, wxSOLID))
1110 dc.DrawRectangle(rect.GetX(), rect.GetY(),
1111 rect.GetWidth(), rect.GetHeight())
1112
1113 if not isinstance(data, ClassGroupMap):
1114 self.previewer.Draw(dc, rect, data.GetProperties(), self.shapeType)
1115
1116 if isSelected:
1117 dc.SetPen(wxPen(wxColour(0 * 255, 0 * 255, 0 * 255),
1118 4, wxSOLID))
1119 dc.SetBrush(wxTRANSPARENT_BRUSH)
1120 dc.DrawRectangle(rect.GetX(), rect.GetY(),
1121 rect.GetWidth(), rect.GetHeight())
1122
1123 dc.DestroyClippingRegion()
1124

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26