/[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 2700 - (show annotations)
Mon Sep 18 14:27:02 2006 UTC (18 years, 5 months ago) by dpinte
File MIME type: text/x-python
File size: 52589 byte(s)
2006-09-18 Didrik Pinte <dpinte@itae.be>
    
        * wxPython 2.6 update : wx 2.4 syntax has been updated to 2.6


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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26