/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/UI/classifier.py
ViewVC logotype

Diff of /branches/WIP-pyshapelib-bramz/Thuban/UI/classifier.py

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

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

Legend:
Removed from v.392  
changed lines
  Added in v.2560

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26