/[thuban]/trunk/thuban/Thuban/UI/classifier.py
ViewVC logotype

Diff of /trunk/thuban/Thuban/UI/classifier.py

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

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

Legend:
Removed from v.513  
changed lines
  Added in v.2766

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26