/[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 2376 - (show annotations)
Sun Oct 3 21:23:25 2004 UTC (20 years, 5 months ago) by jan
Original Path: trunk/thuban/Thuban/UI/classifier.py
File MIME type: text/x-python
File size: 51066 byte(s)
(ClassDataPreviewer.Draw): Added doc-string.
Also, now there is a return value that indicates whether the drawing
size exceeded the given rect extent and if so the new extent.
Finally, point objects are drawn depending on the size. If either
the width or height is exceeded, the new extent is returned.
(ClassRenderer.Draw): Now when calling the previewer drawing function,
evaluate the return value and, if not None, adapt the grid widget size
accordingly and redraw again.

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