/[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 2895 - (show annotations)
Thu Jul 14 13:17:17 2011 UTC (13 years, 7 months ago) by bricks
File MIME type: text/x-python
File size: 52870 byte(s)
Raise the classifier dialog again after something was edited. This
avoids "loosing" the window in the background and the resulting
expression that nothing has changed.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26