/[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 1058 - (show annotations)
Tue May 27 11:30:29 2003 UTC (21 years, 9 months ago) by frank
File MIME type: text/x-python
File size: 44975 byte(s)
Dialog derived from NonModalNonParentDialog

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, RasterLayer, \
32 SHAPETYPE_ARC, SHAPETYPE_POLYGON, SHAPETYPE_POINT
33
34 from Thuban.UI.classgen import ClassGenDialog, ClassGenerator
35
36 from dialogs import NonModalNonParentDialog
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(NonModalNonParentDialog):
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 NonModalNonParentDialog.__init__(self, parent, name, "")
682
683 self.__SetTitle(layer.Title())
684
685 self.layer = layer
686
687
688 self.genDlg = None
689
690 ############################
691 # Create the controls
692 #
693
694 panel = wxPanel(self, -1)
695
696 text_title = wxTextCtrl(panel, ID_PROPERTY_TITLE, layer.Title())
697 self.fieldTypeText = wxStaticText(panel, -1, "")
698
699 if layer.HasClassification():
700 self.originalClass = self.layer.GetClassification()
701 field = self.originalClass.GetField()
702 fieldType = self.originalClass.GetFieldType()
703
704 #
705 # make field choice box
706 #
707 self.fields = wxChoice(panel, ID_PROPERTY_SELECT,)
708
709 self.num_cols = layer.table.NumColumns()
710 # just assume the first field in case one hasn't been
711 # specified in the file.
712 self.__cur_field = 0
713
714 self.fields.Append("<None>")
715
716 if self.originalClass.GetFieldType() is None:
717 self.fields.SetClientData(0, copy.deepcopy(self.originalClass))
718 else:
719 self.fields.SetClientData(0, None)
720
721 for i in range(self.num_cols):
722 name = layer.table.Column(i).name
723 self.fields.Append(name)
724
725 if name == field:
726 self.__cur_field = i + 1
727 self.fields.SetClientData(i + 1,
728 copy.deepcopy(self.originalClass))
729 else:
730 self.fields.SetClientData(i + 1, None)
731
732 button_gen = wxButton(panel, ID_PROPERTY_GENCLASS,
733 _("Generate Class"))
734 button_add = wxButton(panel, ID_PROPERTY_ADD,
735 _("Add"))
736 button_moveup = wxButton(panel, ID_PROPERTY_MOVEUP,
737 _("Move Up"))
738 button_movedown = wxButton(panel, ID_PROPERTY_MOVEDOWN,
739 _("Move Down"))
740 button_edit = wxButton(panel, ID_PROPERTY_EDITSYM,
741 _("Edit Symbol"))
742 button_remove = wxButton(panel, ID_PROPERTY_REMOVE,
743 _("Remove"))
744
745 self.classGrid = ClassGrid(panel, self)
746
747 # calling __SelectField after creating the classGrid fills in the
748 # grid with the correct information
749 self.fields.SetSelection(self.__cur_field)
750 self.__SelectField(self.__cur_field, group = group)
751
752 button_try = wxButton(self, ID_PROPERTY_TRY, _("Try"))
753 button_revert = wxButton(self, ID_PROPERTY_REVERT, _("Revert"))
754 button_ok = wxButton(self, wxID_OK, _("OK"))
755 button_ok.SetDefault()
756 button_close = wxButton(self, wxID_CANCEL, _("Close"))
757
758 ############################
759 # Layout the controls
760 #
761
762 topBox = wxBoxSizer(wxVERTICAL)
763 panelBox = wxBoxSizer(wxVERTICAL)
764
765 sizer = wxBoxSizer(wxHORIZONTAL)
766 sizer.Add(wxStaticText(panel, -1, _("Title: ")),
767 0, wxALIGN_LEFT | wxALL | wxALIGN_CENTER_VERTICAL, 4)
768 sizer.Add(text_title, 1, wxGROW, 0)
769
770 panelBox.Add(sizer, 0, wxGROW, 4)
771
772 if isinstance(layer, RasterLayer):
773 type = "Image"
774 else:
775 type = layer.ShapeType()
776
777 panelBox.Add(wxStaticText(panel, -1, _("Type: %s") % type),
778 0, wxALIGN_LEFT | wxALL, 4)
779
780 if layer.HasClassification():
781
782 classBox = wxStaticBoxSizer(
783 wxStaticBox(panel, -1, _("Classification")), wxVERTICAL)
784
785
786 sizer = wxBoxSizer(wxHORIZONTAL)
787 sizer.Add(wxStaticText(panel, ID_PROPERTY_FIELDTEXT, _("Field: ")),
788 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
789 sizer.Add(self.fields, 1, wxGROW | wxALL, 4)
790
791 classBox.Add(sizer, 0, wxGROW, 4)
792
793 classBox.Add(self.fieldTypeText, 0,
794 wxGROW | wxALIGN_LEFT | wxALL | wxADJUST_MINSIZE, 4)
795
796 controlBox = wxBoxSizer(wxHORIZONTAL)
797 controlButtonBox = wxBoxSizer(wxVERTICAL)
798
799 controlButtonBox.Add(button_gen, 0, wxGROW|wxALL, 4)
800 controlButtonBox.Add(button_add, 0, wxGROW|wxALL, 4)
801 controlButtonBox.Add(button_moveup, 0, wxGROW|wxALL, 4)
802 controlButtonBox.Add(button_movedown, 0, wxGROW|wxALL, 4)
803 controlButtonBox.Add(button_edit, 0, wxGROW|wxALL, 4)
804 controlButtonBox.Add(60, 20, 0, wxGROW|wxALL|wxALIGN_BOTTOM, 4)
805 controlButtonBox.Add(button_remove, 0,
806 wxGROW|wxALL|wxALIGN_BOTTOM, 4)
807
808 controlBox.Add(self.classGrid, 1, wxGROW, 0)
809 controlBox.Add(controlButtonBox, 0, wxGROW, 10)
810
811 classBox.Add(controlBox, 1, wxGROW, 10)
812 panelBox.Add(classBox, 1, wxGROW, 0)
813
814
815 buttonBox = wxBoxSizer(wxHORIZONTAL)
816 buttonBox.Add(button_try, 0, wxRIGHT|wxEXPAND, 10)
817 buttonBox.Add(button_revert, 0, wxRIGHT|wxEXPAND, 10)
818 buttonBox.Add(button_ok, 0, wxRIGHT|wxEXPAND, 10)
819 buttonBox.Add(button_close, 0, wxRIGHT|wxEXPAND, 10)
820
821 panel.SetAutoLayout(True)
822 panel.SetSizer(panelBox)
823 panelBox.Fit(panel)
824 panelBox.SetSizeHints(panel)
825
826 topBox.Add(panel, 1, wxGROW | wxALL, 4)
827 topBox.Add(buttonBox, 0, wxALIGN_RIGHT|wxBOTTOM|wxTOP, 10)
828
829 self.SetAutoLayout(True)
830 self.SetSizer(topBox)
831 topBox.Fit(self)
832 topBox.SetSizeHints(self)
833 self.Layout()
834
835 ###########
836
837 EVT_CHOICE(self, ID_PROPERTY_SELECT, self._OnFieldSelect)
838 EVT_TEXT(self, ID_PROPERTY_TITLE, self._OnTitleChanged)
839 EVT_BUTTON(self, wxID_OK, self._OnOK)
840 EVT_BUTTON(self, ID_PROPERTY_TRY, self._OnTry)
841 EVT_BUTTON(self, wxID_CANCEL, self._OnCloseBtn)
842 EVT_BUTTON(self, ID_PROPERTY_REVERT, self._OnRevert)
843
844 EVT_BUTTON(self, ID_PROPERTY_ADD, self._OnAdd)
845 EVT_BUTTON(self, ID_PROPERTY_EDITSYM, self._OnEditSymbol)
846 EVT_BUTTON(self, ID_PROPERTY_REMOVE, self._OnRemove)
847 EVT_BUTTON(self, ID_PROPERTY_GENCLASS, self._OnGenClass)
848 EVT_BUTTON(self, ID_PROPERTY_MOVEUP, self._OnMoveUp)
849 EVT_BUTTON(self, ID_PROPERTY_MOVEDOWN, self._OnMoveDown)
850
851 ######################
852
853 text_title.SetFocus()
854 self.haveApplied = False
855
856 def EditSymbol(self, row):
857 table = self.classGrid.GetTable()
858 prop = table.GetValueAsCustom(row, COL_SYMBOL, None)
859
860 # get a new ClassGroupProperties object and copy the
861 # values over to our current object
862 propDlg = SelectPropertiesDialog(NULL, prop, self.layer.ShapeType())
863
864 self.Enable(False)
865 if propDlg.ShowModal() == wxID_OK:
866 new_prop = propDlg.GetClassGroupProperties()
867 table.SetValueAsCustom(row, COL_SYMBOL, None, new_prop)
868 self.Enable(True)
869 propDlg.Destroy()
870
871 def _SetClassification(self, clazz):
872
873 self.fields.SetClientData(self.__cur_field, clazz)
874 self.classGrid.GetTable().SetClassification(clazz)
875
876 def __BuildClassification(self, fieldIndex, copyClass = False):
877
878 # numRows = self.classGrid.GetNumberRows()
879 # assert numRows > 0 # there should always be a default row
880
881 # clazz = Classification()
882 if fieldIndex == 0:
883 fieldName = None
884 fieldType = None
885 else:
886 fieldName = self.fields.GetString(fieldIndex)
887 fieldType = self.layer.GetFieldType(fieldName)
888
889 clazz = self.classGrid.GetTable().GetClassification()
890
891 if copyClass:
892 clazz = copy.deepcopy(clazz)
893
894 clazz.SetField(fieldName)
895 clazz.SetFieldType(fieldType)
896
897
898 # table = self.classGrid.GetTable()
899 # clazz.SetDefaultGroup(table.GetClassGroup(0))
900
901 # for i in range(1, numRows):
902 # clazz.AppendGroup(table.GetClassGroup(i))
903
904 return clazz
905
906 def __SetGridTable(self, fieldIndex, group = None):
907
908 clazz = self.fields.GetClientData(fieldIndex)
909
910 if clazz is None:
911 clazz = Classification()
912 clazz.SetDefaultGroup(
913 ClassGroupDefault(
914 self.layer.GetClassification().
915 GetDefaultGroup().GetProperties()))
916
917 fieldName = self.fields.GetString(fieldIndex)
918 fieldType = self.layer.GetFieldType(fieldName)
919 clazz.SetFieldType(fieldType)
920
921 self.classGrid.CreateTable(clazz, self.layer.ShapeType(), group)
922
923 def __SetFieldTypeText(self, fieldIndex):
924 fieldName = self.fields.GetString(fieldIndex)
925 fieldType = self.layer.GetFieldType(fieldName)
926
927 assert Classifier.type2string.has_key(fieldType)
928
929 text = Classifier.type2string[fieldType]
930
931 self.fieldTypeText.SetLabel(_("Data Type: %s") % text)
932
933 def __SelectField(self, newIndex, oldIndex = -1, group = None):
934 """This method assumes that the current selection for the
935 combo has already been set by a call to SetSelection().
936 """
937
938 assert oldIndex >= -1
939
940 if oldIndex != -1:
941 clazz = self.__BuildClassification(oldIndex)
942 self.fields.SetClientData(oldIndex, clazz)
943
944 self.__SetGridTable(newIndex, group)
945
946 self.__EnableButtons(EB_SELECT_FIELD, newIndex != 0)
947
948 self.__SetFieldTypeText(newIndex)
949
950 def __SetTitle(self, title):
951 if title != "":
952 title = ": " + title
953
954 self.SetTitle(_("Layer Properties") + title)
955
956 def _OnEditSymbol(self, event):
957 sel = self.classGrid.GetCurrentSelection()
958
959 if len(sel) == 1:
960 self.EditSymbol(sel[0])
961
962 def _OnFieldSelect(self, event):
963 index = self.fields.GetSelection()
964 self.__SelectField(index, self.__cur_field)
965 self.__cur_field = index
966
967 def _OnTry(self, event):
968 """Put the data from the table into a new Classification and hand
969 it to the layer.
970 """
971
972 if self.layer.HasClassification():
973 clazz = self.fields.GetClientData(self.__cur_field)
974
975 #
976 # only build the classification if there wasn't one to
977 # to begin with or it has been modified
978 #
979 self.classGrid.SaveEditControlValue()
980 if clazz is None or self.classGrid.GetTable().IsModified():
981 clazz = self.__BuildClassification(self.__cur_field, True)
982
983 self.layer.SetClassification(clazz)
984
985 self.haveApplied = True
986
987 def _OnOK(self, event):
988 self._OnTry(event)
989 self.Close()
990
991 def OnClose(self, event):
992 NonModalNonParentDialog.OnClose(self, event)
993
994 def _OnCloseBtn(self, event):
995 """Close is similar to Cancel except that any changes that were
996 made and applied remain applied, but the currently displayed
997 classification is discarded.
998 """
999
1000 self.Close()
1001
1002 def _OnRevert(self, event):
1003 """The layer's current classification stays the same."""
1004 if self.haveApplied:
1005 self.layer.SetClassification(self.originalClass)
1006
1007 #self.Close()
1008
1009 def _OnAdd(self, event):
1010 self.classGrid.AppendRows()
1011
1012 def _OnRemove(self, event):
1013 self.classGrid.DeleteSelectedRows()
1014
1015 def _OnGenClass(self, event):
1016
1017 self.genDlg = ClassGenDialog(self, self.layer,
1018 self.fields.GetString(self.__cur_field))
1019
1020 EVT_CLOSE(self.genDlg, self._OnGenDialogClose)
1021
1022 self.__EnableButtons(EB_GEN_CLASS, False)
1023
1024 self.genDlg.Show()
1025
1026 def _OnGenDialogClose(self, event):
1027 self.genDlg.Destroy()
1028 self.__EnableButtons(EB_GEN_CLASS, True)
1029
1030 def _OnMoveUp(self, event):
1031 sel = self.classGrid.GetCurrentSelection()
1032
1033 if len(sel) == 1:
1034 i = sel[0]
1035 if i > 1:
1036 table = self.classGrid.GetTable()
1037 x = table.GetClassGroup(i - 1)
1038 y = table.GetClassGroup(i)
1039 table.SetClassGroup(i - 1, y)
1040 table.SetClassGroup(i, x)
1041 self.classGrid.ClearSelection()
1042 self.classGrid.SelectRow(i - 1)
1043 self.classGrid.MakeCellVisible(i - 1, 0)
1044
1045 def _OnMoveDown(self, event):
1046 sel = self.classGrid.GetCurrentSelection()
1047
1048 if len(sel) == 1:
1049 i = sel[0]
1050 table = self.classGrid.GetTable()
1051 if 0 < i < table.GetNumberRows() - 1:
1052 x = table.GetClassGroup(i)
1053 y = table.GetClassGroup(i + 1)
1054 table.SetClassGroup(i, y)
1055 table.SetClassGroup(i + 1, x)
1056 self.classGrid.ClearSelection()
1057 self.classGrid.SelectRow(i + 1)
1058 self.classGrid.MakeCellVisible(i + 1, 0)
1059
1060 def _OnTitleChanged(self, event):
1061 obj = event.GetEventObject()
1062
1063 self.layer.SetTitle(obj.GetValue())
1064 self.__SetTitle(self.layer.Title())
1065
1066 self.__EnableButtons(EB_LAYER_TITLE, self.layer.Title() != "")
1067
1068 def __EnableButtons(self, case, enable):
1069
1070 if case == EB_LAYER_TITLE:
1071 list = (wxID_OK,
1072 wxID_CANCEL)
1073
1074 elif case == EB_SELECT_FIELD:
1075 list = (ID_PROPERTY_GENCLASS,
1076 ID_PROPERTY_ADD,
1077 ID_PROPERTY_MOVEUP,
1078 ID_PROPERTY_MOVEDOWN,
1079 ID_PROPERTY_EDITSYM,
1080 ID_PROPERTY_REMOVE)
1081
1082 elif case == EB_GEN_CLASS:
1083 list = (ID_PROPERTY_SELECT,
1084 ID_PROPERTY_FIELDTEXT,
1085 ID_PROPERTY_GENCLASS,
1086 ID_PROPERTY_EDITSYM)
1087
1088 for id in list:
1089 self.FindWindowById(id).Enable(enable)
1090
1091 ID_SELPROP_SPINCTRL = 4002
1092 ID_SELPROP_PREVIEW = 4003
1093 ID_SELPROP_STROKECLR = 4004
1094 ID_SELPROP_FILLCLR = 4005
1095 ID_SELPROP_STROKECLRTRANS = 4006
1096 ID_SELPROP_FILLCLRTRANS = 4007
1097
1098 class SelectPropertiesDialog(wxDialog):
1099
1100 def __init__(self, parent, prop, shapeType):
1101 wxDialog.__init__(self, parent, -1, _("Select Properties"),
1102 style = wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
1103
1104 self.prop = ClassGroupProperties(prop)
1105
1106 topBox = wxBoxSizer(wxVERTICAL)
1107
1108 itemBox = wxBoxSizer(wxHORIZONTAL)
1109
1110 # preview box
1111 previewBox = wxBoxSizer(wxVERTICAL)
1112 previewBox.Add(wxStaticText(self, -1, _("Preview:")),
1113 0, wxALIGN_LEFT | wxALL, 4)
1114
1115 self.previewWin = ClassGroupPropertiesCtrl(
1116 self, ID_SELPROP_PREVIEW, self.prop, shapeType,
1117 (40, 40), wxSIMPLE_BORDER)
1118
1119 self.previewWin.AllowEdit(False)
1120
1121 previewBox.Add(self.previewWin, 1, wxGROW | wxALL, 4)
1122
1123 itemBox.Add(previewBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
1124
1125 # control box
1126 ctrlBox = wxBoxSizer(wxVERTICAL)
1127
1128 lineColorBox = wxBoxSizer(wxHORIZONTAL)
1129 button = wxButton(self, ID_SELPROP_STROKECLR, _("Change Line Color"))
1130 button.SetFocus()
1131 lineColorBox.Add(button, 1, wxALL | wxGROW, 4)
1132 EVT_BUTTON(self, ID_SELPROP_STROKECLR, self._OnChangeLineColor)
1133
1134 lineColorBox.Add(
1135 wxButton(self, ID_SELPROP_STROKECLRTRANS, _("Transparent")),
1136 1, wxALL | wxGROW, 4)
1137 EVT_BUTTON(self, ID_SELPROP_STROKECLRTRANS,
1138 self._OnChangeLineColorTrans)
1139
1140 ctrlBox.Add(lineColorBox, 0,
1141 wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
1142
1143 if shapeType != SHAPETYPE_ARC:
1144 fillColorBox = wxBoxSizer(wxHORIZONTAL)
1145 fillColorBox.Add(
1146 wxButton(self, ID_SELPROP_FILLCLR, _("Change Fill Color")),
1147 1, wxALL | wxGROW, 4)
1148 EVT_BUTTON(self, ID_SELPROP_FILLCLR, self._OnChangeFillColor)
1149 fillColorBox.Add(
1150 wxButton(self, ID_SELPROP_FILLCLRTRANS, _("Transparent")),
1151 1, wxALL | wxGROW, 4)
1152 EVT_BUTTON(self, ID_SELPROP_FILLCLRTRANS,
1153 self._OnChangeFillColorTrans)
1154 ctrlBox.Add(fillColorBox, 0,
1155 wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
1156
1157 spinBox = wxBoxSizer(wxHORIZONTAL)
1158 spinBox.Add(wxStaticText(self, -1, _("Line Width: ")),
1159 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
1160 self.spinCtrl = wxSpinCtrl(self, ID_SELPROP_SPINCTRL,
1161 min=1, max=10,
1162 value=str(prop.GetLineWidth()),
1163 initial=prop.GetLineWidth())
1164
1165 EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL, self._OnSpin)
1166
1167 spinBox.Add(self.spinCtrl, 0, wxALIGN_LEFT | wxALL, 4)
1168
1169 ctrlBox.Add(spinBox, 0, wxALIGN_RIGHT | wxALL, 0)
1170 itemBox.Add(ctrlBox, 0, wxALIGN_RIGHT | wxALL | wxGROW, 0)
1171 topBox.Add(itemBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
1172
1173 #
1174 # Control buttons:
1175 #
1176 buttonBox = wxBoxSizer(wxHORIZONTAL)
1177 button_ok = wxButton(self, wxID_OK, _("OK"))
1178 button_ok.SetDefault()
1179 buttonBox.Add(button_ok, 0, wxRIGHT|wxEXPAND, 10)
1180 buttonBox.Add(wxButton(self, wxID_CANCEL, _("Cancel")),
1181 0, wxRIGHT|wxEXPAND, 10)
1182 topBox.Add(buttonBox, 0, wxALIGN_RIGHT|wxBOTTOM|wxTOP, 10)
1183
1184 #EVT_BUTTON(self, wxID_OK, self._OnOK)
1185 #EVT_BUTTON(self, ID_SELPROP_CANCEL, self._OnCancel)
1186
1187 self.SetAutoLayout(True)
1188 self.SetSizer(topBox)
1189 topBox.Fit(self)
1190 topBox.SetSizeHints(self)
1191
1192 def OnOK(self, event):
1193 self.EndModal(wxID_OK)
1194
1195 def OnCancel(self, event):
1196 self.EndModal(wxID_CANCEL)
1197
1198 def _OnSpin(self, event):
1199 self.prop.SetLineWidth(self.spinCtrl.GetValue())
1200 self.previewWin.Refresh()
1201
1202 def __GetColor(self, cur):
1203 dialog = wxColourDialog(self)
1204 if cur is not Color.Transparent:
1205 dialog.GetColourData().SetColour(Color2wxColour(cur))
1206
1207 ret = None
1208 if dialog.ShowModal() == wxID_OK:
1209 ret = wxColour2Color(dialog.GetColourData().GetColour())
1210
1211 dialog.Destroy()
1212
1213 return ret
1214
1215 def _OnChangeLineColor(self, event):
1216 clr = self.__GetColor(self.prop.GetLineColor())
1217 if clr is not None:
1218 self.prop.SetLineColor(clr)
1219 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1220
1221 def _OnChangeLineColorTrans(self, event):
1222 self.prop.SetLineColor(Color.Transparent)
1223 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1224
1225 def _OnChangeFillColor(self, event):
1226 clr = self.__GetColor(self.prop.GetFill())
1227 if clr is not None:
1228 self.prop.SetFill(clr)
1229 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1230
1231 def _OnChangeFillColorTrans(self, event):
1232 self.prop.SetFill(Color.Transparent)
1233 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1234
1235 def GetClassGroupProperties(self):
1236 return self.prop
1237
1238
1239 class ClassDataPreviewWindow(wxWindow):
1240
1241 def __init__(self, rect, prop, shapeType,
1242 parent = None, id = -1, size = wxDefaultSize):
1243 if parent is not None:
1244 wxWindow.__init__(self, parent, id, (0, 0), size)
1245 EVT_PAINT(self, self._OnPaint)
1246
1247 self.rect = rect
1248
1249 self.prop = prop
1250 self.shapeType = shapeType
1251 self.previewer = ClassDataPreviewer()
1252
1253 def GetProperties():
1254 return self.prop
1255
1256 def _OnPaint(self, event):
1257 dc = wxPaintDC(self)
1258
1259 # XXX: this doesn't seem to be having an effect:
1260 dc.DestroyClippingRegion()
1261
1262 if self.rect is None:
1263 w, h = self.GetSize()
1264 rect = wxRect(0, 0, w, h)
1265 else:
1266 rect = self.rect
1267
1268 self.previewer.Draw(dc, rect, self.prop, self.shapeType)
1269
1270 class ClassDataPreviewer:
1271
1272 def Draw(self, dc, rect, prop, shapeType):
1273
1274 assert dc is not None
1275 assert isinstance(prop, ClassGroupProperties)
1276
1277 if rect is None:
1278 x = 0
1279 y = 0
1280 w, h = dc.GetSize()
1281 else:
1282 x = rect.GetX()
1283 y = rect.GetY()
1284 w = rect.GetWidth()
1285 h = rect.GetHeight()
1286
1287 stroke = prop.GetLineColor()
1288 if stroke is Color.Transparent:
1289 pen = wxTRANSPARENT_PEN
1290 else:
1291 pen = wxPen(Color2wxColour(stroke),
1292 prop.GetLineWidth(),
1293 wxSOLID)
1294
1295 stroke = prop.GetFill()
1296 if stroke is Color.Transparent:
1297 brush = wxTRANSPARENT_BRUSH
1298 else:
1299 brush = wxBrush(Color2wxColour(stroke), wxSOLID)
1300
1301 dc.SetPen(pen)
1302 dc.SetBrush(brush)
1303
1304 if shapeType == SHAPETYPE_ARC:
1305 dc.DrawSpline([wxPoint(x, y + h),
1306 wxPoint(x + w/2, y + h/4),
1307 wxPoint(x + w/2, y + h/4*3),
1308 wxPoint(x + w, y)])
1309
1310 elif shapeType == SHAPETYPE_POINT:
1311
1312 dc.DrawCircle(x + w/2, y + h/2,
1313 (min(w, h) - prop.GetLineWidth())/2)
1314
1315 elif shapeType == SHAPETYPE_POLYGON:
1316 dc.DrawRectangle(x, y, w, h)
1317
1318 class ClassRenderer(wxPyGridCellRenderer):
1319
1320 def __init__(self, shapeType):
1321 wxPyGridCellRenderer.__init__(self)
1322 self.shapeType = shapeType
1323 self.previewer = ClassDataPreviewer()
1324
1325 def Draw(self, grid, attr, dc, rect, row, col, isSelected):
1326 data = grid.GetTable().GetClassGroup(row)
1327
1328 dc.SetClippingRegion(rect.GetX(), rect.GetY(),
1329 rect.GetWidth(), rect.GetHeight())
1330 dc.SetPen(wxPen(wxLIGHT_GREY))
1331 dc.SetBrush(wxBrush(wxLIGHT_GREY, wxSOLID))
1332 dc.DrawRectangle(rect.GetX(), rect.GetY(),
1333 rect.GetWidth(), rect.GetHeight())
1334
1335 if not isinstance(data, ClassGroupMap):
1336 self.previewer.Draw(dc, rect, data.GetProperties(), self.shapeType)
1337
1338 if isSelected:
1339 dc.SetPen(wxPen(wxBLACK, 1, wxSOLID))
1340 dc.SetBrush(wxTRANSPARENT_BRUSH)
1341
1342 dc.DrawRectangle(rect.GetX(), rect.GetY(),
1343 rect.GetWidth(), rect.GetHeight())
1344
1345 dc.DestroyClippingRegion()
1346
1347
1348 class ClassGroupPropertiesCtrl(wxWindow, wxControl):
1349
1350 def __init__(self, parent, id, props, shapeType,
1351 size = wxDefaultSize, style = 0):
1352
1353 wxWindow.__init__(self, parent, id, size = size, style = style)
1354
1355 self.SetProperties(props)
1356 self.SetShapeType(shapeType)
1357 self.AllowEdit(True)
1358
1359 EVT_PAINT(self, self._OnPaint)
1360 EVT_LEFT_DCLICK(self, self._OnLeftDClick)
1361
1362 self.previewer = ClassDataPreviewer()
1363
1364 def _OnPaint(self, event):
1365 dc = wxPaintDC(self)
1366
1367 # XXX: this doesn't seem to be having an effect:
1368 dc.DestroyClippingRegion()
1369
1370 w, h = self.GetClientSize()
1371
1372 self.previewer.Draw(dc,
1373 wxRect(0, 0, w, h),
1374 self.GetProperties(),
1375 self.GetShapeType())
1376
1377
1378 def GetProperties(self):
1379 return self.props
1380
1381 def SetProperties(self, props):
1382 self.props = props
1383 self.Refresh()
1384
1385 def GetShapeType(self):
1386 return self.shapeType
1387
1388 def SetShapeType(self, shapeType):
1389 self.shapeType = shapeType
1390 self.Refresh()
1391
1392 def AllowEdit(self, allow):
1393 self.allowEdit = allow
1394
1395 def DoEdit(self):
1396 if not self.allowEdit: return
1397
1398 propDlg = SelectPropertiesDialog(NULL,
1399 self.GetProperties(),
1400 self.GetShapeType())
1401
1402 if propDlg.ShowModal() == wxID_OK:
1403 new_prop = propDlg.GetClassGroupProperties()
1404 self.SetProperties(new_prop)
1405 self.Refresh()
1406
1407 propDlg.Destroy()
1408
1409 def _OnLeftDClick(self, event):
1410 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