/[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 1142 - (show annotations)
Tue Jun 10 09:41:57 2003 UTC (21 years, 8 months ago) by bh
File MIME type: text/x-python
File size: 45747 byte(s)
* Thuban/Model/messages.py (LAYER_SHAPESTORE_REPLACED): New
message.

* Thuban/Model/layer.py (Layer.SetShapeStore): Send
LAYER_SHAPESTORE_REPLACED when the shapestore changes. A
LAYER_CHANGED will still be sent if the classification changes.

* Thuban/UI/classifier.py (Classifier.__init__): Add the map as
parameter so we can subscribe to some of its messages
(Classifier.__init__): Subscribe to the map's MAP_LAYERS_REMOVED
and the layer's LAYER_SHAPESTORE_REPLACED
(Classifier.unsubscribe_messages): New. Unsubscribe from message
subscribed to in __init__
(Classifier.map_layers_removed)
(Classifier.layer_shapestore_replaced): receivers for the messages
subscribed to in __init__. Unsubscribe and close the dialog

* Thuban/UI/mainwindow.py (MainWindow.OpenLayerProperties): Pass
the map to the Classifier dialog

* test/test_layer.py (SetShapeStoreTests): Derive from
SubscriberMixin as well so we can test messages
(SetShapeStoreTests.setUp): Subscribe to some of the layer's
messages
(SetShapeStoreTests.tearDown): Clear the messages again
(SetShapeStoreTests.test_sanity): Expand the doc-string and check
for the modified flag too
(SetShapeStoreTests.test_set_shape_store_modified_flag): New test
to check whether SetShapeStore sets the modified flag
(SetShapeStoreTests.test_set_shape_store_different_field_name)
(SetShapeStoreTests.test_set_shape_store_same_field)
(SetShapeStoreTests.test_set_shape_store_same_field_different_type):
Add tests for the messages. This checks both the new
LAYER_SHAPESTORE_REPLACED and the older LAYER_CHANGED

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