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

Legend:
Removed from v.507  
changed lines
  Added in v.2817

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26