/[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 1527 - (hide annotations)
Wed Jul 30 15:43:28 2003 UTC (21 years, 7 months ago) by jonathan
File MIME type: text/x-python
File size: 49686 byte(s)
Add docstrings.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26