/[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 1464 - (hide annotations)
Fri Jul 18 18:20:40 2003 UTC (21 years, 7 months ago) by bh
File MIME type: text/x-python
File size: 46758 byte(s)
* Thuban/UI/messages.py (MAP_REPLACED): New message.

* Thuban/UI/viewport.py (ViewPort.SetMap): Issue MAP_REPLACED
after the new map has been assigned

* Thuban/UI/mainwindow.py (MainWindow.delegated_messages):
Delegate MAP_REPLACED to the canvas too
(MainWindow.prepare_new_session): Removed. Thanks to the new
MAP_REPLACED message it's no longer needed
(MainWindow.OpenSession, MainWindow.NewSession):
prepare_new_session has been removed.

* Thuban/UI/classifier.py (Classifier.__init__): Subscribe to
MAP_REPLACED so that we can close the dialog if a new map is set.
(Classifier.unsubscribe_messages): Unsubscribe from MAP_REPLACED
(Classifier.map_replaced): Handle MAP_REPLACED by closing the
dialog

* test/test_viewport.py (SimpleViewPortTest)
(SimpleViewPortTest.test_default_size): Add doc-strings
(ViewPortTest.setUp): Bind map to self.map so we can use it in
tests. Subscribe to MAP_REPLACED messages too.
(ViewPortTest.tearDown): No need to explicitly unsubscribe
(ViewPortTest.test_set_map): New test for the SetMap method.

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     return self.clazz
320    
321 jonathan 606 def SetClassification(self, clazz, group = None):
322    
323     self.GetView().BeginBatch()
324    
325     old_len = self.GetNumberRows()
326 jonathan 376
327 jonathan 570 row = -1
328 jonathan 615 self.clazz = clazz
329 jonathan 441
330 jonathan 606 self.__NotifyRowChanges(old_len, self.GetNumberRows())
331 jonathan 415
332 jonathan 638 #
333     # XXX: this is dead code at the moment
334     #
335 jonathan 570 if row > -1:
336     self.GetView().ClearSelection()
337     self.GetView().SelectRow(row)
338     self.GetView().MakeCellVisible(row, 0)
339 jonathan 606
340 jonathan 615 self.__Modified()
341    
342 jonathan 638
343 jonathan 451 self.GetView().EndBatch()
344 jonathan 606 self.GetView().FitInside()
345 jonathan 451
346     def __NotifyRowChanges(self, curRows, newRows):
347 jonathan 415 #
348     # silly message processing for updates to the number of
349     # rows and columns
350     #
351     if newRows > curRows:
352     msg = wxGridTableMessage(self,
353     wxGRIDTABLE_NOTIFY_ROWS_APPENDED,
354     newRows - curRows) # how many
355     self.GetView().ProcessTableMessage(msg)
356 jonathan 519 self.GetView().FitInside()
357 jonathan 415 elif newRows < curRows:
358     msg = wxGridTableMessage(self,
359     wxGRIDTABLE_NOTIFY_ROWS_DELETED,
360 jonathan 606 curRows, # position
361 jonathan 415 curRows - newRows) # how many
362     self.GetView().ProcessTableMessage(msg)
363 jonathan 519 self.GetView().FitInside()
364 jonathan 415
365 jonathan 615
366 jonathan 441 def __SetRow(self, row, group):
367 jonathan 460 """Set a row's data to that of the group.
368 jonathan 441
369 jonathan 485 The table is considered modified after this operation.
370    
371 jonathan 606 row -- if row is < 0 'group' is inserted at the top of the table
372     if row is >= GetNumberRows() or None 'group' is append to
373     the end of the table.
374     otherwise 'group' replaces row 'row'
375 jonathan 460 """
376 jonathan 441
377 jonathan 460 # either append or replace
378 jonathan 606 if row is None or row >= self.GetNumberRows():
379 jonathan 615 self.clazz.AppendGroup(group)
380 jonathan 606 elif row < 0:
381 jonathan 615 self.clazz.InsertGroup(0, group)
382 jonathan 441 else:
383 jonathan 615 if row == 0:
384     self.clazz.SetDefaultGroup(group)
385     else:
386     self.clazz.ReplaceGroup(row - 1, group)
387 jonathan 441
388 jonathan 460 self.__Modified()
389    
390 jonathan 415 def GetColLabelValue(self, col):
391 jonathan 460 """Return the label for the given column."""
392 jonathan 415 return self.__col_labels[col]
393    
394     def GetRowLabelValue(self, row):
395 jonathan 460 """Return the label for the given row."""
396 jonathan 415
397 jonathan 615 if row == 0:
398     return _("Default")
399     else:
400     group = self.clazz.GetGroup(row - 1)
401     if isinstance(group, ClassGroupDefault): return _("Default")
402     if isinstance(group, ClassGroupSingleton): return _("Singleton")
403     if isinstance(group, ClassGroupRange): return _("Range")
404     if isinstance(group, ClassGroupMap): return _("Map")
405 jonathan 460
406 jonathan 606 assert False # shouldn't get here
407 bh 671 return ""
408 jonathan 460
409 jonathan 376 def GetNumberRows(self):
410 jonathan 460 """Return the number of rows."""
411 jonathan 615 if self.clazz is None:
412     return 0
413 jonathan 376
414 jonathan 615 return self.clazz.GetNumGroups() + 1 # +1 for default group
415    
416 jonathan 376 def GetNumberCols(self):
417 jonathan 460 """Return the number of columns."""
418 jonathan 638 return NUM_COLS
419 jonathan 376
420     def IsEmptyCell(self, row, col):
421 jonathan 460 """Determine if a cell is empty. This is always false."""
422     return False
423 jonathan 376
424     def GetValue(self, row, col):
425 jonathan 460 """Return the object that is used to represent the given
426     cell coordinates. This may not be a string."""
427     return self.GetValueAsCustom(row, col, None)
428 jonathan 376
429     def SetValue(self, row, col, value):
430 jonathan 460 """Assign 'value' to the cell specified by 'row' and 'col'.
431    
432     The table is considered modified after this operation.
433     """
434    
435     self.SetValueAsCustom(row, col, None, value)
436 jonathan 415
437 jonathan 392 def GetValueAsCustom(self, row, col, typeName):
438 jonathan 460 """Return the object that is used to represent the given
439     cell coordinates. This may not be a string.
440    
441     typeName -- unused, but needed to overload wxPyGridTableBase
442     """
443 jonathan 376
444 jonathan 615 if row == 0:
445     group = self.clazz.GetDefaultGroup()
446     else:
447     group = self.clazz.GetGroup(row - 1)
448 jonathan 460
449 jonathan 615
450 jonathan 638 if col == COL_VISIBLE:
451     return group.IsVisible()
452    
453 jonathan 460 if col == COL_SYMBOL:
454 jonathan 485 return group.GetProperties()
455 jonathan 460
456     if col == COL_LABEL:
457     return group.GetLabel()
458    
459     # col must be COL_VALUE
460 jonathan 606 assert col == COL_VALUE
461 jonathan 460
462     if isinstance(group, ClassGroupDefault):
463     return _("DEFAULT")
464     elif isinstance(group, ClassGroupSingleton):
465     return group.GetValue()
466     elif isinstance(group, ClassGroupRange):
467 jonathan 878 return group.GetRange()
468 jonathan 460
469 jonathan 878 assert False # shouldn't get here
470 jonathan 460 return None
471    
472 jonathan 415 def __ParseInput(self, value):
473     """Try to determine what kind of input value is
474 jonathan 460 (string, number, or range)
475    
476 jonathan 878 Returns a tuple (type, data) where type is 0 if data is
477     a singleton value, or 1 if is a range
478 jonathan 415 """
479 jonathan 392
480 jonathan 485 type = self.fieldType
481 jonathan 415
482 jonathan 460 if type == FIELDTYPE_STRING:
483 jonathan 878 return (0, value)
484 jonathan 630 elif type in (FIELDTYPE_INT, FIELDTYPE_DOUBLE):
485 jonathan 460 if type == FIELDTYPE_INT:
486 jonathan 782 # the float call allows the user to enter 1.0 for 1
487 jonathan 460 conv = lambda p: int(float(p))
488     else:
489 jonathan 878 conv = float
490 jonathan 460
491 jonathan 451 #
492     # first try to take the input as a single number
493     # if there's an exception try to break it into
494 jonathan 878 # a range. if there is an exception here, let it
495     # pass up to the calling function.
496 jonathan 451 #
497     try:
498 jonathan 878 return (0, conv(value))
499 jonathan 460 except ValueError:
500 jonathan 878 return (1, Range(value))
501 jonathan 451
502 jonathan 606 assert False # shouldn't get here
503 jonathan 878 return (0,None)
504 jonathan 415
505     def SetValueAsCustom(self, row, col, typeName, value):
506 jonathan 460 """Set the cell specified by 'row' and 'col' to 'value'.
507 jonathan 415
508 jonathan 460 If column represents the value column, the input is parsed
509     to determine if a string, number, or range was entered.
510     A new ClassGroup may be created if the type of data changes.
511    
512     The table is considered modified after this operation.
513    
514     typeName -- unused, but needed to overload wxPyGridTableBase
515     """
516    
517 jonathan 650 assert 0 <= col < self.GetNumberCols()
518     assert 0 <= row < self.GetNumberRows()
519 jonathan 460
520 jonathan 615 if row == 0:
521     group = self.clazz.GetDefaultGroup()
522     else:
523     group = self.clazz.GetGroup(row - 1)
524 jonathan 460
525 jonathan 485 mod = True # assume the data will change
526 jonathan 460
527 jonathan 638 if col == COL_VISIBLE:
528     group.SetVisible(value)
529     elif col == COL_SYMBOL:
530 jonathan 485 group.SetProperties(value)
531     elif col == COL_LABEL:
532     group.SetLabel(value)
533 jonathan 415 elif col == COL_VALUE:
534 jonathan 451 if isinstance(group, ClassGroupDefault):
535     # not allowed to modify the default value
536     pass
537     elif isinstance(group, ClassGroupMap):
538     # something special
539     pass
540     else: # SINGLETON, RANGE
541     try:
542     dataInfo = self.__ParseInput(value)
543 jonathan 460 except ValueError:
544 jonathan 451 # bad input, ignore the request
545 jonathan 485 mod = False
546 jonathan 451 else:
547 jonathan 415
548 jonathan 485 changed = False
549 jonathan 451 ngroup = group
550     props = group.GetProperties()
551 jonathan 460
552     #
553     # try to update the values, which may include
554     # changing the underlying group type if the
555     # group was a singleton and a range was entered
556     #
557 jonathan 878 if dataInfo[0] == 0:
558 jonathan 451 if not isinstance(group, ClassGroupSingleton):
559 jonathan 782 ngroup = ClassGroupSingleton(props = props)
560 jonathan 485 changed = True
561 jonathan 878 ngroup.SetValue(dataInfo[1])
562     elif dataInfo[0] == 1:
563 jonathan 451 if not isinstance(group, ClassGroupRange):
564 jonathan 782 ngroup = ClassGroupRange(props = props)
565 jonathan 485 changed = True
566 jonathan 878 ngroup.SetRange(dataInfo[1])
567 jonathan 415 else:
568 jonathan 606 assert False
569 jonathan 485 pass
570 jonathan 415
571 jonathan 485 if changed:
572     ngroup.SetLabel(group.GetLabel())
573     self.SetClassGroup(row, ngroup)
574     else:
575 jonathan 606 assert False # shouldn't be here
576 jonathan 485 pass
577 jonathan 460
578 jonathan 485 if mod:
579 jonathan 460 self.__Modified()
580     self.GetView().Refresh()
581 jonathan 415
582     def GetAttr(self, row, col, someExtraParameter):
583 jonathan 460 """Returns the cell attributes"""
584    
585 jonathan 638 return self.__colAttr.get(col, wxGridCellAttr()).Clone()
586 jonathan 415
587 jonathan 441 def GetClassGroup(self, row):
588 jonathan 460 """Return the ClassGroup object representing row 'row'."""
589 jonathan 415
590 jonathan 615 #return self.GetValueAsCustom(row, COL_SYMBOL, None)
591     if row == 0:
592     return self.clazz.GetDefaultGroup()
593     else:
594     return self.clazz.GetGroup(row - 1)
595 jonathan 415
596 jonathan 460 def SetClassGroup(self, row, group):
597 jonathan 485 self.__SetRow(row, group)
598     self.GetView().Refresh()
599 jonathan 460
600     def __Modified(self, mod = True):
601 jonathan 485 """Adjust the modified flag.
602 jonathan 460
603 jonathan 485 mod -- if -1 set the modified flag to False, otherwise perform
604     an 'or' operation with the current value of the flag and
605     'mod'
606     """
607    
608     if mod == -1:
609     self.modified = False
610     else:
611     self.modified = mod or self.modified
612    
613 jonathan 415 def IsModified(self):
614 jonathan 460 """True if this table is considered modified."""
615 jonathan 415 return self.modified
616    
617 jonathan 451 def DeleteRows(self, pos, numRows = 1):
618 jonathan 485 """Deletes 'numRows' beginning at row 'pos'.
619 jonathan 460
620 jonathan 485 The row representing the default group is not removed.
621    
622     The table is considered modified if any rows are removed.
623 jonathan 460 """
624    
625 jonathan 606 assert pos >= 0
626 jonathan 615 old_len = self.GetNumberRows()
627 jonathan 451 for row in range(pos, pos - numRows, -1):
628 jonathan 485 group = self.GetClassGroup(row)
629 jonathan 615 if row != 0:
630     self.clazz.RemoveGroup(row - 1)
631 jonathan 451 self.__Modified()
632    
633     if self.IsModified():
634 jonathan 615 self.__NotifyRowChanges(old_len, self.GetNumberRows())
635 jonathan 415
636 jonathan 451 def AppendRows(self, numRows = 1):
637 jonathan 485 """Append 'numRows' empty rows to the end of the table.
638 jonathan 460
639 jonathan 485 The table is considered modified if any rows are appended.
640     """
641    
642 jonathan 615 old_len = self.GetNumberRows()
643 jonathan 451 for i in range(numRows):
644     np = ClassGroupSingleton()
645 jonathan 606 self.__SetRow(None, np)
646 jonathan 451
647     if self.IsModified():
648 jonathan 615 self.__NotifyRowChanges(old_len, self.GetNumberRows())
649 jonathan 451
650    
651 jonathan 650 ID_PROPERTY_REVERT = 4002
652     ID_PROPERTY_ADD = 4003
653     ID_PROPERTY_GENCLASS = 4004
654     ID_PROPERTY_REMOVE = 4005
655     ID_PROPERTY_MOVEUP = 4006
656     ID_PROPERTY_MOVEDOWN = 4007
657     ID_PROPERTY_TRY = 4008
658     ID_PROPERTY_EDITSYM = 4009
659     ID_PROPERTY_SELECT = 4011
660     ID_PROPERTY_TITLE = 4012
661     ID_PROPERTY_FIELDTEXT = 4013
662 jonathan 630
663     BTN_ADD = 0
664     BTN_EDIT = 1
665     BTN_GEN = 2
666     BTN_UP = 3
667     BTN_DOWN = 4
668     BTN_RM = 5
669    
670 jonathan 650 EB_LAYER_TITLE = 0
671     EB_SELECT_FIELD = 1
672     EB_GEN_CLASS = 2
673    
674 frank 1058 class Classifier(NonModalNonParentDialog):
675 bh 535
676 jonathan 630 type2string = {None: _("None"),
677     FIELDTYPE_STRING: _("Text"),
678     FIELDTYPE_INT: _("Integer"),
679     FIELDTYPE_DOUBLE: _("Decimal")}
680    
681 bh 1142 def __init__(self, parent, name, map, layer, group = None):
682 frank 1058 NonModalNonParentDialog.__init__(self, parent, name, "")
683 jonathan 372
684 jonathan 650 self.__SetTitle(layer.Title())
685 jonathan 509
686 bh 1464 self.parent.Subscribe(MAP_REPLACED, self.map_replaced)
687 jonathan 415 self.layer = layer
688 bh 1142 self.map = map
689 jonathan 415
690 bh 1142 self.map.Subscribe(MAP_LAYERS_REMOVED, self.map_layers_removed)
691     self.layer.Subscribe(LAYER_SHAPESTORE_REPLACED,
692     self.layer_shapestore_replaced)
693 jonathan 485
694 jonathan 630 self.genDlg = None
695    
696 jonathan 813 ############################
697     # Create the controls
698     #
699 jonathan 372
700 jonathan 813 panel = wxPanel(self, -1)
701 jonathan 661
702 jonathan 813 text_title = wxTextCtrl(panel, ID_PROPERTY_TITLE, layer.Title())
703 jonathan 935 self.fieldTypeText = wxStaticText(panel, -1, "")
704 jonathan 372
705 jonathan 935 if layer.HasClassification():
706     self.originalClass = self.layer.GetClassification()
707 bh 1452 self.originalClassField = self.layer.GetClassificationColumn()
708 jonathan 1433 field = self.originalClassField
709     fieldType = self.layer.GetFieldType(field)
710 jonathan 460
711 bh 1219 table = layer.ShapeStore().Table()
712 jonathan 935 #
713     # make field choice box
714     #
715     self.fields = wxChoice(panel, ID_PROPERTY_SELECT,)
716 jonathan 460
717 bh 1219 self.num_cols = table.NumColumns()
718 jonathan 935 # just assume the first field in case one hasn't been
719     # specified in the file.
720     self.__cur_field = 0
721 jonathan 650
722 jonathan 935 self.fields.Append("<None>")
723 jonathan 451
724 jonathan 1433 if fieldType is None:
725 jonathan 935 self.fields.SetClientData(0, copy.deepcopy(self.originalClass))
726 jonathan 451 else:
727 jonathan 935 self.fields.SetClientData(0, None)
728 jonathan 372
729 jonathan 935 for i in range(self.num_cols):
730 bh 1219 name = table.Column(i).name
731 jonathan 935 self.fields.Append(name)
732 jonathan 372
733 jonathan 935 if name == field:
734     self.__cur_field = i + 1
735     self.fields.SetClientData(i + 1,
736 jonathan 1433 copy.deepcopy(self.originalClass))
737 jonathan 935 else:
738     self.fields.SetClientData(i + 1, None)
739 jonathan 485
740 jonathan 935 button_gen = wxButton(panel, ID_PROPERTY_GENCLASS,
741     _("Generate Class"))
742     button_add = wxButton(panel, ID_PROPERTY_ADD,
743     _("Add"))
744     button_moveup = wxButton(panel, ID_PROPERTY_MOVEUP,
745     _("Move Up"))
746     button_movedown = wxButton(panel, ID_PROPERTY_MOVEDOWN,
747     _("Move Down"))
748     button_edit = wxButton(panel, ID_PROPERTY_EDITSYM,
749     _("Edit Symbol"))
750     button_remove = wxButton(panel, ID_PROPERTY_REMOVE,
751     _("Remove"))
752 jonathan 650
753 jonathan 935 self.classGrid = ClassGrid(panel, self)
754 jonathan 650
755 jonathan 935 # calling __SelectField after creating the classGrid fills in the
756     # grid with the correct information
757     self.fields.SetSelection(self.__cur_field)
758     self.__SelectField(self.__cur_field, group = group)
759 jonathan 650
760 frank 977 button_try = wxButton(self, ID_PROPERTY_TRY, _("Try"))
761     button_revert = wxButton(self, ID_PROPERTY_REVERT, _("Revert"))
762     button_ok = wxButton(self, wxID_OK, _("OK"))
763 jonathan 1342 button_close = wxButton(self, wxID_CANCEL, _("Close"))
764 jonathan 813 button_ok.SetDefault()
765 jonathan 485
766 jonathan 813 ############################
767     # Layout the controls
768 jonathan 485 #
769 jonathan 509
770 jonathan 813 topBox = wxBoxSizer(wxVERTICAL)
771     panelBox = wxBoxSizer(wxVERTICAL)
772 jonathan 451
773 jonathan 813 sizer = wxBoxSizer(wxHORIZONTAL)
774     sizer.Add(wxStaticText(panel, -1, _("Title: ")),
775     0, wxALIGN_LEFT | wxALL | wxALIGN_CENTER_VERTICAL, 4)
776     sizer.Add(text_title, 1, wxGROW, 0)
777 jonathan 485
778 jonathan 813 panelBox.Add(sizer, 0, wxGROW, 4)
779 jonathan 606
780 jonathan 935 if isinstance(layer, RasterLayer):
781     type = "Image"
782     else:
783     type = layer.ShapeType()
784    
785     panelBox.Add(wxStaticText(panel, -1, _("Type: %s") % type),
786 jonathan 813 0, wxALIGN_LEFT | wxALL, 4)
787 jonathan 485
788 jonathan 935 if layer.HasClassification():
789 jonathan 485
790 jonathan 935 classBox = wxStaticBoxSizer(
791     wxStaticBox(panel, -1, _("Classification")), wxVERTICAL)
792 jonathan 485
793    
794 jonathan 935 sizer = wxBoxSizer(wxHORIZONTAL)
795     sizer.Add(wxStaticText(panel, ID_PROPERTY_FIELDTEXT, _("Field: ")),
796     0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
797     sizer.Add(self.fields, 1, wxGROW | wxALL, 4)
798 jonathan 570
799 jonathan 935 classBox.Add(sizer, 0, wxGROW, 4)
800 jonathan 570
801 jonathan 935 classBox.Add(self.fieldTypeText, 0,
802     wxGROW | wxALIGN_LEFT | wxALL | wxADJUST_MINSIZE, 4)
803 jonathan 570
804 jonathan 935 controlBox = wxBoxSizer(wxHORIZONTAL)
805     controlButtonBox = wxBoxSizer(wxVERTICAL)
806 jonathan 570
807 jonathan 935 controlButtonBox.Add(button_gen, 0, wxGROW|wxALL, 4)
808     controlButtonBox.Add(button_add, 0, wxGROW|wxALL, 4)
809     controlButtonBox.Add(button_moveup, 0, wxGROW|wxALL, 4)
810     controlButtonBox.Add(button_movedown, 0, wxGROW|wxALL, 4)
811     controlButtonBox.Add(button_edit, 0, wxGROW|wxALL, 4)
812     controlButtonBox.Add(60, 20, 0, wxGROW|wxALL|wxALIGN_BOTTOM, 4)
813     controlButtonBox.Add(button_remove, 0,
814     wxGROW|wxALL|wxALIGN_BOTTOM, 4)
815 jonathan 415
816 jonathan 935 controlBox.Add(self.classGrid, 1, wxGROW, 0)
817     controlBox.Add(controlButtonBox, 0, wxGROW, 10)
818 jonathan 661
819 jonathan 935 classBox.Add(controlBox, 1, wxGROW, 10)
820     panelBox.Add(classBox, 1, wxGROW, 0)
821 jonathan 415
822 jonathan 935
823 jonathan 813 buttonBox = wxBoxSizer(wxHORIZONTAL)
824 frank 977 buttonBox.Add(button_try, 0, wxRIGHT|wxEXPAND, 10)
825     buttonBox.Add(button_revert, 0, wxRIGHT|wxEXPAND, 10)
826     buttonBox.Add(button_ok, 0, wxRIGHT|wxEXPAND, 10)
827     buttonBox.Add(button_close, 0, wxRIGHT|wxEXPAND, 10)
828 jonathan 509
829 jonathan 650 panel.SetAutoLayout(True)
830     panel.SetSizer(panelBox)
831 jonathan 813 panelBox.Fit(panel)
832 jonathan 650 panelBox.SetSizeHints(panel)
833    
834     topBox.Add(panel, 1, wxGROW | wxALL, 4)
835 frank 977 topBox.Add(buttonBox, 0, wxALIGN_RIGHT|wxBOTTOM|wxTOP, 10)
836 jonathan 650
837 jonathan 813 self.SetAutoLayout(True)
838     self.SetSizer(topBox)
839     topBox.Fit(self)
840     topBox.SetSizeHints(self)
841     self.Layout()
842    
843 jonathan 650 ###########
844    
845 jonathan 813 EVT_CHOICE(self, ID_PROPERTY_SELECT, self._OnFieldSelect)
846     EVT_TEXT(self, ID_PROPERTY_TITLE, self._OnTitleChanged)
847     EVT_BUTTON(self, wxID_OK, self._OnOK)
848 jonathan 650 EVT_BUTTON(self, ID_PROPERTY_TRY, self._OnTry)
849 jonathan 813 EVT_BUTTON(self, wxID_CANCEL, self._OnCloseBtn)
850 jonathan 650 EVT_BUTTON(self, ID_PROPERTY_REVERT, self._OnRevert)
851 jonathan 372
852 jonathan 813 EVT_BUTTON(self, ID_PROPERTY_ADD, self._OnAdd)
853     EVT_BUTTON(self, ID_PROPERTY_EDITSYM, self._OnEditSymbol)
854     EVT_BUTTON(self, ID_PROPERTY_REMOVE, self._OnRemove)
855     EVT_BUTTON(self, ID_PROPERTY_GENCLASS, self._OnGenClass)
856     EVT_BUTTON(self, ID_PROPERTY_MOVEUP, self._OnMoveUp)
857     EVT_BUTTON(self, ID_PROPERTY_MOVEDOWN, self._OnMoveDown)
858 jonathan 509
859 jonathan 549 ######################
860    
861 jonathan 935 text_title.SetFocus()
862 jonathan 549 self.haveApplied = False
863    
864 bh 1142 def unsubscribe_messages(self):
865 bh 1464 self.parent.Unsubscribe(MAP_REPLACED, self.map_replaced)
866 bh 1142 self.map.Unsubscribe(MAP_LAYERS_REMOVED, self.map_layers_removed)
867     self.layer.Unsubscribe(LAYER_SHAPESTORE_REPLACED,
868     self.layer_shapestore_replaced)
869    
870     def map_layers_removed(self, map):
871     if self.layer not in self.map.Layers():
872     self.Close()
873    
874     def layer_shapestore_replaced(self, *args):
875 bh 1464 """Subscribed to the map's LAYER_SHAPESTORE_REPLACED message.
876    
877     Close self.
878     """
879 bh 1142 self.Close()
880    
881 bh 1464 def map_replaced(self, *args):
882     """Subscribed to the mainwindow's MAP_REPLACED message. Close self."""
883     self.Close()
884    
885 jonathan 638 def EditSymbol(self, row):
886 jonathan 606 table = self.classGrid.GetTable()
887     prop = table.GetValueAsCustom(row, COL_SYMBOL, None)
888    
889     # get a new ClassGroupProperties object and copy the
890     # values over to our current object
891 jonathan 1302 propDlg = SelectPropertiesDialog(self, prop, self.layer.ShapeType())
892 jonathan 630
893     self.Enable(False)
894 jonathan 606 if propDlg.ShowModal() == wxID_OK:
895     new_prop = propDlg.GetClassGroupProperties()
896     table.SetValueAsCustom(row, COL_SYMBOL, None, new_prop)
897 jonathan 630 self.Enable(True)
898 jonathan 606 propDlg.Destroy()
899    
900 jonathan 630 def _SetClassification(self, clazz):
901    
902     self.fields.SetClientData(self.__cur_field, clazz)
903     self.classGrid.GetTable().SetClassification(clazz)
904 jonathan 606
905 jonathan 1447 def __BuildClassification(self, fieldIndex, copyClass=False, force=False):
906 jonathan 415
907 jonathan 615 # numRows = self.classGrid.GetNumberRows()
908     # assert numRows > 0 # there should always be a default row
909 jonathan 496
910     if fieldIndex == 0:
911     fieldName = None
912     fieldType = None
913     else:
914     fieldName = self.fields.GetString(fieldIndex)
915     fieldType = self.layer.GetFieldType(fieldName)
916 jonathan 415
917 jonathan 1447 clazz = self.fields.GetClientData(fieldIndex)
918     if clazz is None or self.classGrid.GetTable().IsModified() or force:
919     clazz = self.classGrid.GetTable().GetClassification()
920     if copyClass:
921     clazz = copy.deepcopy(clazz)
922 jonathan 615
923 jonathan 1447 return clazz, fieldName
924 jonathan 615
925 jonathan 570 def __SetGridTable(self, fieldIndex, group = None):
926 jonathan 415
927 jonathan 460 clazz = self.fields.GetClientData(fieldIndex)
928 jonathan 415
929 jonathan 460 if clazz is None:
930     clazz = Classification()
931     clazz.SetDefaultGroup(
932     ClassGroupDefault(
933 jonathan 485 self.layer.GetClassification().
934     GetDefaultGroup().GetProperties()))
935 jonathan 460
936 jonathan 1433 fieldName = self.fields.GetString(fieldIndex)
937     fieldType = self.layer.GetFieldType(fieldName)
938 jonathan 460
939 jonathan 1433 self.classGrid.CreateTable(clazz, fieldType,
940     self.layer.ShapeType(), group)
941 jonathan 460
942 jonathan 485 def __SetFieldTypeText(self, fieldIndex):
943     fieldName = self.fields.GetString(fieldIndex)
944     fieldType = self.layer.GetFieldType(fieldName)
945    
946 jonathan 606 assert Classifier.type2string.has_key(fieldType)
947 jonathan 485
948 jonathan 496 text = Classifier.type2string[fieldType]
949    
950 jonathan 650 self.fieldTypeText.SetLabel(_("Data Type: %s") % text)
951 jonathan 485
952 jonathan 570 def __SelectField(self, newIndex, oldIndex = -1, group = None):
953 jonathan 611 """This method assumes that the current selection for the
954     combo has already been set by a call to SetSelection().
955     """
956 jonathan 460
957 jonathan 606 assert oldIndex >= -1
958 jonathan 415
959 jonathan 498 if oldIndex != -1:
960 jonathan 1447 clazz, name = self.__BuildClassification(oldIndex, force = True)
961 jonathan 498 self.fields.SetClientData(oldIndex, clazz)
962 jonathan 485
963 jonathan 570 self.__SetGridTable(newIndex, group)
964 jonathan 498
965 jonathan 1307 self.__EnableButtons(EB_SELECT_FIELD)
966 jonathan 498
967     self.__SetFieldTypeText(newIndex)
968 jonathan 485
969 jonathan 650 def __SetTitle(self, title):
970     if title != "":
971     title = ": " + title
972    
973     self.SetTitle(_("Layer Properties") + title)
974 jonathan 496
975 jonathan 638 def _OnEditSymbol(self, event):
976 jonathan 606 sel = self.classGrid.GetCurrentSelection()
977    
978     if len(sel) == 1:
979 jonathan 638 self.EditSymbol(sel[0])
980 jonathan 606
981 jonathan 496 def _OnFieldSelect(self, event):
982 jonathan 498 index = self.fields.GetSelection()
983     self.__SelectField(index, self.__cur_field)
984     self.__cur_field = index
985 jonathan 485
986 jonathan 638 def _OnTry(self, event):
987 jonathan 415 """Put the data from the table into a new Classification and hand
988     it to the layer.
989     """
990    
991 jonathan 935 if self.layer.HasClassification():
992     clazz = self.fields.GetClientData(self.__cur_field)
993 jonathan 415
994 jonathan 935 #
995     # only build the classification if there wasn't one to
996     # to begin with or it has been modified
997     #
998     self.classGrid.SaveEditControlValue()
999 jonathan 1447 clazz, name = self.__BuildClassification(self.__cur_field, True)
1000 jonathan 415
1001 bh 1452 self.layer.SetClassificationColumn(name)
1002 jonathan 935 self.layer.SetClassification(clazz)
1003 jonathan 415
1004 jonathan 549 self.haveApplied = True
1005    
1006 jonathan 485 def _OnOK(self, event):
1007 jonathan 638 self._OnTry(event)
1008 jonathan 615 self.Close()
1009 jonathan 415
1010 jonathan 813 def OnClose(self, event):
1011 bh 1207 self.unsubscribe_messages()
1012 frank 1058 NonModalNonParentDialog.OnClose(self, event)
1013 jonathan 813
1014 jonathan 615 def _OnCloseBtn(self, event):
1015     """Close is similar to Cancel except that any changes that were
1016     made and applied remain applied, but the currently displayed
1017     classification is discarded.
1018     """
1019    
1020     self.Close()
1021    
1022 jonathan 638 def _OnRevert(self, event):
1023 jonathan 485 """The layer's current classification stays the same."""
1024 jonathan 549 if self.haveApplied:
1025 bh 1452 self.layer.SetClassificationColumn(self.originalClassField)
1026 jonathan 549 self.layer.SetClassification(self.originalClass)
1027    
1028 jonathan 638 #self.Close()
1029 jonathan 415
1030 jonathan 460 def _OnAdd(self, event):
1031 jonathan 451 self.classGrid.AppendRows()
1032 jonathan 415
1033 jonathan 460 def _OnRemove(self, event):
1034 jonathan 451 self.classGrid.DeleteSelectedRows()
1035    
1036 jonathan 606 def _OnGenClass(self, event):
1037 jonathan 415
1038 jonathan 630 self.genDlg = ClassGenDialog(self, self.layer,
1039     self.fields.GetString(self.__cur_field))
1040 jonathan 606
1041 jonathan 630 EVT_CLOSE(self.genDlg, self._OnGenDialogClose)
1042 jonathan 606
1043 jonathan 1307 self.__EnableButtons(EB_GEN_CLASS)
1044 jonathan 630
1045     self.genDlg.Show()
1046    
1047     def _OnGenDialogClose(self, event):
1048     self.genDlg.Destroy()
1049 jonathan 1307 self.genDlg = None
1050     self.__EnableButtons(EB_GEN_CLASS)
1051 jonathan 630
1052 jonathan 460 def _OnMoveUp(self, event):
1053     sel = self.classGrid.GetCurrentSelection()
1054 jonathan 415
1055 jonathan 460 if len(sel) == 1:
1056     i = sel[0]
1057     if i > 1:
1058     table = self.classGrid.GetTable()
1059     x = table.GetClassGroup(i - 1)
1060     y = table.GetClassGroup(i)
1061     table.SetClassGroup(i - 1, y)
1062     table.SetClassGroup(i, x)
1063     self.classGrid.ClearSelection()
1064     self.classGrid.SelectRow(i - 1)
1065 jonathan 570 self.classGrid.MakeCellVisible(i - 1, 0)
1066 jonathan 460
1067     def _OnMoveDown(self, event):
1068     sel = self.classGrid.GetCurrentSelection()
1069    
1070     if len(sel) == 1:
1071     i = sel[0]
1072     table = self.classGrid.GetTable()
1073     if 0 < i < table.GetNumberRows() - 1:
1074     x = table.GetClassGroup(i)
1075     y = table.GetClassGroup(i + 1)
1076     table.SetClassGroup(i, y)
1077     table.SetClassGroup(i + 1, x)
1078     self.classGrid.ClearSelection()
1079     self.classGrid.SelectRow(i + 1)
1080 jonathan 570 self.classGrid.MakeCellVisible(i + 1, 0)
1081 jonathan 460
1082 jonathan 650 def _OnTitleChanged(self, event):
1083     obj = event.GetEventObject()
1084 jonathan 460
1085 jonathan 650 self.layer.SetTitle(obj.GetValue())
1086     self.__SetTitle(self.layer.Title())
1087    
1088 jonathan 1307 self.__EnableButtons(EB_LAYER_TITLE)
1089 jonathan 650
1090 jonathan 1307 def __EnableButtons(self, case):
1091 jonathan 650
1092 jonathan 1307 list = {wxID_OK : True,
1093     wxID_CANCEL : True,
1094     ID_PROPERTY_ADD : True,
1095     ID_PROPERTY_MOVEUP : True,
1096     ID_PROPERTY_MOVEDOWN : True,
1097     ID_PROPERTY_REMOVE : True,
1098     ID_PROPERTY_SELECT : True,
1099     ID_PROPERTY_FIELDTEXT : True,
1100     ID_PROPERTY_GENCLASS : True,
1101     ID_PROPERTY_EDITSYM : True}
1102    
1103 jonathan 650 if case == EB_LAYER_TITLE:
1104 jonathan 1307 if self.layer.Title() == "":
1105     list[wxID_OK] = False
1106     list[wxID_CANCEL] = False
1107 jonathan 650
1108     elif case == EB_SELECT_FIELD:
1109 jonathan 1307 if self.fields.GetSelection() == 0:
1110     list[ID_PROPERTY_GENCLASS] = False
1111     list[ID_PROPERTY_ADD] = False
1112     list[ID_PROPERTY_MOVEUP] = False
1113     list[ID_PROPERTY_MOVEDOWN] = False
1114     list[ID_PROPERTY_REMOVE] = False
1115 jonathan 650
1116     elif case == EB_GEN_CLASS:
1117 jonathan 1307 if self.genDlg is not None:
1118     list[ID_PROPERTY_SELECT] = False
1119     list[ID_PROPERTY_FIELDTEXT] = False
1120     list[ID_PROPERTY_GENCLASS] = False
1121 jonathan 650
1122 jonathan 1307 for id, enable in list.items():
1123     win = self.FindWindowById(id)
1124     if win:
1125     win.Enable(enable)
1126 jonathan 650
1127 jonathan 415 ID_SELPROP_SPINCTRL = 4002
1128 jonathan 430 ID_SELPROP_PREVIEW = 4003
1129     ID_SELPROP_STROKECLR = 4004
1130     ID_SELPROP_FILLCLR = 4005
1131 jonathan 485 ID_SELPROP_STROKECLRTRANS = 4006
1132     ID_SELPROP_FILLCLRTRANS = 4007
1133 jonathan 415
1134     class SelectPropertiesDialog(wxDialog):
1135    
1136     def __init__(self, parent, prop, shapeType):
1137     wxDialog.__init__(self, parent, -1, _("Select Properties"),
1138 jonathan 507 style = wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
1139 jonathan 415
1140 jonathan 441 self.prop = ClassGroupProperties(prop)
1141 jonathan 415
1142 jonathan 430 topBox = wxBoxSizer(wxVERTICAL)
1143 jonathan 415
1144 jonathan 430 itemBox = wxBoxSizer(wxHORIZONTAL)
1145    
1146     # preview box
1147     previewBox = wxBoxSizer(wxVERTICAL)
1148     previewBox.Add(wxStaticText(self, -1, _("Preview:")),
1149     0, wxALIGN_LEFT | wxALL, 4)
1150 jonathan 630
1151     self.previewWin = ClassGroupPropertiesCtrl(
1152     self, ID_SELPROP_PREVIEW, self.prop, shapeType,
1153     (40, 40), wxSIMPLE_BORDER)
1154    
1155     self.previewWin.AllowEdit(False)
1156    
1157 jonathan 615 previewBox.Add(self.previewWin, 1, wxGROW | wxALL, 4)
1158 jonathan 430
1159     itemBox.Add(previewBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
1160    
1161     # control box
1162     ctrlBox = wxBoxSizer(wxVERTICAL)
1163 jonathan 485
1164     lineColorBox = wxBoxSizer(wxHORIZONTAL)
1165 jonathan 813 button = wxButton(self, ID_SELPROP_STROKECLR, _("Change Line Color"))
1166     button.SetFocus()
1167     lineColorBox.Add(button, 1, wxALL | wxGROW, 4)
1168 jonathan 460 EVT_BUTTON(self, ID_SELPROP_STROKECLR, self._OnChangeLineColor)
1169 jonathan 430
1170 jonathan 485 lineColorBox.Add(
1171 jonathan 500 wxButton(self, ID_SELPROP_STROKECLRTRANS, _("Transparent")),
1172 jonathan 485 1, wxALL | wxGROW, 4)
1173     EVT_BUTTON(self, ID_SELPROP_STROKECLRTRANS,
1174     self._OnChangeLineColorTrans)
1175    
1176     ctrlBox.Add(lineColorBox, 0,
1177     wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
1178    
1179 jonathan 430 if shapeType != SHAPETYPE_ARC:
1180 jonathan 485 fillColorBox = wxBoxSizer(wxHORIZONTAL)
1181     fillColorBox.Add(
1182 jonathan 500 wxButton(self, ID_SELPROP_FILLCLR, _("Change Fill Color")),
1183 jonathan 485 1, wxALL | wxGROW, 4)
1184 jonathan 460 EVT_BUTTON(self, ID_SELPROP_FILLCLR, self._OnChangeFillColor)
1185 jonathan 485 fillColorBox.Add(
1186 jonathan 500 wxButton(self, ID_SELPROP_FILLCLRTRANS, _("Transparent")),
1187 jonathan 485 1, wxALL | wxGROW, 4)
1188     EVT_BUTTON(self, ID_SELPROP_FILLCLRTRANS,
1189     self._OnChangeFillColorTrans)
1190     ctrlBox.Add(fillColorBox, 0,
1191     wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
1192 jonathan 430
1193 jonathan 415 spinBox = wxBoxSizer(wxHORIZONTAL)
1194 jonathan 460 spinBox.Add(wxStaticText(self, -1, _("Line Width: ")),
1195 jonathan 430 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
1196 jonathan 415 self.spinCtrl = wxSpinCtrl(self, ID_SELPROP_SPINCTRL,
1197     min=1, max=10,
1198 jonathan 460 value=str(prop.GetLineWidth()),
1199     initial=prop.GetLineWidth())
1200 jonathan 415
1201 jonathan 460 EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL, self._OnSpin)
1202 jonathan 415
1203     spinBox.Add(self.spinCtrl, 0, wxALIGN_LEFT | wxALL, 4)
1204    
1205 jonathan 430 ctrlBox.Add(spinBox, 0, wxALIGN_RIGHT | wxALL, 0)
1206     itemBox.Add(ctrlBox, 0, wxALIGN_RIGHT | wxALL | wxGROW, 0)
1207     topBox.Add(itemBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
1208 jonathan 415
1209     #
1210     # Control buttons:
1211     #
1212     buttonBox = wxBoxSizer(wxHORIZONTAL)
1213 jonathan 813 button_ok = wxButton(self, wxID_OK, _("OK"))
1214 frank 977 buttonBox.Add(button_ok, 0, wxRIGHT|wxEXPAND, 10)
1215 jonathan 813 buttonBox.Add(wxButton(self, wxID_CANCEL, _("Cancel")),
1216 frank 977 0, wxRIGHT|wxEXPAND, 10)
1217     topBox.Add(buttonBox, 0, wxALIGN_RIGHT|wxBOTTOM|wxTOP, 10)
1218 jonathan 1342
1219     button_ok.SetDefault()
1220 jonathan 415
1221 jonathan 813 #EVT_BUTTON(self, wxID_OK, self._OnOK)
1222     #EVT_BUTTON(self, ID_SELPROP_CANCEL, self._OnCancel)
1223 jonathan 415
1224 jonathan 509 self.SetAutoLayout(True)
1225 jonathan 415 self.SetSizer(topBox)
1226     topBox.Fit(self)
1227     topBox.SetSizeHints(self)
1228    
1229 jonathan 813 def OnOK(self, event):
1230 jonathan 372 self.EndModal(wxID_OK)
1231    
1232 jonathan 813 def OnCancel(self, event):
1233 jonathan 372 self.EndModal(wxID_CANCEL)
1234    
1235 jonathan 460 def _OnSpin(self, event):
1236     self.prop.SetLineWidth(self.spinCtrl.GetValue())
1237 jonathan 549 self.previewWin.Refresh()
1238 jonathan 392
1239 jonathan 430 def __GetColor(self, cur):
1240     dialog = wxColourDialog(self)
1241 jonathan 1342 if cur is not Transparent:
1242 jonathan 606 dialog.GetColourData().SetColour(Color2wxColour(cur))
1243    
1244 jonathan 430 ret = None
1245     if dialog.ShowModal() == wxID_OK:
1246     ret = wxColour2Color(dialog.GetColourData().GetColour())
1247    
1248     dialog.Destroy()
1249    
1250     return ret
1251    
1252 jonathan 460 def _OnChangeLineColor(self, event):
1253     clr = self.__GetColor(self.prop.GetLineColor())
1254 jonathan 430 if clr is not None:
1255 jonathan 460 self.prop.SetLineColor(clr)
1256 jonathan 549 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1257 jonathan 430
1258 jonathan 485 def _OnChangeLineColorTrans(self, event):
1259 jonathan 1342 self.prop.SetLineColor(Transparent)
1260 jonathan 549 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1261 jonathan 485
1262 jonathan 460 def _OnChangeFillColor(self, event):
1263 jonathan 430 clr = self.__GetColor(self.prop.GetFill())
1264     if clr is not None:
1265     self.prop.SetFill(clr)
1266 jonathan 549 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1267 jonathan 430
1268 jonathan 485 def _OnChangeFillColorTrans(self, event):
1269 jonathan 1342 self.prop.SetFill(Transparent)
1270 jonathan 549 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1271 jonathan 485
1272 jonathan 441 def GetClassGroupProperties(self):
1273 jonathan 415 return self.prop
1274 jonathan 392
1275    
1276 jonathan 549 class ClassDataPreviewWindow(wxWindow):
1277 jonathan 415
1278 jonathan 441 def __init__(self, rect, prop, shapeType,
1279 jonathan 430 parent = None, id = -1, size = wxDefaultSize):
1280     if parent is not None:
1281 jonathan 549 wxWindow.__init__(self, parent, id, (0, 0), size)
1282 jonathan 460 EVT_PAINT(self, self._OnPaint)
1283 jonathan 415
1284 jonathan 430 self.rect = rect
1285 jonathan 549
1286 jonathan 441 self.prop = prop
1287 jonathan 430 self.shapeType = shapeType
1288 jonathan 549 self.previewer = ClassDataPreviewer()
1289 jonathan 430
1290 jonathan 630 def GetProperties():
1291     return self.prop
1292    
1293 jonathan 460 def _OnPaint(self, event):
1294 jonathan 430 dc = wxPaintDC(self)
1295    
1296     # XXX: this doesn't seem to be having an effect:
1297     dc.DestroyClippingRegion()
1298    
1299 jonathan 549 if self.rect is None:
1300     w, h = self.GetSize()
1301     rect = wxRect(0, 0, w, h)
1302     else:
1303     rect = self.rect
1304 jonathan 430
1305 jonathan 549 self.previewer.Draw(dc, rect, self.prop, self.shapeType)
1306 jonathan 430
1307 jonathan 549 class ClassDataPreviewer:
1308 jonathan 430
1309 jonathan 549 def Draw(self, dc, rect, prop, shapeType):
1310    
1311 jonathan 606 assert dc is not None
1312     assert isinstance(prop, ClassGroupProperties)
1313 jonathan 549
1314 jonathan 430 if rect is None:
1315 jonathan 549 x = 0
1316     y = 0
1317     w, h = dc.GetSize()
1318 jonathan 430 else:
1319     x = rect.GetX()
1320     y = rect.GetY()
1321     w = rect.GetWidth()
1322     h = rect.GetHeight()
1323    
1324 jonathan 460 stroke = prop.GetLineColor()
1325 jonathan 1342 if stroke is Transparent:
1326 jonathan 392 pen = wxTRANSPARENT_PEN
1327     else:
1328 jonathan 430 pen = wxPen(Color2wxColour(stroke),
1329 jonathan 460 prop.GetLineWidth(),
1330 jonathan 392 wxSOLID)
1331    
1332 jonathan 441 stroke = prop.GetFill()
1333 jonathan 1342 if stroke is Transparent:
1334 jonathan 392 brush = wxTRANSPARENT_BRUSH
1335     else:
1336 jonathan 430 brush = wxBrush(Color2wxColour(stroke), wxSOLID)
1337 jonathan 392
1338     dc.SetPen(pen)
1339     dc.SetBrush(brush)
1340    
1341 jonathan 415 if shapeType == SHAPETYPE_ARC:
1342 jonathan 430 dc.DrawSpline([wxPoint(x, y + h),
1343     wxPoint(x + w/2, y + h/4),
1344     wxPoint(x + w/2, y + h/4*3),
1345     wxPoint(x + w, y)])
1346 jonathan 392
1347 jonathan 576 elif shapeType == SHAPETYPE_POINT:
1348 jonathan 415
1349 jonathan 430 dc.DrawCircle(x + w/2, y + h/2,
1350 jonathan 460 (min(w, h) - prop.GetLineWidth())/2)
1351 jonathan 392
1352 jonathan 576 elif shapeType == SHAPETYPE_POLYGON:
1353     dc.DrawRectangle(x, y, w, h)
1354    
1355 jonathan 415 class ClassRenderer(wxPyGridCellRenderer):
1356    
1357     def __init__(self, shapeType):
1358     wxPyGridCellRenderer.__init__(self)
1359 jonathan 549 self.shapeType = shapeType
1360     self.previewer = ClassDataPreviewer()
1361 jonathan 415
1362     def Draw(self, grid, attr, dc, rect, row, col, isSelected):
1363 jonathan 485 data = grid.GetTable().GetClassGroup(row)
1364 jonathan 415
1365     dc.SetClippingRegion(rect.GetX(), rect.GetY(),
1366     rect.GetWidth(), rect.GetHeight())
1367     dc.SetPen(wxPen(wxLIGHT_GREY))
1368     dc.SetBrush(wxBrush(wxLIGHT_GREY, wxSOLID))
1369     dc.DrawRectangle(rect.GetX(), rect.GetY(),
1370     rect.GetWidth(), rect.GetHeight())
1371    
1372 jonathan 441 if not isinstance(data, ClassGroupMap):
1373 jonathan 549 self.previewer.Draw(dc, rect, data.GetProperties(), self.shapeType)
1374 jonathan 415
1375     if isSelected:
1376 jonathan 615 dc.SetPen(wxPen(wxBLACK, 1, wxSOLID))
1377 jonathan 415 dc.SetBrush(wxTRANSPARENT_BRUSH)
1378 jonathan 615
1379 jonathan 415 dc.DrawRectangle(rect.GetX(), rect.GetY(),
1380     rect.GetWidth(), rect.GetHeight())
1381    
1382 jonathan 392 dc.DestroyClippingRegion()
1383    
1384 jonathan 630
1385     class ClassGroupPropertiesCtrl(wxWindow, wxControl):
1386    
1387     def __init__(self, parent, id, props, shapeType,
1388     size = wxDefaultSize, style = 0):
1389    
1390     wxWindow.__init__(self, parent, id, size = size, style = style)
1391    
1392 jonathan 1302 self.parent = parent
1393    
1394 jonathan 630 self.SetProperties(props)
1395     self.SetShapeType(shapeType)
1396     self.AllowEdit(True)
1397    
1398     EVT_PAINT(self, self._OnPaint)
1399     EVT_LEFT_DCLICK(self, self._OnLeftDClick)
1400    
1401     self.previewer = ClassDataPreviewer()
1402    
1403     def _OnPaint(self, event):
1404     dc = wxPaintDC(self)
1405    
1406     # XXX: this doesn't seem to be having an effect:
1407     dc.DestroyClippingRegion()
1408    
1409     w, h = self.GetClientSize()
1410    
1411     self.previewer.Draw(dc,
1412     wxRect(0, 0, w, h),
1413     self.GetProperties(),
1414     self.GetShapeType())
1415    
1416    
1417     def GetProperties(self):
1418     return self.props
1419    
1420     def SetProperties(self, props):
1421     self.props = props
1422     self.Refresh()
1423    
1424     def GetShapeType(self):
1425     return self.shapeType
1426    
1427     def SetShapeType(self, shapeType):
1428     self.shapeType = shapeType
1429     self.Refresh()
1430    
1431     def AllowEdit(self, allow):
1432     self.allowEdit = allow
1433    
1434     def DoEdit(self):
1435     if not self.allowEdit: return
1436    
1437 jonathan 1302 propDlg = SelectPropertiesDialog(self.parent,
1438 jonathan 630 self.GetProperties(),
1439     self.GetShapeType())
1440    
1441     if propDlg.ShowModal() == wxID_OK:
1442     new_prop = propDlg.GetClassGroupProperties()
1443     self.SetProperties(new_prop)
1444     self.Refresh()
1445    
1446     propDlg.Destroy()
1447    
1448     def _OnLeftDClick(self, event):
1449     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