/[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 782 - (show annotations)
Tue Apr 29 16:53:55 2003 UTC (21 years, 10 months ago) by jonathan
File MIME type: text/x-python
File size: 44716 byte(s)
Remove all uses of Str2Num.
(ClassTable.SetValueAsCustom): Rename keyword argument in
        ClassGroup* constructors to match argument name.

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