/[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 2560 - (show annotations)
Tue Feb 8 20:25:22 2005 UTC (20 years, 1 month ago) by bh
File MIME type: text/x-python
File size: 49088 byte(s)
Compatibility with wxPython 2.5.  The changes should make it work
better with 2.5 while still keeping compatibility with 2.4.  There
are still problems with 2.5, though.

* Thuban/UI/dock.py (DockableWindow.__CreateBorder): Pass the size
of a spacer as a single item.

* Thuban/UI/classifier.py (ClassGroupPropertiesCtrl): Derive only
from wxControl

* Thuban/UI/legend.py (LegendTree): When running with wxPython <
2.5, add an implementation of the GetFirstChild method that does
not require the second parameter.
(LegendTree.find_layer, LegendTree._OnMsgMapLayersAdded)
(LegendTree._OnMsgMapLayersRemoved, LegendTree.DeleteAllItems)
(LegendTree.DeleteChildren, LegendTree.__ShowHideLayer): Do not
pass the second parameter to GetFirstChild

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26