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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26