/[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 2817 - (hide annotations)
Sun Jan 27 00:01:32 2008 UTC (17 years, 1 month ago) by bernhard
File MIME type: text/x-python
File size: 52767 byte(s)
Changed a few places in Thuban and Extensions to convert wx
strings to internal string encoding. 

* Thuban/UI/classgen.py: Checked for return strings from wx problems.
Added one clarifing comment, removed two unused variables.

* Thuban/UI/mainwindow.py(OpenSession, SaveSessionAs, TableOpen): Using 
internal_from_wxstring for the pathname.

* Thuban/UI/tableview.py(doExport):  Using internal_from_wxstring
for the pathname.

* Thuban/UI/view.py(MapCanvas.Export):  Using internal_from_wxstring
for the pathname.

* Extensions/bboxdump/bboxdump.py(OnSelectFilename),
Extensions/export_shapefile/export_shapefile.py,
Extensions/gns2shp/gns2shp.py, Extensions/importAPR/importAPR.py,
Extensions/svgexport/maplegend.py, Extensions/svgexport/svgsaver.py,
Extensions/umn_mapserver/mf_import.py: Using
internal_from_wxstring for the pathname.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26