/[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 878 - (show annotations)
Fri May 9 16:32:31 2003 UTC (21 years, 10 months ago) by jonathan
File MIME type: text/x-python
File size: 44437 byte(s)
Explicit imports.
(ClassTable.GetValueAsCust, ClassTable.__ParseInput,
ClassTable.SetValueAsCustom): Reworked to use new Range class.

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