/[thuban]/trunk/thuban/Thuban/UI/classifier.py
ViewVC logotype

Annotation of /trunk/thuban/Thuban/UI/classifier.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2386 - (hide 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 jan 2386 # Copyright (c) 2003-2004 by Intevation GmbH
2 jonathan 372 # Authors:
3 jan 2386 # 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 jonathan 372 #
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 jan 2386 # $Source$
16     # $Id$
17 jonathan 372
18 jonathan 376 import copy
19    
20 jonathan 473 from Thuban.Model.table import FIELDTYPE_INT, FIELDTYPE_DOUBLE, \
21     FIELDTYPE_STRING
22    
23 jonathan 372 from wxPython.wx import *
24     from wxPython.grid import *
25    
26 jan 374 from Thuban import _
27 jonathan 878 from Thuban.UI.common import Color2wxColour, wxColour2Color
28 jan 374
29 bh 1142 from Thuban.Model.messages import MAP_LAYERS_REMOVED, LAYER_SHAPESTORE_REPLACED
30 jonathan 878 from Thuban.Model.range import Range
31     from Thuban.Model.classification import \
32     Classification, ClassGroupDefault, \
33     ClassGroupSingleton, ClassGroupRange, ClassGroupMap, \
34     ClassGroupProperties
35 jonathan 392
36 jonathan 1342 from Thuban.Model.color import Transparent
37 jonathan 415
38 bh 1539 from Thuban.Model.layer import Layer, RasterLayer
39     from Thuban.Model.data import SHAPETYPE_ARC, SHAPETYPE_POLYGON, SHAPETYPE_POINT
40 jonathan 392
41 jonathan 1101 from Thuban.UI.classgen import ClassGenDialog
42 jan 2200 from Thuban.UI.colordialog import ColorDialog
43 jonathan 606
44 frank 1058 from dialogs import NonModalNonParentDialog
45 bh 1464 from messages import MAP_REPLACED
46 jonathan 485
47 jonathan 372 ID_CLASS_TABLE = 40011
48    
49    
50 jonathan 460 # table columns
51 jonathan 638 COL_VISIBLE = 0
52     COL_SYMBOL = 1
53     COL_VALUE = 2
54     COL_LABEL = 3
55     NUM_COLS = 4
56 jonathan 415
57 jonathan 460 # indices into the client data lists in Classifier.fields
58 jonathan 451 FIELD_CLASS = 0
59     FIELD_TYPE = 1
60     FIELD_NAME = 2
61    
62 jonathan 415 #
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 jonathan 570
70 jonathan 606 def __init__(self, parent, classifier):
71 jonathan 460 """Constructor.
72    
73     parent -- the parent window
74    
75     clazz -- the working classification that this grid should
76     use for display.
77     """
78    
79 jonathan 813 wxGrid.__init__(self, parent, ID_CLASS_TABLE, style = 0)
80 jonathan 415
81 jonathan 606 self.classifier = classifier
82    
83 jonathan 570 self.currentSelection = []
84    
85 jonathan 460 EVT_GRID_CELL_LEFT_DCLICK(self, self._OnCellDClick)
86 jonathan 451 EVT_GRID_RANGE_SELECT(self, self._OnSelectedRange)
87     EVT_GRID_SELECT_CELL(self, self._OnSelectedCell)
88 jonathan 606 EVT_GRID_COL_SIZE(self, self._OnCellResize)
89     EVT_GRID_ROW_SIZE(self, self._OnCellResize)
90 jonathan 451
91 jonathan 638 #def GetCellAttr(self, row, col):
92     #print "GetCellAttr ", row, col
93     #wxGrid.GetCellAttr(self, row, col)
94    
95 jonathan 1433 def CreateTable(self, clazz, fieldType, shapeType, group = None):
96 jonathan 570
97 jonathan 606 assert isinstance(clazz, Classification)
98 jonathan 460
99     table = self.GetTable()
100     if table is None:
101 jonathan 650 w = self.GetDefaultColSize() * NUM_COLS \
102     + self.GetDefaultRowLabelSize()
103     h = self.GetDefaultRowSize() * 4 \
104     + self.GetDefaultColLabelSize()
105    
106 jonathan 500 self.SetDimensions(-1, -1, w, h)
107     self.SetSizeHints(w, h, -1, -1)
108 jonathan 570 table = ClassTable(self)
109     self.SetTable(table, True)
110 jonathan 460
111 jonathan 570
112 bh 476 self.SetSelectionMode(wxGrid.wxGridSelectRows)
113 jonathan 460 self.ClearSelection()
114    
115 jonathan 1433 table.Reset(clazz, fieldType, shapeType, group)
116 jonathan 570
117 jonathan 451 def GetCurrentSelection(self):
118 jonathan 460 """Return the currently highlighted rows as an increasing list
119     of row numbers."""
120 jonathan 451 sel = copy.copy(self.currentSelection)
121     sel.sort()
122     return sel
123    
124 jonathan 570 def GetSelectedRows(self):
125     return self.GetCurrentSelection()
126    
127 jonathan 638 #def SetCellRenderer(self, row, col, renderer):
128     #raise ValueError(_("Must not allow setting of renderer in ClassGrid!"))
129 jonathan 415
130 jonathan 460 #
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 jonathan 415 def SetTable(self, object, *attributes):
137     self.tableRef = weakref.ref(object)
138     return wxGrid.SetTable(self, object, *attributes)
139    
140     def GetTable(self):
141 jonathan 460 try:
142     return self.tableRef()
143     except:
144     return None
145 jonathan 415
146 jonathan 451 def DeleteSelectedRows(self):
147 jonathan 460 """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 jonathan 451 sel = self.GetCurrentSelection()
153 jonathan 415
154 jonathan 460 # 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 jonathan 451 if len(sel) == 1:
160 jonathan 485 #group = self.GetTable().GetValueAsCustom(sel[0], COL_SYMBOL, None)
161     group = self.GetTable().GetClassGroup(sel[0])
162 jonathan 451 if isinstance(group, ClassGroupDefault):
163     wxMessageDialog(self,
164     "The Default group cannot be removed.",
165     style = wxOK | wxICON_EXCLAMATION).ShowModal()
166     return
167    
168 jonathan 460
169 jonathan 451 self.ClearSelection()
170    
171 jonathan 460 # 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 jonathan 451 sel.reverse()
174 jonathan 460
175     #
176     # actually remove the rows
177     #
178 jonathan 451 table = self.GetTable()
179     for row in sel:
180     table.DeleteRows(row)
181    
182 jonathan 460 #
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 jonathan 451 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 jonathan 570
194     def SelectGroup(self, group, makeVisible = True):
195     if group is None: return
196    
197 jonathan 606 assert isinstance(group, ClassGroup)
198 jonathan 570
199     table = self.GetTable()
200    
201 jonathan 606 assert table is not None
202 jonathan 570
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 jonathan 451 #
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 jonathan 460 def _OnCellDClick(self, event):
223 jonathan 638 """Handle a double click on a cell."""
224 jonathan 460
225 jonathan 451 r = event.GetRow()
226     c = event.GetCol()
227 jonathan 638
228 jonathan 460 if c == COL_SYMBOL:
229 jonathan 638 self.classifier.EditSymbol(r)
230     else:
231     event.Skip()
232 jonathan 460
233 jonathan 451 #
234     # _OnSelectedRange() and _OnSelectedCell() were borrowed
235 jonathan 460 # from http://wiki.wxpython.org to keep track of which
236     # cells are currently highlighted
237 jonathan 451 #
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 jonathan 606 def _OnCellResize(self, event):
259     self.FitInside()
260 jonathan 878 event.Skip()
261 jonathan 606
262 jonathan 376 class ClassTable(wxPyGridTableBase):
263 jonathan 460 """Represents the underlying data structure for the grid."""
264 jonathan 376
265 jonathan 638 __col_labels = [_("Visible"), _("Symbol"), _("Value"), _("Label")]
266 jonathan 415
267    
268 jonathan 570 def __init__(self, view = None):
269 jonathan 460 """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 jonathan 376 wxPyGridTableBase.__init__(self)
277 jonathan 485
278 jonathan 638 assert len(ClassTable.__col_labels) == NUM_COLS
279    
280 jonathan 615 self.clazz = None
281 jonathan 638 self.__colAttr = {}
282 jonathan 376
283 jonathan 638 self.SetView(view)
284 jonathan 415
285 jonathan 1433 def Reset(self, clazz, fieldType, shapeType, group = None):
286 jonathan 460 """Reset the table with the given data.
287 jonathan 415
288 jonathan 460 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 jonathan 606 assert isinstance(clazz, Classification)
300 jonathan 460
301 jonathan 415 self.GetView().BeginBatch()
302    
303 jonathan 1433 self.fieldType = fieldType
304 jonathan 415 self.shapeType = shapeType
305    
306 jonathan 606 self.SetClassification(clazz, group)
307     self.__Modified(-1)
308 jonathan 415
309 jonathan 638 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 jonathan 606 self.GetView().EndBatch()
323     self.GetView().FitInside()
324    
325 jonathan 615 def GetClassification(self):
326 jonathan 1527 """Return the current classification."""
327 jonathan 615 return self.clazz
328    
329 jonathan 606 def SetClassification(self, clazz, group = None):
330 jonathan 1527 """Fill in the table with the given classification.
331     Select the given group if group is not None.
332     """
333 jonathan 606
334     self.GetView().BeginBatch()
335    
336     old_len = self.GetNumberRows()
337 jonathan 376
338 jonathan 570 row = -1
339 jonathan 615 self.clazz = clazz
340 jan 2376
341 jonathan 606 self.__NotifyRowChanges(old_len, self.GetNumberRows())
342 jonathan 415
343 jonathan 638 #
344     # XXX: this is dead code at the moment
345     #
346 jonathan 570 if row > -1:
347     self.GetView().ClearSelection()
348     self.GetView().SelectRow(row)
349     self.GetView().MakeCellVisible(row, 0)
350 jonathan 606
351 jonathan 615 self.__Modified()
352    
353 jonathan 451 self.GetView().EndBatch()
354 jonathan 606 self.GetView().FitInside()
355 jonathan 451
356     def __NotifyRowChanges(self, curRows, newRows):
357 jonathan 1527 """Make sure table updates correctly if the number of
358     rows changes.
359     """
360 jonathan 415 #
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 jonathan 519 self.GetView().FitInside()
370 jonathan 415 elif newRows < curRows:
371     msg = wxGridTableMessage(self,
372     wxGRIDTABLE_NOTIFY_ROWS_DELETED,
373 jonathan 606 curRows, # position
374 jonathan 415 curRows - newRows) # how many
375     self.GetView().ProcessTableMessage(msg)
376 jonathan 519 self.GetView().FitInside()
377 jonathan 415
378 jonathan 441 def __SetRow(self, row, group):
379 jonathan 460 """Set a row's data to that of the group.
380 jonathan 441
381 jonathan 485 The table is considered modified after this operation.
382    
383 jonathan 606 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 jonathan 460 """
388 jonathan 441
389 jonathan 460 # either append or replace
390 jonathan 606 if row is None or row >= self.GetNumberRows():
391 jonathan 615 self.clazz.AppendGroup(group)
392 jonathan 606 elif row < 0:
393 jonathan 615 self.clazz.InsertGroup(0, group)
394 jonathan 441 else:
395 jonathan 615 if row == 0:
396     self.clazz.SetDefaultGroup(group)
397     else:
398     self.clazz.ReplaceGroup(row - 1, group)
399 jonathan 441
400 jonathan 460 self.__Modified()
401    
402 jonathan 415 def GetColLabelValue(self, col):
403 jonathan 460 """Return the label for the given column."""
404 jonathan 415 return self.__col_labels[col]
405    
406     def GetRowLabelValue(self, row):
407 jonathan 460 """Return the label for the given row."""
408 jonathan 415
409 jonathan 615 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 jonathan 460
418 jonathan 606 assert False # shouldn't get here
419 bh 671 return ""
420 jonathan 460
421 jonathan 376 def GetNumberRows(self):
422 jonathan 460 """Return the number of rows."""
423 jonathan 615 if self.clazz is None:
424     return 0
425 jonathan 376
426 jonathan 615 return self.clazz.GetNumGroups() + 1 # +1 for default group
427    
428 jonathan 376 def GetNumberCols(self):
429 jonathan 460 """Return the number of columns."""
430 jonathan 638 return NUM_COLS
431 jonathan 376
432     def IsEmptyCell(self, row, col):
433 jonathan 460 """Determine if a cell is empty. This is always false."""
434     return False
435 jonathan 376
436     def GetValue(self, row, col):
437 jonathan 460 """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 jonathan 376
441     def SetValue(self, row, col, value):
442 jonathan 460 """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 jan 2376
449 jonathan 392 def GetValueAsCustom(self, row, col, typeName):
450 jonathan 460 """Return the object that is used to represent the given
451     cell coordinates. This may not be a string.
452 jan 2376
453 jonathan 460 typeName -- unused, but needed to overload wxPyGridTableBase
454     """
455 jonathan 376
456 jonathan 615 if row == 0:
457     group = self.clazz.GetDefaultGroup()
458     else:
459     group = self.clazz.GetGroup(row - 1)
460 jonathan 460
461 jonathan 615
462 jonathan 638 if col == COL_VISIBLE:
463     return group.IsVisible()
464    
465 jonathan 460 if col == COL_SYMBOL:
466 jonathan 485 return group.GetProperties()
467 jonathan 460
468     if col == COL_LABEL:
469     return group.GetLabel()
470    
471     # col must be COL_VALUE
472 jonathan 606 assert col == COL_VALUE
473 jonathan 460
474     if isinstance(group, ClassGroupDefault):
475     return _("DEFAULT")
476     elif isinstance(group, ClassGroupSingleton):
477     return group.GetValue()
478     elif isinstance(group, ClassGroupRange):
479 jonathan 878 return group.GetRange()
480 jonathan 460
481 jonathan 878 assert False # shouldn't get here
482 jonathan 460 return None
483    
484 jonathan 415 def __ParseInput(self, value):
485     """Try to determine what kind of input value is
486 jonathan 460 (string, number, or range)
487    
488 jonathan 878 Returns a tuple (type, data) where type is 0 if data is
489     a singleton value, or 1 if is a range
490 jonathan 415 """
491 jonathan 392
492 jonathan 485 type = self.fieldType
493 jonathan 415
494 jonathan 460 if type == FIELDTYPE_STRING:
495 jonathan 878 return (0, value)
496 jonathan 630 elif type in (FIELDTYPE_INT, FIELDTYPE_DOUBLE):
497 jonathan 460 if type == FIELDTYPE_INT:
498 jonathan 782 # the float call allows the user to enter 1.0 for 1
499 jonathan 460 conv = lambda p: int(float(p))
500     else:
501 jonathan 878 conv = float
502 jonathan 460
503 jonathan 451 #
504     # first try to take the input as a single number
505     # if there's an exception try to break it into
506 jonathan 878 # a range. if there is an exception here, let it
507     # pass up to the calling function.
508 jonathan 451 #
509     try:
510 jonathan 878 return (0, conv(value))
511 jonathan 460 except ValueError:
512 jonathan 878 return (1, Range(value))
513 jonathan 451
514 jonathan 606 assert False # shouldn't get here
515 jonathan 878 return (0,None)
516 jonathan 415
517     def SetValueAsCustom(self, row, col, typeName, value):
518 jonathan 460 """Set the cell specified by 'row' and 'col' to 'value'.
519 jonathan 415
520 jonathan 460 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 jonathan 650 assert 0 <= col < self.GetNumberCols()
530     assert 0 <= row < self.GetNumberRows()
531 jonathan 460
532 jonathan 615 if row == 0:
533     group = self.clazz.GetDefaultGroup()
534     else:
535     group = self.clazz.GetGroup(row - 1)
536 jonathan 460
537 jonathan 485 mod = True # assume the data will change
538 jonathan 460
539 jonathan 638 if col == COL_VISIBLE:
540     group.SetVisible(value)
541     elif col == COL_SYMBOL:
542 jonathan 485 group.SetProperties(value)
543     elif col == COL_LABEL:
544     group.SetLabel(value)
545 jonathan 415 elif col == COL_VALUE:
546 jonathan 451 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 jonathan 460 except ValueError:
556 jonathan 451 # bad input, ignore the request
557 jonathan 485 mod = False
558 jonathan 451 else:
559 jonathan 415
560 jonathan 485 changed = False
561 jonathan 451 ngroup = group
562     props = group.GetProperties()
563 jonathan 460
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 jonathan 878 if dataInfo[0] == 0:
570 jonathan 451 if not isinstance(group, ClassGroupSingleton):
571 jonathan 782 ngroup = ClassGroupSingleton(props = props)
572 jonathan 485 changed = True
573 jonathan 878 ngroup.SetValue(dataInfo[1])
574     elif dataInfo[0] == 1:
575 jonathan 451 if not isinstance(group, ClassGroupRange):
576 jonathan 782 ngroup = ClassGroupRange(props = props)
577 jonathan 485 changed = True
578 jonathan 878 ngroup.SetRange(dataInfo[1])
579 jonathan 415 else:
580 jonathan 606 assert False
581 jonathan 485 pass
582 jonathan 415
583 jonathan 485 if changed:
584     ngroup.SetLabel(group.GetLabel())
585     self.SetClassGroup(row, ngroup)
586     else:
587 jonathan 606 assert False # shouldn't be here
588 jonathan 485 pass
589 jonathan 460
590 jonathan 485 if mod:
591 jonathan 460 self.__Modified()
592     self.GetView().Refresh()
593 jonathan 415
594     def GetAttr(self, row, col, someExtraParameter):
595 jonathan 460 """Returns the cell attributes"""
596    
597 jonathan 638 return self.__colAttr.get(col, wxGridCellAttr()).Clone()
598 jonathan 415
599 jonathan 441 def GetClassGroup(self, row):
600 jonathan 460 """Return the ClassGroup object representing row 'row'."""
601 jonathan 415
602 jonathan 615 #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 jonathan 415
608 jonathan 460 def SetClassGroup(self, row, group):
609 jonathan 1527 """Set the given row to properties of group."""
610 jonathan 485 self.__SetRow(row, group)
611     self.GetView().Refresh()
612 jonathan 460
613     def __Modified(self, mod = True):
614 jonathan 485 """Adjust the modified flag.
615 jonathan 460
616 jonathan 485 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 jonathan 415 def IsModified(self):
627 jonathan 460 """True if this table is considered modified."""
628 jonathan 415 return self.modified
629    
630 jonathan 451 def DeleteRows(self, pos, numRows = 1):
631 jonathan 485 """Deletes 'numRows' beginning at row 'pos'.
632 jonathan 460
633 jonathan 485 The row representing the default group is not removed.
634    
635     The table is considered modified if any rows are removed.
636 jonathan 460 """
637    
638 jonathan 606 assert pos >= 0
639 jonathan 615 old_len = self.GetNumberRows()
640 jonathan 451 for row in range(pos, pos - numRows, -1):
641 jonathan 485 group = self.GetClassGroup(row)
642 jonathan 615 if row != 0:
643     self.clazz.RemoveGroup(row - 1)
644 jonathan 451 self.__Modified()
645    
646     if self.IsModified():
647 jonathan 615 self.__NotifyRowChanges(old_len, self.GetNumberRows())
648 jonathan 415
649 jonathan 451 def AppendRows(self, numRows = 1):
650 jonathan 485 """Append 'numRows' empty rows to the end of the table.
651 jonathan 460
652 jonathan 485 The table is considered modified if any rows are appended.
653     """
654    
655 jonathan 615 old_len = self.GetNumberRows()
656 jonathan 451 for i in range(numRows):
657     np = ClassGroupSingleton()
658 jonathan 606 self.__SetRow(None, np)
659 jonathan 451
660     if self.IsModified():
661 jonathan 615 self.__NotifyRowChanges(old_len, self.GetNumberRows())
662 jonathan 451
663    
664 jonathan 650 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 jonathan 630
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 jonathan 650 EB_LAYER_TITLE = 0
684     EB_SELECT_FIELD = 1
685     EB_GEN_CLASS = 2
686    
687 frank 1058 class Classifier(NonModalNonParentDialog):
688 bh 535
689 jonathan 630 type2string = {None: _("None"),
690     FIELDTYPE_STRING: _("Text"),
691     FIELDTYPE_INT: _("Integer"),
692     FIELDTYPE_DOUBLE: _("Decimal")}
693    
694 joey 2362 def __init__(self, parent, name, layer, group = None):
695 jonathan 1527 """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 frank 1058 NonModalNonParentDialog.__init__(self, parent, name, "")
701 jonathan 372
702 jonathan 650 self.__SetTitle(layer.Title())
703 jonathan 509
704 bh 1464 self.parent.Subscribe(MAP_REPLACED, self.map_replaced)
705 jonathan 415 self.layer = layer
706 joey 2362 self.map = parent.Map()
707 jonathan 415
708 bh 1142 self.map.Subscribe(MAP_LAYERS_REMOVED, self.map_layers_removed)
709     self.layer.Subscribe(LAYER_SHAPESTORE_REPLACED,
710     self.layer_shapestore_replaced)
711 jonathan 485
712 jonathan 630 self.genDlg = None
713    
714 jonathan 813 ############################
715     # Create the controls
716     #
717 jonathan 372
718 jonathan 813 panel = wxPanel(self, -1)
719 jonathan 661
720 jonathan 813 text_title = wxTextCtrl(panel, ID_PROPERTY_TITLE, layer.Title())
721 jonathan 935 self.fieldTypeText = wxStaticText(panel, -1, "")
722 jonathan 372
723 jonathan 935 if layer.HasClassification():
724     self.originalClass = self.layer.GetClassification()
725 bh 1452 self.originalClassField = self.layer.GetClassificationColumn()
726 jonathan 1433 field = self.originalClassField
727     fieldType = self.layer.GetFieldType(field)
728 jonathan 460
729 bh 1219 table = layer.ShapeStore().Table()
730 jonathan 935 #
731     # make field choice box
732     #
733     self.fields = wxChoice(panel, ID_PROPERTY_SELECT,)
734 jonathan 460
735 bh 1219 self.num_cols = table.NumColumns()
736 jonathan 935 # just assume the first field in case one hasn't been
737     # specified in the file.
738     self.__cur_field = 0
739 jonathan 650
740 jonathan 935 self.fields.Append("<None>")
741 jonathan 451
742 jonathan 1433 if fieldType is None:
743 jonathan 935 self.fields.SetClientData(0, copy.deepcopy(self.originalClass))
744 jonathan 451 else:
745 jonathan 935 self.fields.SetClientData(0, None)
746 jonathan 372
747 jonathan 935 for i in range(self.num_cols):
748 bh 1219 name = table.Column(i).name
749 jonathan 935 self.fields.Append(name)
750 jonathan 372
751 jonathan 935 if name == field:
752     self.__cur_field = i + 1
753     self.fields.SetClientData(i + 1,
754 jonathan 1433 copy.deepcopy(self.originalClass))
755 jonathan 935 else:
756     self.fields.SetClientData(i + 1, None)
757 jonathan 485
758 jonathan 935 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 jonathan 650
771 jonathan 935 self.classGrid = ClassGrid(panel, self)
772 jonathan 650
773 jonathan 935 # 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 jonathan 650
778 frank 977 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 jonathan 1342 button_close = wxButton(self, wxID_CANCEL, _("Close"))
782 jonathan 813 button_ok.SetDefault()
783 jonathan 485
784 jonathan 813 ############################
785     # Layout the controls
786 jonathan 485 #
787 jonathan 509
788 jonathan 813 topBox = wxBoxSizer(wxVERTICAL)
789     panelBox = wxBoxSizer(wxVERTICAL)
790 jonathan 451
791 jonathan 813 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 jonathan 485
796 jonathan 813 panelBox.Add(sizer, 0, wxGROW, 4)
797 jonathan 606
798 jonathan 935 if isinstance(layer, RasterLayer):
799     type = "Image"
800     else:
801     type = layer.ShapeType()
802    
803     panelBox.Add(wxStaticText(panel, -1, _("Type: %s") % type),
804 jonathan 813 0, wxALIGN_LEFT | wxALL, 4)
805 jonathan 485
806 jonathan 935 if layer.HasClassification():
807 jonathan 485
808 jonathan 935 classBox = wxStaticBoxSizer(
809     wxStaticBox(panel, -1, _("Classification")), wxVERTICAL)
810 jonathan 485
811    
812 jonathan 935 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 jonathan 570
817 jonathan 935 classBox.Add(sizer, 0, wxGROW, 4)
818 jonathan 570
819 jonathan 935 classBox.Add(self.fieldTypeText, 0,
820     wxGROW | wxALIGN_LEFT | wxALL | wxADJUST_MINSIZE, 4)
821 jonathan 570
822 jonathan 935 controlBox = wxBoxSizer(wxHORIZONTAL)
823     controlButtonBox = wxBoxSizer(wxVERTICAL)
824 jonathan 570
825 jonathan 935 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 jonathan 415
834 jonathan 935 controlBox.Add(self.classGrid, 1, wxGROW, 0)
835     controlBox.Add(controlButtonBox, 0, wxGROW, 10)
836 jonathan 661
837 jonathan 935 classBox.Add(controlBox, 1, wxGROW, 10)
838     panelBox.Add(classBox, 1, wxGROW, 0)
839 jonathan 415
840 jonathan 935
841 jonathan 813 buttonBox = wxBoxSizer(wxHORIZONTAL)
842 frank 977 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 jonathan 509
847 jonathan 650 panel.SetAutoLayout(True)
848     panel.SetSizer(panelBox)
849 jonathan 813 panelBox.Fit(panel)
850 jonathan 650 panelBox.SetSizeHints(panel)
851    
852     topBox.Add(panel, 1, wxGROW | wxALL, 4)
853 frank 977 topBox.Add(buttonBox, 0, wxALIGN_RIGHT|wxBOTTOM|wxTOP, 10)
854 jonathan 650
855 jonathan 813 self.SetAutoLayout(True)
856     self.SetSizer(topBox)
857     topBox.Fit(self)
858     topBox.SetSizeHints(self)
859     self.Layout()
860    
861 jonathan 650 ###########
862    
863 jonathan 813 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 jonathan 650 EVT_BUTTON(self, ID_PROPERTY_TRY, self._OnTry)
867 jonathan 813 EVT_BUTTON(self, wxID_CANCEL, self._OnCloseBtn)
868 jonathan 650 EVT_BUTTON(self, ID_PROPERTY_REVERT, self._OnRevert)
869 jonathan 372
870 jonathan 813 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 jonathan 509
877 jonathan 549 ######################
878    
879 jonathan 935 text_title.SetFocus()
880 jonathan 549 self.haveApplied = False
881    
882 bh 1142 def unsubscribe_messages(self):
883 jonathan 1527 """Unsubscribe from all messages."""
884 bh 1464 self.parent.Unsubscribe(MAP_REPLACED, self.map_replaced)
885 bh 1142 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 jonathan 1527 """Subscribed to MAP_LAYERS_REMOVED. If this layer was removed,
891     Close self.
892     """
893 bh 1142 if self.layer not in self.map.Layers():
894     self.Close()
895    
896     def layer_shapestore_replaced(self, *args):
897 bh 1464 """Subscribed to the map's LAYER_SHAPESTORE_REPLACED message.
898    
899     Close self.
900     """
901 bh 1142 self.Close()
902    
903 bh 1464 def map_replaced(self, *args):
904     """Subscribed to the mainwindow's MAP_REPLACED message. Close self."""
905     self.Close()
906    
907 jonathan 638 def EditSymbol(self, row):
908 jonathan 1527 """Open up a dialog where the user can select the properties
909     for a group.
910     """
911 jonathan 606 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 jonathan 1302 propDlg = SelectPropertiesDialog(self, prop, self.layer.ShapeType())
917 jonathan 630
918     self.Enable(False)
919 jonathan 606 if propDlg.ShowModal() == wxID_OK:
920     new_prop = propDlg.GetClassGroupProperties()
921     table.SetValueAsCustom(row, COL_SYMBOL, None, new_prop)
922 jonathan 630 self.Enable(True)
923 jonathan 606 propDlg.Destroy()
924 jan 2376
925 jonathan 630 def _SetClassification(self, clazz):
926 jonathan 1527 """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 jan 2376
931 jonathan 630 self.fields.SetClientData(self.__cur_field, clazz)
932     self.classGrid.GetTable().SetClassification(clazz)
933 jonathan 606
934 jonathan 1447 def __BuildClassification(self, fieldIndex, copyClass=False, force=False):
935 jonathan 1527 """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 jonathan 415
940 jonathan 615 # numRows = self.classGrid.GetNumberRows()
941     # assert numRows > 0 # there should always be a default row
942 jonathan 496
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 jonathan 415
950 jonathan 1447 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 jonathan 615
956 jonathan 1447 return clazz, fieldName
957 jonathan 615
958 jonathan 570 def __SetGridTable(self, fieldIndex, group = None):
959 jonathan 1527 """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 jonathan 415
964 jonathan 460 clazz = self.fields.GetClientData(fieldIndex)
965 jonathan 415
966 jonathan 460 if clazz is None:
967     clazz = Classification()
968     clazz.SetDefaultGroup(
969     ClassGroupDefault(
970 jonathan 485 self.layer.GetClassification().
971     GetDefaultGroup().GetProperties()))
972 jonathan 460
973 jonathan 1433 fieldName = self.fields.GetString(fieldIndex)
974     fieldType = self.layer.GetFieldType(fieldName)
975 jonathan 460
976 jonathan 1433 self.classGrid.CreateTable(clazz, fieldType,
977     self.layer.ShapeType(), group)
978 jonathan 460
979 jonathan 485 def __SetFieldTypeText(self, fieldIndex):
980 jonathan 1527 """Set the field type string using the data type of the field
981     at fieldIndex.
982     """
983 jonathan 485 fieldName = self.fields.GetString(fieldIndex)
984     fieldType = self.layer.GetFieldType(fieldName)
985    
986 jonathan 606 assert Classifier.type2string.has_key(fieldType)
987 jonathan 485
988 jonathan 496 text = Classifier.type2string[fieldType]
989    
990 jonathan 650 self.fieldTypeText.SetLabel(_("Data Type: %s") % text)
991 jonathan 485
992 jonathan 570 def __SelectField(self, newIndex, oldIndex = -1, group = None):
993 jonathan 611 """This method assumes that the current selection for the
994     combo has already been set by a call to SetSelection().
995     """
996 jonathan 460
997 jonathan 606 assert oldIndex >= -1
998 jonathan 415
999 jonathan 498 if oldIndex != -1:
1000 jonathan 1447 clazz, name = self.__BuildClassification(oldIndex, force = True)
1001 jonathan 498 self.fields.SetClientData(oldIndex, clazz)
1002 jonathan 485
1003 jonathan 570 self.__SetGridTable(newIndex, group)
1004 jonathan 498
1005 jonathan 1307 self.__EnableButtons(EB_SELECT_FIELD)
1006 jonathan 498
1007     self.__SetFieldTypeText(newIndex)
1008 jonathan 485
1009 jonathan 650 def __SetTitle(self, title):
1010 jonathan 1527 """Set the title of the dialog."""
1011 jonathan 650 if title != "":
1012     title = ": " + title
1013    
1014     self.SetTitle(_("Layer Properties") + title)
1015 jonathan 496
1016 jonathan 638 def _OnEditSymbol(self, event):
1017 jonathan 1527 """Open up a dialog for the user to select group properties."""
1018 jonathan 606 sel = self.classGrid.GetCurrentSelection()
1019    
1020     if len(sel) == 1:
1021 jonathan 638 self.EditSymbol(sel[0])
1022 jonathan 606
1023 jonathan 496 def _OnFieldSelect(self, event):
1024 jonathan 498 index = self.fields.GetSelection()
1025     self.__SelectField(index, self.__cur_field)
1026     self.__cur_field = index
1027 jonathan 485
1028 jonathan 638 def _OnTry(self, event):
1029 jonathan 415 """Put the data from the table into a new Classification and hand
1030     it to the layer.
1031     """
1032    
1033 jonathan 935 if self.layer.HasClassification():
1034     clazz = self.fields.GetClientData(self.__cur_field)
1035 jonathan 415
1036 jonathan 935 #
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 jonathan 1447 clazz, name = self.__BuildClassification(self.__cur_field, True)
1042 jonathan 415
1043 bh 1452 self.layer.SetClassificationColumn(name)
1044 jonathan 935 self.layer.SetClassification(clazz)
1045 jonathan 415
1046 jonathan 549 self.haveApplied = True
1047    
1048 jonathan 485 def _OnOK(self, event):
1049 jonathan 638 self._OnTry(event)
1050 jonathan 615 self.Close()
1051 jonathan 415
1052 jonathan 813 def OnClose(self, event):
1053 bh 1207 self.unsubscribe_messages()
1054 frank 1058 NonModalNonParentDialog.OnClose(self, event)
1055 jonathan 813
1056 jonathan 615 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 jonathan 638 def _OnRevert(self, event):
1065 jonathan 485 """The layer's current classification stays the same."""
1066 jonathan 549 if self.haveApplied:
1067 bh 1452 self.layer.SetClassificationColumn(self.originalClassField)
1068 jonathan 549 self.layer.SetClassification(self.originalClass)
1069    
1070 jonathan 638 #self.Close()
1071 jonathan 415
1072 jonathan 460 def _OnAdd(self, event):
1073 jonathan 451 self.classGrid.AppendRows()
1074 jonathan 415
1075 jonathan 460 def _OnRemove(self, event):
1076 jonathan 451 self.classGrid.DeleteSelectedRows()
1077    
1078 jonathan 606 def _OnGenClass(self, event):
1079 jonathan 1527 """Open up a dialog for the user to generate classifications."""
1080 jonathan 415
1081 jonathan 630 self.genDlg = ClassGenDialog(self, self.layer,
1082     self.fields.GetString(self.__cur_field))
1083 jonathan 606
1084 jonathan 630 EVT_CLOSE(self.genDlg, self._OnGenDialogClose)
1085 jonathan 606
1086 jonathan 1307 self.__EnableButtons(EB_GEN_CLASS)
1087 jonathan 630
1088     self.genDlg.Show()
1089    
1090     def _OnGenDialogClose(self, event):
1091 jonathan 1527 """Reenable buttons after the generate classification
1092     dialog is closed.
1093     """
1094 jonathan 630 self.genDlg.Destroy()
1095 jonathan 1307 self.genDlg = None
1096     self.__EnableButtons(EB_GEN_CLASS)
1097 jonathan 630
1098 jonathan 460 def _OnMoveUp(self, event):
1099 jonathan 1527 """When the user clicks MoveUp, try to move a group up one row."""
1100 jonathan 460 sel = self.classGrid.GetCurrentSelection()
1101 jonathan 415
1102 jonathan 460 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 jonathan 570 self.classGrid.MakeCellVisible(i - 1, 0)
1113 jonathan 460
1114     def _OnMoveDown(self, event):
1115 jonathan 1527 """When the user clicks MoveDown, try to move a group down one row."""
1116 jonathan 460 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 jonathan 570 self.classGrid.MakeCellVisible(i + 1, 0)
1129 jonathan 460
1130 jonathan 650 def _OnTitleChanged(self, event):
1131 jonathan 1527 """Update the dialog title when the user changed the layer name."""
1132 jonathan 650 obj = event.GetEventObject()
1133 jonathan 460
1134 jonathan 650 self.layer.SetTitle(obj.GetValue())
1135     self.__SetTitle(self.layer.Title())
1136    
1137 jonathan 1307 self.__EnableButtons(EB_LAYER_TITLE)
1138 jonathan 650
1139 jonathan 1307 def __EnableButtons(self, case):
1140 jonathan 1527 """Helper method that enables/disables the appropriate buttons
1141     based on the case provided. Cases are constants beginning with EB_.
1142     """
1143 jonathan 650
1144 jonathan 1307 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 jonathan 650 if case == EB_LAYER_TITLE:
1156 jonathan 1307 if self.layer.Title() == "":
1157     list[wxID_OK] = False
1158     list[wxID_CANCEL] = False
1159 jonathan 650
1160     elif case == EB_SELECT_FIELD:
1161 jonathan 1307 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 jonathan 650
1168     elif case == EB_GEN_CLASS:
1169 jonathan 1307 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 jonathan 650
1174 jonathan 1307 for id, enable in list.items():
1175     win = self.FindWindowById(id)
1176     if win:
1177     win.Enable(enable)
1178 jonathan 650
1179 jan 2386 ID_SELPROP_SPINCTRL_LINEWIDTH = 4002
1180 jonathan 430 ID_SELPROP_PREVIEW = 4003
1181     ID_SELPROP_STROKECLR = 4004
1182     ID_SELPROP_FILLCLR = 4005
1183 jonathan 485 ID_SELPROP_STROKECLRTRANS = 4006
1184     ID_SELPROP_FILLCLRTRANS = 4007
1185 jan 2386 ID_SELPROP_SPINCTRL_SIZE = 4008
1186 jonathan 415
1187     class SelectPropertiesDialog(wxDialog):
1188 jonathan 1527 """Dialog that allows the user to select group properties."""
1189 jonathan 415
1190     def __init__(self, parent, prop, shapeType):
1191 jonathan 1527 """Open the dialog with the initial prop properties and shapeType."""
1192    
1193 jonathan 415 wxDialog.__init__(self, parent, -1, _("Select Properties"),
1194 jonathan 507 style = wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
1195 jonathan 415
1196 jonathan 441 self.prop = ClassGroupProperties(prop)
1197 jonathan 415
1198 jonathan 430 topBox = wxBoxSizer(wxVERTICAL)
1199 jonathan 415
1200 jonathan 430 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 jonathan 630
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 jonathan 615 previewBox.Add(self.previewWin, 1, wxGROW | wxALL, 4)
1214 jonathan 430
1215     itemBox.Add(previewBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
1216    
1217     # control box
1218     ctrlBox = wxBoxSizer(wxVERTICAL)
1219 jonathan 485
1220     lineColorBox = wxBoxSizer(wxHORIZONTAL)
1221 jonathan 813 button = wxButton(self, ID_SELPROP_STROKECLR, _("Change Line Color"))
1222     button.SetFocus()
1223     lineColorBox.Add(button, 1, wxALL | wxGROW, 4)
1224 jonathan 460 EVT_BUTTON(self, ID_SELPROP_STROKECLR, self._OnChangeLineColor)
1225 jonathan 430
1226 jonathan 485 lineColorBox.Add(
1227 jonathan 500 wxButton(self, ID_SELPROP_STROKECLRTRANS, _("Transparent")),
1228 jonathan 485 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 jonathan 430 if shapeType != SHAPETYPE_ARC:
1236 jonathan 485 fillColorBox = wxBoxSizer(wxHORIZONTAL)
1237     fillColorBox.Add(
1238 jonathan 500 wxButton(self, ID_SELPROP_FILLCLR, _("Change Fill Color")),
1239 jonathan 485 1, wxALL | wxGROW, 4)
1240 jonathan 460 EVT_BUTTON(self, ID_SELPROP_FILLCLR, self._OnChangeFillColor)
1241 jonathan 485 fillColorBox.Add(
1242 jonathan 500 wxButton(self, ID_SELPROP_FILLCLRTRANS, _("Transparent")),
1243 jonathan 485 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 jonathan 430
1249 jan 2386 # Line width selection
1250 jonathan 415 spinBox = wxBoxSizer(wxHORIZONTAL)
1251 jonathan 460 spinBox.Add(wxStaticText(self, -1, _("Line Width: ")),
1252 jonathan 430 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
1253 jan 2386 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 jonathan 415
1259 jan 2386 EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL_LINEWIDTH,
1260     self._OnSpinLineWidth)
1261 jonathan 415
1262 jan 2386 spinBox.Add(self.spinCtrl_linewidth, 0, wxALIGN_LEFT | wxALL, 4)
1263     ctrlBox.Add(spinBox, 0, wxALIGN_RIGHT | wxALL, 0)
1264 jonathan 415
1265 jan 2386 # 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 jonathan 430 itemBox.Add(ctrlBox, 0, wxALIGN_RIGHT | wxALL | wxGROW, 0)
1282     topBox.Add(itemBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
1283 jonathan 415
1284     #
1285     # Control buttons:
1286     #
1287     buttonBox = wxBoxSizer(wxHORIZONTAL)
1288 jonathan 813 button_ok = wxButton(self, wxID_OK, _("OK"))
1289 frank 977 buttonBox.Add(button_ok, 0, wxRIGHT|wxEXPAND, 10)
1290 jonathan 813 buttonBox.Add(wxButton(self, wxID_CANCEL, _("Cancel")),
1291 frank 977 0, wxRIGHT|wxEXPAND, 10)
1292     topBox.Add(buttonBox, 0, wxALIGN_RIGHT|wxBOTTOM|wxTOP, 10)
1293 jonathan 1342
1294     button_ok.SetDefault()
1295 jan 2376
1296 jonathan 813 #EVT_BUTTON(self, wxID_OK, self._OnOK)
1297     #EVT_BUTTON(self, ID_SELPROP_CANCEL, self._OnCancel)
1298 jan 2376
1299 jonathan 509 self.SetAutoLayout(True)
1300 jonathan 415 self.SetSizer(topBox)
1301     topBox.Fit(self)
1302     topBox.SetSizeHints(self)
1303    
1304 jonathan 813 def OnOK(self, event):
1305 jonathan 372 self.EndModal(wxID_OK)
1306    
1307 jonathan 813 def OnCancel(self, event):
1308 jonathan 372 self.EndModal(wxID_CANCEL)
1309    
1310 jan 2386 def _OnSpinLineWidth(self, event):
1311     self.prop.SetLineWidth(self.spinCtrl_linewidth.GetValue())
1312 jonathan 549 self.previewWin.Refresh()
1313 jonathan 392
1314 jan 2386 def _OnSpinSize(self, event):
1315     self.prop.SetSize(self.spinCtrl_size.GetValue())
1316     self.previewWin.Refresh()
1317    
1318 jonathan 430 def __GetColor(self, cur):
1319 jan 2200 dialog = ColorDialog(self)
1320     dialog.SetColor(cur)
1321 jonathan 606
1322 jonathan 430 ret = None
1323     if dialog.ShowModal() == wxID_OK:
1324 jan 2200 ret = dialog.GetColor()
1325 jonathan 430
1326     dialog.Destroy()
1327    
1328     return ret
1329 jan 2376
1330 jonathan 460 def _OnChangeLineColor(self, event):
1331     clr = self.__GetColor(self.prop.GetLineColor())
1332 jonathan 430 if clr is not None:
1333 jonathan 460 self.prop.SetLineColor(clr)
1334 jonathan 549 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1335 jonathan 430
1336 jonathan 485 def _OnChangeLineColorTrans(self, event):
1337 jonathan 1342 self.prop.SetLineColor(Transparent)
1338 jonathan 549 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1339 jan 2376
1340 jonathan 460 def _OnChangeFillColor(self, event):
1341 jonathan 430 clr = self.__GetColor(self.prop.GetFill())
1342     if clr is not None:
1343     self.prop.SetFill(clr)
1344 jonathan 549 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1345 jonathan 430
1346 jonathan 485 def _OnChangeFillColorTrans(self, event):
1347 jonathan 1342 self.prop.SetFill(Transparent)
1348 jonathan 549 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1349 jonathan 485
1350 jonathan 441 def GetClassGroupProperties(self):
1351 jonathan 415 return self.prop
1352 jonathan 392
1353    
1354 jonathan 549 class ClassDataPreviewWindow(wxWindow):
1355 jonathan 1527 """A custom window that draws group properties using the correct shape."""
1356 jonathan 415
1357 jonathan 441 def __init__(self, rect, prop, shapeType,
1358 jonathan 430 parent = None, id = -1, size = wxDefaultSize):
1359 jonathan 1527 """Draws the appropriate shape as specified with shapeType using
1360     prop properities.
1361     """
1362 jonathan 430 if parent is not None:
1363 jonathan 549 wxWindow.__init__(self, parent, id, (0, 0), size)
1364 jonathan 460 EVT_PAINT(self, self._OnPaint)
1365 jonathan 415
1366 jonathan 430 self.rect = rect
1367 jonathan 549
1368 jonathan 441 self.prop = prop
1369 jonathan 430 self.shapeType = shapeType
1370 jonathan 549 self.previewer = ClassDataPreviewer()
1371 jonathan 430
1372 jonathan 630 def GetProperties():
1373     return self.prop
1374    
1375 jonathan 460 def _OnPaint(self, event):
1376 jonathan 430 dc = wxPaintDC(self)
1377    
1378     # XXX: this doesn't seem to be having an effect:
1379     dc.DestroyClippingRegion()
1380    
1381 jonathan 549 if self.rect is None:
1382     w, h = self.GetSize()
1383     rect = wxRect(0, 0, w, h)
1384     else:
1385     rect = self.rect
1386 jonathan 430
1387 jonathan 549 self.previewer.Draw(dc, rect, self.prop, self.shapeType)
1388 jonathan 430
1389 jonathan 549 class ClassDataPreviewer:
1390 jonathan 1527 """Class that actually draws a group property preview."""
1391 jonathan 430
1392 jonathan 549 def Draw(self, dc, rect, prop, shapeType):
1393 jan 2376 """Draw the property.
1394 jonathan 549
1395 jan 2376 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 jonathan 606 assert dc is not None
1402     assert isinstance(prop, ClassGroupProperties)
1403 jonathan 549
1404 jonathan 430 if rect is None:
1405 jonathan 549 x = 0
1406     y = 0
1407     w, h = dc.GetSize()
1408 jonathan 430 else:
1409     x = rect.GetX()
1410     y = rect.GetY()
1411     w = rect.GetWidth()
1412     h = rect.GetHeight()
1413    
1414 jonathan 460 stroke = prop.GetLineColor()
1415 jonathan 1342 if stroke is Transparent:
1416 jonathan 392 pen = wxTRANSPARENT_PEN
1417     else:
1418 jonathan 430 pen = wxPen(Color2wxColour(stroke),
1419 jonathan 460 prop.GetLineWidth(),
1420 jonathan 392 wxSOLID)
1421    
1422 jonathan 441 stroke = prop.GetFill()
1423 jonathan 1342 if stroke is Transparent:
1424 jonathan 392 brush = wxTRANSPARENT_BRUSH
1425     else:
1426 jonathan 430 brush = wxBrush(Color2wxColour(stroke), wxSOLID)
1427 jonathan 392
1428     dc.SetPen(pen)
1429     dc.SetBrush(brush)
1430    
1431 jonathan 415 if shapeType == SHAPETYPE_ARC:
1432 jonathan 430 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 jonathan 392
1437 jonathan 576 elif shapeType == SHAPETYPE_POINT:
1438 jonathan 415
1439 jan 2376 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 jonathan 392
1448 jonathan 576 elif shapeType == SHAPETYPE_POLYGON:
1449     dc.DrawRectangle(x, y, w, h)
1450    
1451 jan 2376 return None
1452    
1453 jonathan 415 class ClassRenderer(wxPyGridCellRenderer):
1454 jonathan 1527 """A wrapper class that can be used to draw group properties in a
1455     grid table.
1456     """
1457 jonathan 415
1458     def __init__(self, shapeType):
1459     wxPyGridCellRenderer.__init__(self)
1460 jonathan 549 self.shapeType = shapeType
1461     self.previewer = ClassDataPreviewer()
1462 jonathan 415
1463     def Draw(self, grid, attr, dc, rect, row, col, isSelected):
1464 jonathan 485 data = grid.GetTable().GetClassGroup(row)
1465 jonathan 415
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 jonathan 441 if not isinstance(data, ClassGroupMap):
1474 jan 2376 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 jonathan 415
1482 jan 2376 # 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 jonathan 415 if isSelected:
1496 jonathan 615 dc.SetPen(wxPen(wxBLACK, 1, wxSOLID))
1497 jonathan 415 dc.SetBrush(wxTRANSPARENT_BRUSH)
1498 jonathan 615
1499 jonathan 415 dc.DrawRectangle(rect.GetX(), rect.GetY(),
1500     rect.GetWidth(), rect.GetHeight())
1501    
1502 jonathan 392 dc.DestroyClippingRegion()
1503    
1504 jonathan 630
1505     class ClassGroupPropertiesCtrl(wxWindow, wxControl):
1506 jonathan 1527 """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 jonathan 630
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 jonathan 1302 self.parent = parent
1517    
1518 jonathan 630 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 jonathan 1527 """Allow/Disallow double-clicking on the control."""
1557 jonathan 630 self.allowEdit = allow
1558    
1559     def DoEdit(self):
1560 jonathan 1527 """Open the properties selector dialog."""
1561    
1562 jonathan 630 if not self.allowEdit: return
1563    
1564 jonathan 1302 propDlg = SelectPropertiesDialog(self.parent,
1565 jonathan 630 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 joey 2366
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