/[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 783 - (show annotations)
Tue Apr 29 17:29:32 2003 UTC (21 years, 10 months ago) by jonathan
File MIME type: text/x-python
File size: 44688 byte(s)
Remove Thuban.common import.

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