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

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26