/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/UI/classifier.py
ViewVC logotype

Contents of /branches/WIP-pyshapelib-bramz/Thuban/UI/classifier.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1539 - (show annotations)
Fri Aug 1 14:27:57 2003 UTC (21 years, 7 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/classifier.py
File MIME type: text/x-python
File size: 49709 byte(s)
Import the SHAPETYPE_* constants from data
instead of layer.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26