/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/UI/classifier.py
ViewVC logotype

Annotation of /branches/WIP-pyshapelib-bramz/Thuban/UI/classifier.py

Parent Directory Parent Directory | Revision Log Revision Log


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