/[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 2597 - (hide annotations)
Thu Apr 7 19:46:29 2005 UTC (19 years, 11 months ago) by bh
File MIME type: text/x-python
File size: 49054 byte(s)
(ClassGrid.__init__): Use -1 as the ID.
(ID_CLASS_TABLE): Removed. It wasn't used anywhere except in
ClassGrid.__init__ and it's value is outside of the valid
range (must be < 32768).  wxPython 2.5 complains about it with an
exception.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26