/[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 2556 - (hide annotations)
Mon Feb 7 13:46:53 2005 UTC (20 years, 1 month ago) by bernhard
Original Path: trunk/thuban/Thuban/UI/classifier.py
File MIME type: text/x-python
File size: 49098 byte(s)
	* Thuban/UI/classifier.py (ClassGrid.DeleteSelectedRows):
	Enable translation for message string.

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