/[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 2817 - (show annotations)
Sun Jan 27 00:01:32 2008 UTC (17 years, 1 month ago) by bernhard
File MIME type: text/x-python
File size: 52767 byte(s)
Changed a few places in Thuban and Extensions to convert wx
strings to internal string encoding. 

* Thuban/UI/classgen.py: Checked for return strings from wx problems.
Added one clarifing comment, removed two unused variables.

* Thuban/UI/mainwindow.py(OpenSession, SaveSessionAs, TableOpen): Using 
internal_from_wxstring for the pathname.

* Thuban/UI/tableview.py(doExport):  Using internal_from_wxstring
for the pathname.

* Thuban/UI/view.py(MapCanvas.Export):  Using internal_from_wxstring
for the pathname.

* Extensions/bboxdump/bboxdump.py(OnSelectFilename),
Extensions/export_shapefile/export_shapefile.py,
Extensions/gns2shp/gns2shp.py, Extensions/importAPR/importAPR.py,
Extensions/svgexport/maplegend.py, Extensions/svgexport/svgsaver.py,
Extensions/umn_mapserver/mf_import.py: Using
internal_from_wxstring for the pathname.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26