/[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 935 - (show annotations)
Tue May 20 15:24:17 2003 UTC (21 years, 9 months ago) by jonathan
File MIME type: text/x-python
File size: 45044 byte(s)
(Classifier.__init__): Rearrange how
        the dialog is constructed so that we can support layers that
        do not have classifications.
(Classifier._OnTry): Only build a classification if the layer supports one.

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