/[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 2597 - (show annotations)
Thu Apr 7 19:46:29 2005 UTC (19 years, 11 months ago) by bh
File MIME type: text/x-python
File size: 49054 byte(s)
(ClassGrid.__init__): Use -1 as the ID.
(ID_CLASS_TABLE): Removed. It wasn't used anywhere except in
ClassGrid.__init__ and it's value is outside of the valid
range (must be < 32768).  wxPython 2.5 complains about it with an
exception.

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

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26