/[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 638 - (show annotations)
Thu Apr 10 14:35:20 2003 UTC (21 years, 10 months ago) by jonathan
File MIME type: text/x-python
File size: 44305 byte(s)
(ClassTable): Add a new column for the "Visible" check boxes.
(Classifier): Rename the buttons and refactor the code to match the new labels.

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 Thuban.UI.classgen import ClassGenDialog, ClassGenerator
31
32 from dialogs import NonModalDialog
33
34 # widget id's
35 ID_PROPERTY_SELECT = 4010
36 ID_CLASS_TABLE = 40011
37
38
39 # table columns
40 COL_VISIBLE = 0
41 COL_SYMBOL = 1
42 COL_VALUE = 2
43 COL_LABEL = 3
44 NUM_COLS = 4
45
46 # indices into the client data lists in Classifier.fields
47 FIELD_CLASS = 0
48 FIELD_TYPE = 1
49 FIELD_NAME = 2
50
51 #
52 # this is a silly work around to ensure that the table that is
53 # passed into SetTable is the same that is returned by GetTable
54 #
55 import weakref
56 class ClassGrid(wxGrid):
57
58
59 def __init__(self, parent, classifier):
60 """Constructor.
61
62 parent -- the parent window
63
64 clazz -- the working classification that this grid should
65 use for display.
66 """
67
68 wxGrid.__init__(self, parent, ID_CLASS_TABLE)
69
70 self.classifier = classifier
71
72 self.currentSelection = []
73
74 EVT_GRID_CELL_LEFT_DCLICK(self, self._OnCellDClick)
75 EVT_GRID_RANGE_SELECT(self, self._OnSelectedRange)
76 EVT_GRID_SELECT_CELL(self, self._OnSelectedCell)
77 EVT_GRID_COL_SIZE(self, self._OnCellResize)
78 EVT_GRID_ROW_SIZE(self, self._OnCellResize)
79
80 #def GetCellAttr(self, row, col):
81 #print "GetCellAttr ", row, col
82 #wxGrid.GetCellAttr(self, row, col)
83
84 def CreateTable(self, clazz, shapeType, group = None):
85
86 assert isinstance(clazz, Classification)
87
88 table = self.GetTable()
89 if table is None:
90 w = self.GetDefaultColSize() * 3 + self.GetDefaultRowLabelSize()
91 h = self.GetDefaultRowSize() * 4 + self.GetDefaultColLabelSize()
92 self.SetDimensions(-1, -1, w, h)
93 self.SetSizeHints(w, h, -1, -1)
94 table = ClassTable(self)
95 self.SetTable(table, True)
96
97
98 self.SetSelectionMode(wxGrid.wxGridSelectRows)
99 self.ClearSelection()
100
101 table.Reset(clazz, shapeType, group)
102
103 def GetCurrentSelection(self):
104 """Return the currently highlighted rows as an increasing list
105 of row numbers."""
106 sel = copy.copy(self.currentSelection)
107 sel.sort()
108 return sel
109
110 def GetSelectedRows(self):
111 return self.GetCurrentSelection()
112
113 #def SetCellRenderer(self, row, col, renderer):
114 #raise ValueError(_("Must not allow setting of renderer in ClassGrid!"))
115
116 #
117 # [Set|Get]Table is taken from http://wiki.wxpython.org
118 # they are needed as a work around to ensure that the table
119 # that is passed to SetTable is the one that is returned
120 # by GetTable.
121 #
122 def SetTable(self, object, *attributes):
123 self.tableRef = weakref.ref(object)
124 return wxGrid.SetTable(self, object, *attributes)
125
126 def GetTable(self):
127 try:
128 return self.tableRef()
129 except:
130 return None
131
132 def DeleteSelectedRows(self):
133 """Deletes all highlighted rows.
134
135 If only one row is highlighted then after it is deleted the
136 row that was below the deleted row is highlighted."""
137
138 sel = self.GetCurrentSelection()
139
140 # nothing to do
141 if len(sel) == 0: return
142
143 # if only one thing is selected check if it is the default
144 # data row, because we can't remove that
145 if len(sel) == 1:
146 #group = self.GetTable().GetValueAsCustom(sel[0], COL_SYMBOL, None)
147 group = self.GetTable().GetClassGroup(sel[0])
148 if isinstance(group, ClassGroupDefault):
149 wxMessageDialog(self,
150 "The Default group cannot be removed.",
151 style = wxOK | wxICON_EXCLAMATION).ShowModal()
152 return
153
154
155 self.ClearSelection()
156
157 # we need to remove things from the bottom up so we don't
158 # change the indexes of rows that will be deleted next
159 sel.reverse()
160
161 #
162 # actually remove the rows
163 #
164 table = self.GetTable()
165 for row in sel:
166 table.DeleteRows(row)
167
168 #
169 # if there was only one row selected highlight the row
170 # that was directly below it, or move up one if the
171 # deleted row was the last row.
172 #
173 if len(sel) == 1:
174 r = sel[0]
175 if r > self.GetNumberRows() - 1:
176 r = self.GetNumberRows() - 1
177 self.SelectRow(r)
178
179
180 def SelectGroup(self, group, makeVisible = True):
181 if group is None: return
182
183 assert isinstance(group, ClassGroup)
184
185 table = self.GetTable()
186
187 assert table is not None
188
189 for i in range(table.GetNumberRows()):
190 g = table.GetClassGroup(i)
191 if g is group:
192 self.SelectRow(i)
193 if makeVisible:
194 self.MakeCellVisible(i, 0)
195 break
196
197 #
198 # XXX: This isn't working, and there is no way to deselect rows wxPython!
199 #
200 # def DeselectRow(self, row):
201 # self.ProcessEvent(
202 # wxGridRangeSelectEvent(-1,
203 # wxEVT_GRID_RANGE_SELECT,
204 # self,
205 # (row, row), (row, row),
206 # sel = False))
207
208 def _OnCellDClick(self, event):
209 """Handle a double click on a cell."""
210
211 r = event.GetRow()
212 c = event.GetCol()
213
214 if c == COL_SYMBOL:
215 self.classifier.EditSymbol(r)
216 else:
217 event.Skip()
218
219 #
220 # _OnSelectedRange() and _OnSelectedCell() were borrowed
221 # from http://wiki.wxpython.org to keep track of which
222 # cells are currently highlighted
223 #
224 def _OnSelectedRange(self, event):
225 """Internal update to the selection tracking list"""
226 if event.Selecting():
227 for index in range( event.GetTopRow(), event.GetBottomRow()+1):
228 if index not in self.currentSelection:
229 self.currentSelection.append( index )
230 else:
231 for index in range( event.GetTopRow(), event.GetBottomRow()+1):
232 while index in self.currentSelection:
233 self.currentSelection.remove( index )
234 #self.ConfigureForSelection()
235
236 event.Skip()
237
238 def _OnSelectedCell( self, event ):
239 """Internal update to the selection tracking list"""
240 self.currentSelection = [ event.GetRow() ]
241 #self.ConfigureForSelection()
242 event.Skip()
243
244 def _OnCellResize(self, event):
245 self.FitInside()
246
247 class ClassTable(wxPyGridTableBase):
248 """Represents the underlying data structure for the grid."""
249
250 __col_labels = [_("Visible"), _("Symbol"), _("Value"), _("Label")]
251
252
253 def __init__(self, view = None):
254 #def __init__(self, clazz, shapeType, view = None):
255 """Constructor.
256
257 shapeType -- the type of shape that the layer uses
258
259 view -- a wxGrid object that uses this class for its table
260 """
261
262 wxPyGridTableBase.__init__(self)
263
264 assert len(ClassTable.__col_labels) == NUM_COLS
265
266 self.clazz = None
267 self.__colAttr = {}
268
269 self.SetView(view)
270
271 def Reset(self, clazz, shapeType, group = None):
272 """Reset the table with the given data.
273
274 This is necessary because wxWindows does not allow a grid's
275 table to change once it has been intially set and so we
276 need a way of modifying the data.
277
278 clazz -- the working classification that this table should
279 use for display. This may be different from the
280 classification in the layer.
281
282 shapeType -- the type of shape that the layer uses
283 """
284
285 assert isinstance(clazz, Classification)
286
287 self.GetView().BeginBatch()
288
289 self.fieldType = clazz.GetFieldType()
290 self.shapeType = shapeType
291
292 self.SetClassification(clazz, group)
293 self.__Modified(-1)
294
295 self.__colAttr = {}
296
297 attr = wxGridCellAttr()
298 attr.SetEditor(wxGridCellBoolEditor())
299 attr.SetRenderer(wxGridCellBoolRenderer())
300 attr.SetAlignment(wxALIGN_CENTER, wxALIGN_CENTER)
301 self.__colAttr[COL_VISIBLE] = attr
302
303 attr = wxGridCellAttr()
304 attr.SetRenderer(ClassRenderer(self.shapeType))
305 attr.SetReadOnly()
306 self.__colAttr[COL_SYMBOL] = attr
307
308 self.GetView().EndBatch()
309 self.GetView().FitInside()
310
311 def GetClassification(self):
312 return self.clazz
313
314 def SetClassification(self, clazz, group = None):
315
316 self.GetView().BeginBatch()
317
318 old_len = self.GetNumberRows()
319
320 row = -1
321 self.clazz = clazz
322
323 self.__NotifyRowChanges(old_len, self.GetNumberRows())
324
325 #
326 # XXX: this is dead code at the moment
327 #
328 if row > -1:
329 self.GetView().ClearSelection()
330 self.GetView().SelectRow(row)
331 self.GetView().MakeCellVisible(row, 0)
332
333 self.__Modified()
334
335
336 self.GetView().EndBatch()
337 self.GetView().FitInside()
338
339 def __NotifyRowChanges(self, curRows, newRows):
340 #
341 # silly message processing for updates to the number of
342 # rows and columns
343 #
344 if newRows > curRows:
345 msg = wxGridTableMessage(self,
346 wxGRIDTABLE_NOTIFY_ROWS_APPENDED,
347 newRows - curRows) # how many
348 self.GetView().ProcessTableMessage(msg)
349 self.GetView().FitInside()
350 elif newRows < curRows:
351 msg = wxGridTableMessage(self,
352 wxGRIDTABLE_NOTIFY_ROWS_DELETED,
353 curRows, # position
354 curRows - newRows) # how many
355 self.GetView().ProcessTableMessage(msg)
356 self.GetView().FitInside()
357
358
359 def __SetRow(self, row, group):
360 """Set a row's data to that of the group.
361
362 The table is considered modified after this operation.
363
364 row -- if row is < 0 'group' is inserted at the top of the table
365 if row is >= GetNumberRows() or None 'group' is append to
366 the end of the table.
367 otherwise 'group' replaces row 'row'
368 """
369
370 # either append or replace
371 if row is None or row >= self.GetNumberRows():
372 self.clazz.AppendGroup(group)
373 elif row < 0:
374 self.clazz.InsertGroup(0, group)
375 else:
376 if row == 0:
377 self.clazz.SetDefaultGroup(group)
378 else:
379 self.clazz.ReplaceGroup(row - 1, group)
380
381 self.__Modified()
382
383 def GetColLabelValue(self, col):
384 """Return the label for the given column."""
385 return self.__col_labels[col]
386
387 def GetRowLabelValue(self, row):
388 """Return the label for the given row."""
389
390 if row == 0:
391 return _("Default")
392 else:
393 group = self.clazz.GetGroup(row - 1)
394 if isinstance(group, ClassGroupDefault): return _("Default")
395 if isinstance(group, ClassGroupSingleton): return _("Singleton")
396 if isinstance(group, ClassGroupRange): return _("Range")
397 if isinstance(group, ClassGroupMap): return _("Map")
398
399 assert False # shouldn't get here
400 return _("")
401
402 def GetNumberRows(self):
403 """Return the number of rows."""
404 if self.clazz is None:
405 return 0
406
407 return self.clazz.GetNumGroups() + 1 # +1 for default group
408
409 def GetNumberCols(self):
410 """Return the number of columns."""
411 return NUM_COLS
412
413 def IsEmptyCell(self, row, col):
414 """Determine if a cell is empty. This is always false."""
415 return False
416
417 def GetValue(self, row, col):
418 """Return the object that is used to represent the given
419 cell coordinates. This may not be a string."""
420 return self.GetValueAsCustom(row, col, None)
421
422 def SetValue(self, row, col, value):
423 """Assign 'value' to the cell specified by 'row' and 'col'.
424
425 The table is considered modified after this operation.
426 """
427
428 self.SetValueAsCustom(row, col, None, value)
429
430 def GetValueAsCustom(self, row, col, typeName):
431 """Return the object that is used to represent the given
432 cell coordinates. This may not be a string.
433
434 typeName -- unused, but needed to overload wxPyGridTableBase
435 """
436
437 if row == 0:
438 group = self.clazz.GetDefaultGroup()
439 else:
440 group = self.clazz.GetGroup(row - 1)
441
442
443 if col == COL_VISIBLE:
444 return group.IsVisible()
445
446 if col == COL_SYMBOL:
447 return group.GetProperties()
448
449 if col == COL_LABEL:
450 return group.GetLabel()
451
452 # col must be COL_VALUE
453 assert col == COL_VALUE
454
455 if isinstance(group, ClassGroupDefault):
456 return _("DEFAULT")
457 elif isinstance(group, ClassGroupSingleton):
458 return group.GetValue()
459 elif isinstance(group, ClassGroupRange):
460 return _("%s - %s") % (group.GetMin(), group.GetMax())
461
462 assert(False) # shouldn't get here
463 return None
464
465 def __ParseInput(self, value):
466 """Try to determine what kind of input value is
467 (string, number, or range)
468
469 Returns a tuple of length one if there is a single
470 value, or of length two if it is a range.
471 """
472
473 type = self.fieldType
474
475 if type == FIELDTYPE_STRING:
476 return (value,)
477 elif type in (FIELDTYPE_INT, FIELDTYPE_DOUBLE):
478
479 if type == FIELDTYPE_INT:
480 conv = lambda p: int(float(p))
481 else:
482 conv = lambda p: p
483
484 #
485 # first try to take the input as a single number
486 # if there's an exception try to break it into
487 # a range seperated by a '-'. take care to ignore
488 # a leading '-' as that could be for a negative number.
489 # then try to parse the individual parts. if there
490 # is an exception here, let it pass up to the calling
491 # function.
492 #
493 try:
494 return (conv(Str2Num(value)),)
495 except ValueError:
496 i = value.find('-')
497 if i == 0:
498 i = value.find('-', 1)
499
500 return (conv(Str2Num(value[:i])), conv(Str2Num(value[i+1:])))
501
502 assert False # shouldn't get here
503 return (0,)
504
505
506 def SetValueAsCustom(self, row, col, typeName, value):
507 """Set the cell specified by 'row' and 'col' to 'value'.
508
509 If column represents the value column, the input is parsed
510 to determine if a string, number, or range was entered.
511 A new ClassGroup may be created if the type of data changes.
512
513 The table is considered modified after this operation.
514
515 typeName -- unused, but needed to overload wxPyGridTableBase
516 """
517
518 assert col >= 0 and col < self.GetNumberCols()
519 assert row >= 0 and row < self.GetNumberRows()
520
521 if row == 0:
522 group = self.clazz.GetDefaultGroup()
523 else:
524 group = self.clazz.GetGroup(row - 1)
525
526 mod = True # assume the data will change
527
528 if col == COL_VISIBLE:
529 group.SetVisible(value)
530 elif col == COL_SYMBOL:
531 group.SetProperties(value)
532 elif col == COL_LABEL:
533 group.SetLabel(value)
534 elif col == COL_VALUE:
535 if isinstance(group, ClassGroupDefault):
536 # not allowed to modify the default value
537 pass
538 elif isinstance(group, ClassGroupMap):
539 # something special
540 pass
541 else: # SINGLETON, RANGE
542 try:
543 dataInfo = self.__ParseInput(value)
544 except ValueError:
545 # bad input, ignore the request
546 mod = False
547 else:
548
549 changed = False
550 ngroup = group
551 props = group.GetProperties()
552
553 #
554 # try to update the values, which may include
555 # changing the underlying group type if the
556 # group was a singleton and a range was entered
557 #
558 if len(dataInfo) == 1:
559 if not isinstance(group, ClassGroupSingleton):
560 ngroup = ClassGroupSingleton(prop = props)
561 changed = True
562 ngroup.SetValue(dataInfo[0])
563 elif len(dataInfo) == 2:
564 if not isinstance(group, ClassGroupRange):
565 ngroup = ClassGroupRange(prop = props)
566 changed = True
567 ngroup.SetRange(dataInfo[0], dataInfo[1])
568 else:
569 assert False
570 pass
571
572 if changed:
573 ngroup.SetLabel(group.GetLabel())
574 self.SetClassGroup(row, ngroup)
575 else:
576 assert False # shouldn't be here
577 pass
578
579 if mod:
580 self.__Modified()
581 self.GetView().Refresh()
582
583 def GetAttr(self, row, col, someExtraParameter):
584 """Returns the cell attributes"""
585
586 return self.__colAttr.get(col, wxGridCellAttr()).Clone()
587
588 def GetClassGroup(self, row):
589 """Return the ClassGroup object representing row 'row'."""
590
591 #return self.GetValueAsCustom(row, COL_SYMBOL, None)
592 if row == 0:
593 return self.clazz.GetDefaultGroup()
594 else:
595 return self.clazz.GetGroup(row - 1)
596
597 def SetClassGroup(self, row, group):
598 self.__SetRow(row, group)
599 self.GetView().Refresh()
600
601 def __Modified(self, mod = True):
602 """Adjust the modified flag.
603
604 mod -- if -1 set the modified flag to False, otherwise perform
605 an 'or' operation with the current value of the flag and
606 'mod'
607 """
608
609 if mod == -1:
610 self.modified = False
611 else:
612 self.modified = mod or self.modified
613
614 def IsModified(self):
615 """True if this table is considered modified."""
616 return self.modified
617
618 def DeleteRows(self, pos, numRows = 1):
619 """Deletes 'numRows' beginning at row 'pos'.
620
621 The row representing the default group is not removed.
622
623 The table is considered modified if any rows are removed.
624 """
625
626 assert pos >= 0
627 old_len = self.GetNumberRows()
628 for row in range(pos, pos - numRows, -1):
629 group = self.GetClassGroup(row)
630 if row != 0:
631 self.clazz.RemoveGroup(row - 1)
632 self.__Modified()
633
634 if self.IsModified():
635 self.__NotifyRowChanges(old_len, self.GetNumberRows())
636
637 def AppendRows(self, numRows = 1):
638 """Append 'numRows' empty rows to the end of the table.
639
640 The table is considered modified if any rows are appended.
641 """
642
643 old_len = self.GetNumberRows()
644 for i in range(numRows):
645 np = ClassGroupSingleton()
646 self.__SetRow(None, np)
647
648 if self.IsModified():
649 self.__NotifyRowChanges(old_len, self.GetNumberRows())
650
651
652 ID_CLASSIFY_OK = 4001
653 ID_CLASSIFY_REVERT = 4002
654 ID_CLASSIFY_ADD = 4003
655 ID_CLASSIFY_GENCLASS = 4004
656 ID_CLASSIFY_REMOVE = 4005
657 ID_CLASSIFY_MOVEUP = 4006
658 ID_CLASSIFY_MOVEDOWN = 4007
659 ID_CLASSIFY_TRY = 4008
660 ID_CLASSIFY_EDITSYM = 4009
661 ID_CLASSIFY_CLOSE = 4010
662
663 BTN_ADD = 0
664 BTN_EDIT = 1
665 BTN_GEN = 2
666 BTN_UP = 3
667 BTN_DOWN = 4
668 BTN_RM = 5
669
670 class Classifier(NonModalDialog):
671
672 type2string = {None: _("None"),
673 FIELDTYPE_STRING: _("Text"),
674 FIELDTYPE_INT: _("Integer"),
675 FIELDTYPE_DOUBLE: _("Decimal")}
676
677 def __init__(self, parent, name, layer, group = None):
678 NonModalDialog.__init__(self, parent, name,
679 _("Classifier: %s") % layer.Title())
680
681 panel = wxPanel(self, -1, size=(100, 100))
682
683 self.layer = layer
684
685 self.originalClass = self.layer.GetClassification()
686 field = self.originalClass.GetField()
687 fieldType = self.originalClass.GetFieldType()
688
689 self.genDlg = None
690
691 topBox = wxBoxSizer(wxVERTICAL)
692 panelBox = wxBoxSizer(wxVERTICAL)
693
694 #panelBox.Add(wxStaticText(panel, -1, _("Layer: %s") % layer.Title()),
695 #0, wxALIGN_LEFT | wxALL, 4)
696 panelBox.Add(wxStaticText(panel, -1,
697 _("Layer Type: %s") % layer.ShapeType()),
698 0, wxALIGN_LEFT | wxALL, 4)
699
700
701 #
702 # make field combo box
703 #
704 self.fields = wxComboBox(panel, ID_PROPERTY_SELECT, "",
705 style = wxCB_READONLY)
706
707 self.num_cols = layer.table.field_count()
708 # just assume the first field in case one hasn't been
709 # specified in the file.
710 self.__cur_field = 0
711
712 self.fields.Append("<None>")
713 self.fields.SetClientData(0, None)
714
715 for i in range(self.num_cols):
716 type, name, len, decc = layer.table.field_info(i)
717 self.fields.Append(name)
718
719 if name == field:
720 self.__cur_field = i + 1
721 self.fields.SetClientData(i + 1,
722 copy.deepcopy(self.originalClass))
723 else:
724 self.fields.SetClientData(i + 1, None)
725
726
727 ###########
728
729 self.fieldTypeText = wxStaticText(panel, -1, "")
730 panelBox.Add(self.fieldTypeText, 0,
731 wxGROW | wxALIGN_LEFT | wxALL | wxADJUST_MINSIZE, 4)
732
733 propertyBox = wxBoxSizer(wxHORIZONTAL)
734 propertyBox.Add(wxStaticText(panel, -1, _("Field: ")),
735 0, wxALIGN_LEFT | wxALL, 4)
736 propertyBox.Add(self.fields, 1, wxGROW|wxALL, 4)
737 EVT_COMBOBOX(self, ID_PROPERTY_SELECT, self._OnFieldSelect)
738
739 panelBox.Add(propertyBox, 0, wxGROW, 4)
740
741
742 #
743 # Control Box
744 #
745 controlBox = wxBoxSizer(wxHORIZONTAL)
746
747
748 ###########
749 #
750 # Control buttons:
751 #
752 self.controlButtons = []
753
754 controlButtonBox = wxBoxSizer(wxVERTICAL)
755
756 button = wxButton(panel, ID_CLASSIFY_GENCLASS, _("Generate Class"))
757 controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
758 self.controlButtons.append(button)
759
760 button = wxButton(panel, ID_CLASSIFY_ADD, _("Add"))
761 controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
762 self.controlButtons.append(button)
763
764 button = wxButton(panel, ID_CLASSIFY_MOVEUP, _("Move Up"))
765 controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
766 self.controlButtons.append(button)
767
768 button = wxButton(panel, ID_CLASSIFY_MOVEDOWN, _("Move Down"))
769 controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
770 self.controlButtons.append(button)
771
772 button = wxButton(panel, ID_CLASSIFY_EDITSYM, _("Edit Symbol"))
773 controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
774 self.controlButtons.append(button)
775
776 controlButtonBox.Add(60, 20, 0, wxGROW | wxALL | wxALIGN_BOTTOM, 4)
777
778 button = wxButton(panel, ID_CLASSIFY_REMOVE, _("Remove"))
779 controlButtonBox.Add(button, 0, wxGROW | wxALL | wxALIGN_BOTTOM, 4)
780 self.controlButtons.append(button)
781
782
783 ###########
784 #
785 # Classification data table
786 #
787
788 self.classGrid = ClassGrid(panel, self)
789 # self.classGrid = wxGrid(panel, -1)
790 # self.classGrid.SetDefaultRenderer(wxGridCellBoolRenderer())
791 # self.classGrid.SetDefaultEditor(wxGridCellBoolEditor())
792 #self.classGrid.CreateGrid(5, 5)
793 #self.classGrid.SetCellEditor(0, 0, wxGridCellBoolEditor())
794 #self.classGrid.SetCellRenderer(0, 0, wxGridCellBoolRenderer())
795 #print self.classGrid.GetCellEditor(0, 0)
796 #print self.classGrid.GetCellRenderer(0, 0)
797 #self.classGrid = ClassGrid(panel, self)
798
799 # need these
800 self.__SetGridTable(self.__cur_field, group)
801 self.fields.SetSelection(self.__cur_field)
802 #self.classGrid.SetCellEditor(0, 0, wxGridCellBoolEditor())
803 #self.classGrid.SetCellRenderer(0, 0, wxGridCellBoolRenderer())
804
805 # calling __SelectField after creating the classGrid fills in the
806 # grid with the correct information
807 #self.fields.SetSelection(self.__cur_field)
808 #self.__SelectField(self.__cur_field, group = group)
809
810 #self.classGrid.SelectGroup(group)
811
812 controlBox.Add(self.classGrid, 1, wxGROW, 0)
813
814
815
816 controlBox.Add(controlButtonBox, 0, wxGROW, 10)
817 panelBox.Add(controlBox, 1, wxGROW, 10)
818
819 EVT_BUTTON(self, ID_CLASSIFY_ADD, self._OnAdd)
820 EVT_BUTTON(self, ID_CLASSIFY_EDITSYM, self._OnEditSymbol)
821 EVT_BUTTON(self, ID_CLASSIFY_REMOVE, self._OnRemove)
822 EVT_BUTTON(self, ID_CLASSIFY_GENCLASS, self._OnGenClass)
823 EVT_BUTTON(self, ID_CLASSIFY_MOVEUP, self._OnMoveUp)
824 EVT_BUTTON(self, ID_CLASSIFY_MOVEDOWN, self._OnMoveDown)
825
826 ###########
827
828 buttonBox = wxBoxSizer(wxHORIZONTAL)
829 buttonBox.Add(wxButton(panel, ID_CLASSIFY_TRY, _("Try")),
830 0, wxALL, 4)
831 buttonBox.Add(60, 20, 0, wxALL, 4)
832 buttonBox.Add(wxButton(panel, ID_CLASSIFY_REVERT, _("Revert")),
833 0, wxALL, 4)
834 buttonBox.Add(60, 20, 0, wxALL, 4)
835 buttonBox.Add(wxButton(panel, ID_CLASSIFY_OK, _("OK")),
836 0, wxALL, 4)
837 buttonBox.Add(60, 20, 0, wxALL, 4)
838 buttonBox.Add(wxButton(panel, ID_CLASSIFY_CLOSE, _("Close")),
839 0, wxALL, 4)
840 panelBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 0)
841
842 EVT_BUTTON(self, ID_CLASSIFY_OK, self._OnOK)
843 EVT_BUTTON(self, ID_CLASSIFY_TRY, self._OnTry)
844 EVT_BUTTON(self, ID_CLASSIFY_CLOSE, self._OnCloseBtn)
845 EVT_BUTTON(self, ID_CLASSIFY_REVERT, self._OnRevert)
846
847 ###########
848
849
850 panel.SetAutoLayout(True)
851 panel.SetSizer(panelBox)
852 panelBox.SetSizeHints(panel)
853
854 topBox.Add(panel, 1, wxGROW, 0)
855 panelBox.SetSizeHints(self)
856 self.SetAutoLayout(True)
857 self.SetSizer(topBox)
858
859 #self.Fit()
860 ######################
861
862 self.haveApplied = False
863
864 def EditSymbol(self, row):
865 table = self.classGrid.GetTable()
866 prop = table.GetValueAsCustom(row, COL_SYMBOL, None)
867
868 # get a new ClassGroupProperties object and copy the
869 # values over to our current object
870 propDlg = SelectPropertiesDialog(NULL, prop, self.layer.ShapeType())
871
872 self.Enable(False)
873 if propDlg.ShowModal() == wxID_OK:
874 new_prop = propDlg.GetClassGroupProperties()
875 table.SetValueAsCustom(row, COL_SYMBOL, None, new_prop)
876 self.Enable(True)
877 propDlg.Destroy()
878
879 def _SetClassification(self, clazz):
880
881 self.fields.SetClientData(self.__cur_field, clazz)
882 self.classGrid.GetTable().SetClassification(clazz)
883
884 def __BuildClassification(self, fieldIndex, copyClass = False):
885
886 # numRows = self.classGrid.GetNumberRows()
887 # assert numRows > 0 # there should always be a default row
888
889 # clazz = Classification()
890 if fieldIndex == 0:
891 fieldName = None
892 fieldType = None
893 else:
894 fieldName = self.fields.GetString(fieldIndex)
895 fieldType = self.layer.GetFieldType(fieldName)
896
897 clazz = self.classGrid.GetTable().GetClassification()
898
899 if copyClass:
900 clazz = copy.deepcopy(clazz)
901
902 clazz.SetField(fieldName)
903 clazz.SetFieldType(fieldType)
904
905
906 # table = self.classGrid.GetTable()
907 # clazz.SetDefaultGroup(table.GetClassGroup(0))
908
909 # for i in range(1, numRows):
910 # clazz.AppendGroup(table.GetClassGroup(i))
911
912 return clazz
913
914 def __SetGridTable(self, fieldIndex, group = None):
915
916 clazz = self.fields.GetClientData(fieldIndex)
917
918 if clazz is None:
919 clazz = Classification()
920 clazz.SetDefaultGroup(
921 ClassGroupDefault(
922 self.layer.GetClassification().
923 GetDefaultGroup().GetProperties()))
924
925 fieldName = self.fields.GetString(fieldIndex)
926 fieldType = self.layer.GetFieldType(fieldName)
927 clazz.SetFieldType(fieldType)
928
929 self.classGrid.CreateTable(clazz, self.layer.ShapeType(), group)
930
931 def __SetFieldTypeText(self, fieldIndex):
932 fieldName = self.fields.GetString(fieldIndex)
933 fieldType = self.layer.GetFieldType(fieldName)
934
935 assert Classifier.type2string.has_key(fieldType)
936
937 text = Classifier.type2string[fieldType]
938
939 self.fieldTypeText.SetLabel(_("Field Type: %s") % text)
940
941 def __SelectField(self, newIndex, oldIndex = -1, group = None):
942 """This method assumes that the current selection for the
943 combo has already been set by a call to SetSelection().
944 """
945
946 assert oldIndex >= -1
947
948 if oldIndex != -1:
949 clazz = self.__BuildClassification(oldIndex)
950 self.fields.SetClientData(oldIndex, clazz)
951
952 self.__SetGridTable(newIndex, group)
953
954 enabled = newIndex != 0
955
956 for b in self.controlButtons:
957 b.Enable(enabled)
958
959 self.__SetFieldTypeText(newIndex)
960
961
962 def _OnEditSymbol(self, event):
963 sel = self.classGrid.GetCurrentSelection()
964
965 if len(sel) == 1:
966 self.EditSymbol(sel[0])
967
968 def _OnFieldSelect(self, event):
969 index = self.fields.GetSelection()
970 self.__SelectField(index, self.__cur_field)
971 self.__cur_field = index
972
973 def _OnTry(self, event):
974 """Put the data from the table into a new Classification and hand
975 it to the layer.
976 """
977
978 clazz = self.fields.GetClientData(self.__cur_field)
979
980 #
981 # only build the classification if there wasn't one to
982 # to begin with or it has been modified
983 #
984 if clazz is None or self.classGrid.GetTable().IsModified():
985 clazz = self.__BuildClassification(self.__cur_field, True)
986
987 self.layer.SetClassification(clazz)
988
989 self.haveApplied = True
990
991 def _OnOK(self, event):
992 self._OnTry(event)
993 self.Close()
994
995 def _OnCloseBtn(self, event):
996 """Close is similar to Cancel except that any changes that were
997 made and applied remain applied, but the currently displayed
998 classification is discarded.
999 """
1000
1001 self.Close()
1002
1003 def _OnRevert(self, event):
1004 """The layer's current classification stays the same."""
1005 if self.haveApplied:
1006 self.layer.SetClassification(self.originalClass)
1007
1008 #self.Close()
1009
1010 def _OnAdd(self, event):
1011 self.classGrid.AppendRows()
1012
1013 def _OnRemove(self, event):
1014 self.classGrid.DeleteSelectedRows()
1015
1016 def _OnGenClass(self, event):
1017
1018 #if self.genDlg is None:
1019 self.genDlg = ClassGenDialog(self, self.layer,
1020 self.fields.GetString(self.__cur_field))
1021
1022 EVT_CLOSE(self.genDlg, self._OnGenDialogClose)
1023
1024 self.fields.Enable(False)
1025 self.controlButtons[BTN_EDIT].Enable(False)
1026 self.controlButtons[BTN_GEN].Enable(False)
1027
1028 self.genDlg.Show()
1029 #if self.genDlg.ShowModal() == wxID_OK:
1030 # clazz = self.genDlg.GetClassification()
1031 # self.fields.SetClientData(self.__cur_field, clazz)
1032 # self.classGrid.GetTable().SetClassification(clazz)
1033 #self.Enable(True)
1034 #self.genDlg.Destroy()
1035
1036 def _OnGenDialogClose(self, event):
1037 self.genDlg.Destroy()
1038
1039 self.fields.Enable(True)
1040 self.controlButtons[BTN_EDIT].Enable(True)
1041 self.controlButtons[BTN_GEN].Enable(True)
1042
1043 def _OnMoveUp(self, event):
1044 sel = self.classGrid.GetCurrentSelection()
1045
1046 if len(sel) == 1:
1047 i = sel[0]
1048 if i > 1:
1049 table = self.classGrid.GetTable()
1050 x = table.GetClassGroup(i - 1)
1051 y = table.GetClassGroup(i)
1052 table.SetClassGroup(i - 1, y)
1053 table.SetClassGroup(i, x)
1054 self.classGrid.ClearSelection()
1055 self.classGrid.SelectRow(i - 1)
1056 self.classGrid.MakeCellVisible(i - 1, 0)
1057
1058 def _OnMoveDown(self, event):
1059 sel = self.classGrid.GetCurrentSelection()
1060
1061 if len(sel) == 1:
1062 i = sel[0]
1063 table = self.classGrid.GetTable()
1064 if 0 < i < table.GetNumberRows() - 1:
1065 x = table.GetClassGroup(i)
1066 y = table.GetClassGroup(i + 1)
1067 table.SetClassGroup(i, y)
1068 table.SetClassGroup(i + 1, x)
1069 self.classGrid.ClearSelection()
1070 self.classGrid.SelectRow(i + 1)
1071 self.classGrid.MakeCellVisible(i + 1, 0)
1072
1073
1074 ID_SELPROP_OK = 4001
1075 ID_SELPROP_CANCEL = 4002
1076 ID_SELPROP_SPINCTRL = 4002
1077 ID_SELPROP_PREVIEW = 4003
1078 ID_SELPROP_STROKECLR = 4004
1079 ID_SELPROP_FILLCLR = 4005
1080 ID_SELPROP_STROKECLRTRANS = 4006
1081 ID_SELPROP_FILLCLRTRANS = 4007
1082
1083 class SelectPropertiesDialog(wxDialog):
1084
1085 def __init__(self, parent, prop, shapeType):
1086 wxDialog.__init__(self, parent, -1, _("Select Properties"),
1087 style = wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
1088
1089 self.prop = ClassGroupProperties(prop)
1090
1091 topBox = wxBoxSizer(wxVERTICAL)
1092
1093 itemBox = wxBoxSizer(wxHORIZONTAL)
1094
1095 # preview box
1096 previewBox = wxBoxSizer(wxVERTICAL)
1097 previewBox.Add(wxStaticText(self, -1, _("Preview:")),
1098 0, wxALIGN_LEFT | wxALL, 4)
1099
1100 self.previewWin = ClassGroupPropertiesCtrl(
1101 self, ID_SELPROP_PREVIEW, self.prop, shapeType,
1102 (40, 40), wxSIMPLE_BORDER)
1103
1104 self.previewWin.AllowEdit(False)
1105
1106 previewBox.Add(self.previewWin, 1, wxGROW | wxALL, 4)
1107
1108 itemBox.Add(previewBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
1109
1110 # control box
1111 ctrlBox = wxBoxSizer(wxVERTICAL)
1112
1113 lineColorBox = wxBoxSizer(wxHORIZONTAL)
1114 lineColorBox.Add(
1115 wxButton(self, ID_SELPROP_STROKECLR, _("Change Line Color")),
1116 1, wxALL | wxGROW, 4)
1117 EVT_BUTTON(self, ID_SELPROP_STROKECLR, self._OnChangeLineColor)
1118
1119 lineColorBox.Add(
1120 wxButton(self, ID_SELPROP_STROKECLRTRANS, _("Transparent")),
1121 1, wxALL | wxGROW, 4)
1122 EVT_BUTTON(self, ID_SELPROP_STROKECLRTRANS,
1123 self._OnChangeLineColorTrans)
1124
1125 ctrlBox.Add(lineColorBox, 0,
1126 wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
1127
1128 if shapeType != SHAPETYPE_ARC:
1129 fillColorBox = wxBoxSizer(wxHORIZONTAL)
1130 fillColorBox.Add(
1131 wxButton(self, ID_SELPROP_FILLCLR, _("Change Fill Color")),
1132 1, wxALL | wxGROW, 4)
1133 EVT_BUTTON(self, ID_SELPROP_FILLCLR, self._OnChangeFillColor)
1134 fillColorBox.Add(
1135 wxButton(self, ID_SELPROP_FILLCLRTRANS, _("Transparent")),
1136 1, wxALL | wxGROW, 4)
1137 EVT_BUTTON(self, ID_SELPROP_FILLCLRTRANS,
1138 self._OnChangeFillColorTrans)
1139 ctrlBox.Add(fillColorBox, 0,
1140 wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
1141
1142 spinBox = wxBoxSizer(wxHORIZONTAL)
1143 spinBox.Add(wxStaticText(self, -1, _("Line Width: ")),
1144 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
1145 self.spinCtrl = wxSpinCtrl(self, ID_SELPROP_SPINCTRL,
1146 min=1, max=10,
1147 value=str(prop.GetLineWidth()),
1148 initial=prop.GetLineWidth())
1149
1150 EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL, self._OnSpin)
1151
1152 spinBox.Add(self.spinCtrl, 0, wxALIGN_LEFT | wxALL, 4)
1153
1154 ctrlBox.Add(spinBox, 0, wxALIGN_RIGHT | wxALL, 0)
1155 itemBox.Add(ctrlBox, 0, wxALIGN_RIGHT | wxALL | wxGROW, 0)
1156 topBox.Add(itemBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
1157
1158 #
1159 # Control buttons:
1160 #
1161 buttonBox = wxBoxSizer(wxHORIZONTAL)
1162 buttonBox.Add(wxButton(self, ID_SELPROP_OK, _("OK")),
1163 0, wxALL, 4)
1164 buttonBox.Add(wxButton(self, ID_SELPROP_CANCEL, _("Cancel")),
1165 0, wxALL, 4)
1166 topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)
1167
1168 EVT_BUTTON(self, ID_SELPROP_OK, self._OnOK)
1169 EVT_BUTTON(self, ID_SELPROP_CANCEL, self._OnCancel)
1170
1171 self.SetAutoLayout(True)
1172 self.SetSizer(topBox)
1173 topBox.Fit(self)
1174 topBox.SetSizeHints(self)
1175
1176 def _OnOK(self, event):
1177 self.EndModal(wxID_OK)
1178
1179 def _OnCancel(self, event):
1180 self.EndModal(wxID_CANCEL)
1181
1182 def _OnSpin(self, event):
1183 self.prop.SetLineWidth(self.spinCtrl.GetValue())
1184 self.previewWin.Refresh()
1185
1186 def __GetColor(self, cur):
1187 dialog = wxColourDialog(self)
1188 if cur is not Color.Transparent:
1189 dialog.GetColourData().SetColour(Color2wxColour(cur))
1190
1191 ret = None
1192 if dialog.ShowModal() == wxID_OK:
1193 ret = wxColour2Color(dialog.GetColourData().GetColour())
1194
1195 dialog.Destroy()
1196
1197 return ret
1198
1199 def _OnChangeLineColor(self, event):
1200 clr = self.__GetColor(self.prop.GetLineColor())
1201 if clr is not None:
1202 self.prop.SetLineColor(clr)
1203 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1204
1205 def _OnChangeLineColorTrans(self, event):
1206 self.prop.SetLineColor(Color.Transparent)
1207 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1208
1209 def _OnChangeFillColor(self, event):
1210 clr = self.__GetColor(self.prop.GetFill())
1211 if clr is not None:
1212 self.prop.SetFill(clr)
1213 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1214
1215 def _OnChangeFillColorTrans(self, event):
1216 self.prop.SetFill(Color.Transparent)
1217 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1218
1219 def GetClassGroupProperties(self):
1220 return self.prop
1221
1222
1223 class ClassDataPreviewWindow(wxWindow):
1224
1225 def __init__(self, rect, prop, shapeType,
1226 parent = None, id = -1, size = wxDefaultSize):
1227 if parent is not None:
1228 wxWindow.__init__(self, parent, id, (0, 0), size)
1229 EVT_PAINT(self, self._OnPaint)
1230
1231 self.rect = rect
1232
1233 self.prop = prop
1234 self.shapeType = shapeType
1235 self.previewer = ClassDataPreviewer()
1236
1237 def GetProperties():
1238 return self.prop
1239
1240 def _OnPaint(self, event):
1241 dc = wxPaintDC(self)
1242
1243 # XXX: this doesn't seem to be having an effect:
1244 dc.DestroyClippingRegion()
1245
1246 if self.rect is None:
1247 w, h = self.GetSize()
1248 rect = wxRect(0, 0, w, h)
1249 else:
1250 rect = self.rect
1251
1252 self.previewer.Draw(dc, rect, self.prop, self.shapeType)
1253
1254 class ClassDataPreviewer:
1255
1256 def Draw(self, dc, rect, prop, shapeType):
1257
1258 assert dc is not None
1259 assert isinstance(prop, ClassGroupProperties)
1260
1261 if rect is None:
1262 x = 0
1263 y = 0
1264 w, h = dc.GetSize()
1265 else:
1266 x = rect.GetX()
1267 y = rect.GetY()
1268 w = rect.GetWidth()
1269 h = rect.GetHeight()
1270
1271 stroke = prop.GetLineColor()
1272 if stroke is Color.Transparent:
1273 pen = wxTRANSPARENT_PEN
1274 else:
1275 pen = wxPen(Color2wxColour(stroke),
1276 prop.GetLineWidth(),
1277 wxSOLID)
1278
1279 stroke = prop.GetFill()
1280 if stroke is Color.Transparent:
1281 brush = wxTRANSPARENT_BRUSH
1282 else:
1283 brush = wxBrush(Color2wxColour(stroke), wxSOLID)
1284
1285 dc.SetPen(pen)
1286 dc.SetBrush(brush)
1287
1288 if shapeType == SHAPETYPE_ARC:
1289 dc.DrawSpline([wxPoint(x, y + h),
1290 wxPoint(x + w/2, y + h/4),
1291 wxPoint(x + w/2, y + h/4*3),
1292 wxPoint(x + w, y)])
1293
1294 elif shapeType == SHAPETYPE_POINT:
1295
1296 dc.DrawCircle(x + w/2, y + h/2,
1297 (min(w, h) - prop.GetLineWidth())/2)
1298
1299 elif shapeType == SHAPETYPE_POLYGON:
1300 dc.DrawRectangle(x, y, w, h)
1301
1302 class ClassRenderer(wxPyGridCellRenderer):
1303
1304 def __init__(self, shapeType):
1305 wxPyGridCellRenderer.__init__(self)
1306 self.shapeType = shapeType
1307 self.previewer = ClassDataPreviewer()
1308
1309 def Draw(self, grid, attr, dc, rect, row, col, isSelected):
1310 data = grid.GetTable().GetClassGroup(row)
1311
1312 dc.SetClippingRegion(rect.GetX(), rect.GetY(),
1313 rect.GetWidth(), rect.GetHeight())
1314 dc.SetPen(wxPen(wxLIGHT_GREY))
1315 dc.SetBrush(wxBrush(wxLIGHT_GREY, wxSOLID))
1316 dc.DrawRectangle(rect.GetX(), rect.GetY(),
1317 rect.GetWidth(), rect.GetHeight())
1318
1319 if not isinstance(data, ClassGroupMap):
1320 self.previewer.Draw(dc, rect, data.GetProperties(), self.shapeType)
1321
1322 if isSelected:
1323 dc.SetPen(wxPen(wxBLACK, 1, wxSOLID))
1324 dc.SetBrush(wxTRANSPARENT_BRUSH)
1325
1326 dc.DrawRectangle(rect.GetX(), rect.GetY(),
1327 rect.GetWidth(), rect.GetHeight())
1328
1329 dc.DestroyClippingRegion()
1330
1331
1332 class ClassGroupPropertiesCtrl(wxWindow, wxControl):
1333
1334 def __init__(self, parent, id, props, shapeType,
1335 size = wxDefaultSize, style = 0):
1336
1337 wxWindow.__init__(self, parent, id, size = size, style = style)
1338
1339 self.SetProperties(props)
1340 self.SetShapeType(shapeType)
1341 self.AllowEdit(True)
1342
1343 EVT_PAINT(self, self._OnPaint)
1344 EVT_LEFT_DCLICK(self, self._OnLeftDClick)
1345
1346 self.previewer = ClassDataPreviewer()
1347
1348 def _OnPaint(self, event):
1349 dc = wxPaintDC(self)
1350
1351 # XXX: this doesn't seem to be having an effect:
1352 dc.DestroyClippingRegion()
1353
1354 w, h = self.GetClientSize()
1355
1356 self.previewer.Draw(dc,
1357 wxRect(0, 0, w, h),
1358 self.GetProperties(),
1359 self.GetShapeType())
1360
1361
1362 def GetProperties(self):
1363 return self.props
1364
1365 def SetProperties(self, props):
1366 self.props = props
1367 self.Refresh()
1368
1369 def GetShapeType(self):
1370 return self.shapeType
1371
1372 def SetShapeType(self, shapeType):
1373 self.shapeType = shapeType
1374 self.Refresh()
1375
1376 def AllowEdit(self, allow):
1377 self.allowEdit = allow
1378
1379 def DoEdit(self):
1380 if not self.allowEdit: return
1381
1382 propDlg = SelectPropertiesDialog(NULL,
1383 self.GetProperties(),
1384 self.GetShapeType())
1385
1386 if propDlg.ShowModal() == wxID_OK:
1387 new_prop = propDlg.GetClassGroupProperties()
1388 self.SetProperties(new_prop)
1389 self.Refresh()
1390
1391 propDlg.Destroy()
1392
1393 def _OnLeftDClick(self, event):
1394 self.DoEdit()

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26