/[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 2386 - (show annotations)
Thu Oct 7 14:43:45 2004 UTC (20 years, 5 months ago) by jan
File MIME type: text/x-python
File size: 52379 byte(s)
(ID_SELPROP_SPINCTRL): Renamed to ID_SELPROP_SPINCTRL_LINEWIDTH.
(ID_SELPROP_SPINCTRL_LINEWIDTH): New Id replaces ID_SELPROP_SPINCTRL.
(ID_SELPROP_SPINCTRL_SIZE): New Id.
(SelectPropertiesDialog.__init__): Added a second spin control
for the size in case the corresponding layer is of point type.
(SelectPropertiesDialog._OnSpin): Renamed to _OnSpinLineWidth.
(SelectPropertiesDialog._OnSpinLineWidth): New. Former _OnSpin.
(SelectPropertiesDialog._OnSpinSize): New. Set size of property
and refresh preview.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26