/[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 630 - (show annotations)
Wed Apr 9 10:10:06 2003 UTC (21 years, 11 months ago) by jonathan
File MIME type: text/x-python
File size: 43670 byte(s)
Removed a lot of debugging code.
(Classifier._SetClassification): Callback method so that the
        class generator can set the classification in the grid.
(ClassGroupPropertiesCtrl): New. Encapsulates the drawing and
        editing of a group properties class into a wxWindows control.

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