/[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 400 by jonathan, Tue Feb 11 14:29:09 2003 UTC revision 2386 by jan, Thu Oct 7 14:43:45 2004 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, RasterLayer
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 dialogs import NonModalNonParentDialog
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)
277    
278          self.tdata = []          assert len(ClassTable.__col_labels) == NUM_COLS
279    
280          self.tdata.append([clinfo.DefaultData, 'DEFAULT'])          self.clazz = None
281            self.__colAttr = {}
282    
283          for value, data in clinfo.points.items():          self.SetView(view)
             self.tdata.append([data, value])  
284    
285          for range in 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  class Classifier(wxDialog):          if row == 0:
457                    group = self.clazz.GetDefaultGroup()
458      def __init__(self, parent, layer):          else:
459          wxDialog.__init__(self, parent, -1, _("Classify"),              group = self.clazz.GetGroup(row - 1)
                           style = wxRESIZE_BORDER)  
460    
         topBox = wxBoxSizer(wxVERTICAL)  
461    
462          propertyBox = wxBoxSizer(wxHORIZONTAL)          if col == COL_VISIBLE:
463          propertyBox.Add(wxStaticText(self, -1, _("Property")),              return group.IsVisible()
464              0, wxALIGN_CENTER | wxALL, 4)  
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          self.properties = wxComboBox(self, ID_PROPERTY_SELECT, "",          Returns a tuple (type, data) where type is 0 if data is
489                                       style = wxCB_READONLY)          a singleton value, or 1 if is a range
490            """
491    
492            type = self.fieldType
493    
494            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          self.num_cols = layer.table.field_count()          if mod:
591          cur_hilight = 0              self.__Modified()
592          for i in range(self.num_cols):              self.GetView().Refresh()
             type, name, len, decc = layer.table.field_info(i)  
             if name == layer.classification.field:  
                 cur_hilight = i  
             self.properties.Append(name)  
593    
594          self.properties.SetSelection(cur_hilight)      def GetAttr(self, row, col, someExtraParameter):
595          propertyBox.Add(self.properties, 1, wxGROW, 4)          """Returns the cell attributes"""
         EVT_COMBOBOX(self, ID_PROPERTY_SELECT, self.OnPropertySelect)  
596    
597          topBox.Add(propertyBox, 0, wxGROW, 4)          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            if self.IsModified():
647                self.__NotifyRowChanges(old_len, self.GetNumberRows())
648    
649        def AppendRows(self, numRows = 1):
650            """Append 'numRows' empty rows to the end of the table.
651    
652            The table is considered modified if any rows are appended.
653            """
654    
655            old_len = self.GetNumberRows()
656            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(NonModalNonParentDialog):
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            NonModalNonParentDialog.__init__(self, parent, name, "")
701    
702            self.__SetTitle(layer.Title())
703    
704            self.parent.Subscribe(MAP_REPLACED, self.map_replaced)
705            self.layer = layer
706            self.map = parent.Map()
707    
708            self.map.Subscribe(MAP_LAYERS_REMOVED, self.map_layers_removed)
709            self.layer.Subscribe(LAYER_SHAPESTORE_REPLACED,
710                                 self.layer_shapestore_replaced)
711    
712            self.genDlg = None
713    
714            ############################
715            # Create the controls
716          #          #
717          # Classification data table  
718            panel = wxPanel(self, -1)
719    
720            text_title = wxTextCtrl(panel, ID_PROPERTY_TITLE, layer.Title())
721            self.fieldTypeText = wxStaticText(panel, -1, "")
722    
723            if layer.HasClassification():
724                self.originalClass = self.layer.GetClassification()
725                self.originalClassField = self.layer.GetClassificationColumn()
726                field = self.originalClassField
727                fieldType = self.layer.GetFieldType(field)
728    
729                table = layer.ShapeStore().Table()
730                #
731                # make field choice box
732                #
733                self.fields = wxChoice(panel, ID_PROPERTY_SELECT,)
734    
735                self.num_cols = table.NumColumns()
736                # just assume the first field in case one hasn't been
737                # specified in the file.
738                self.__cur_field = 0
739    
740                self.fields.Append("<None>")
741    
742                if fieldType is None:
743                    self.fields.SetClientData(0, copy.deepcopy(self.originalClass))
744                else:
745                    self.fields.SetClientData(0, None)
746    
747                for i in range(self.num_cols):
748                    name = table.Column(i).name
749                    self.fields.Append(name)
750    
751                    if name == field:
752                        self.__cur_field = i + 1
753                        self.fields.SetClientData(i + 1,
754                                                  copy.deepcopy(self.originalClass))
755                    else:
756                        self.fields.SetClientData(i + 1, None)
757    
758                button_gen = wxButton(panel, ID_PROPERTY_GENCLASS,
759                    _("Generate Class"))
760                button_add = wxButton(panel, ID_PROPERTY_ADD,
761                    _("Add"))
762                button_moveup = wxButton(panel, ID_PROPERTY_MOVEUP,
763                    _("Move Up"))
764                button_movedown = wxButton(panel, ID_PROPERTY_MOVEDOWN,
765                    _("Move Down"))
766                button_edit = wxButton(panel, ID_PROPERTY_EDITSYM,
767                    _("Edit Symbol"))
768                button_remove = wxButton(panel, ID_PROPERTY_REMOVE,
769                    _("Remove"))
770    
771                self.classGrid = ClassGrid(panel, self)
772    
773                # calling __SelectField after creating the classGrid fills in the
774                # grid with the correct information
775                self.fields.SetSelection(self.__cur_field)
776                self.__SelectField(self.__cur_field, group = group)
777    
778            button_try = wxButton(self, ID_PROPERTY_TRY, _("Try"))
779            button_revert = wxButton(self, ID_PROPERTY_REVERT, _("Revert"))
780            button_ok = wxButton(self, wxID_OK, _("OK"))
781            button_close = wxButton(self, wxID_CANCEL, _("Close"))
782            button_ok.SetDefault()
783    
784            ############################
785            # Layout the controls
786          #          #
787    
788          self.classTable = wxGrid(self, ID_CLASS_TABLE, size=(300, 150))          topBox = wxBoxSizer(wxVERTICAL)
789            panelBox = wxBoxSizer(wxVERTICAL)
790    
791            sizer = wxBoxSizer(wxHORIZONTAL)
792            sizer.Add(wxStaticText(panel, -1, _("Title: ")),
793                0, wxALIGN_LEFT | wxALL | wxALIGN_CENTER_VERTICAL, 4)
794            sizer.Add(text_title, 1, wxGROW, 0)
795    
796            panelBox.Add(sizer, 0, wxGROW, 4)
797    
798            if isinstance(layer, RasterLayer):
799                type = "Image"
800            else:
801                type = layer.ShapeType()
802    
803            panelBox.Add(wxStaticText(panel, -1, _("Type: %s") % type),
804                0, wxALIGN_LEFT | wxALL, 4)
805    
806            if layer.HasClassification():
807    
808                classBox = wxStaticBoxSizer(
809                            wxStaticBox(panel, -1, _("Classification")), wxVERTICAL)
810    
811    
812                sizer = wxBoxSizer(wxHORIZONTAL)
813                sizer.Add(wxStaticText(panel, ID_PROPERTY_FIELDTEXT, _("Field: ")),
814                    0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
815                sizer.Add(self.fields, 1, wxGROW | wxALL, 4)
816    
817                classBox.Add(sizer, 0, wxGROW, 4)
818    
819                classBox.Add(self.fieldTypeText, 0,
820                            wxGROW | wxALIGN_LEFT | wxALL | wxADJUST_MINSIZE, 4)
821    
822          table = ClassTable(layer.classification)              controlBox = wxBoxSizer(wxHORIZONTAL)
823          self.classTable.SetTable(table, true)              controlButtonBox = wxBoxSizer(wxVERTICAL)
         self.classTable.EnableEditing(false)  
         cr = ClassRenderer(layer.ShapeType())  
         for i in range(self.classTable.GetNumberRows()):  
             self.classTable.SetCellRenderer(i, 0, cr)  
824    
825          topBox.Add(self.classTable, 1, wxGROW, 0)              controlButtonBox.Add(button_gen, 0, wxGROW|wxALL, 4)
826                controlButtonBox.Add(button_add, 0, wxGROW|wxALL, 4)
827                controlButtonBox.Add(button_moveup, 0, wxGROW|wxALL, 4)
828                controlButtonBox.Add(button_movedown, 0, wxGROW|wxALL, 4)
829                controlButtonBox.Add(button_edit, 0, wxGROW|wxALL, 4)
830                controlButtonBox.Add(60, 20, 0, wxGROW|wxALL|wxALIGN_BOTTOM, 4)
831                controlButtonBox.Add(button_remove, 0,
832                                     wxGROW|wxALL|wxALIGN_BOTTOM, 4)
833    
834                controlBox.Add(self.classGrid, 1, wxGROW, 0)
835                controlBox.Add(controlButtonBox, 0, wxGROW, 10)
836    
837                classBox.Add(controlBox, 1, wxGROW, 10)
838                panelBox.Add(classBox, 1, wxGROW, 0)
839    
840    
841            buttonBox = wxBoxSizer(wxHORIZONTAL)
842            buttonBox.Add(button_try, 0, wxRIGHT|wxEXPAND, 10)
843            buttonBox.Add(button_revert, 0, wxRIGHT|wxEXPAND, 10)
844            buttonBox.Add(button_ok, 0, wxRIGHT|wxEXPAND, 10)
845            buttonBox.Add(button_close, 0, wxRIGHT|wxEXPAND, 10)
846    
847            panel.SetAutoLayout(True)
848            panel.SetSizer(panelBox)
849            panelBox.Fit(panel)
850            panelBox.SetSizeHints(panel)
851    
852            topBox.Add(panel, 1, wxGROW | wxALL, 4)
853            topBox.Add(buttonBox, 0, wxALIGN_RIGHT|wxBOTTOM|wxTOP, 10)
854    
855            self.SetAutoLayout(True)
856            self.SetSizer(topBox)
857            topBox.Fit(self)
858            topBox.SetSizeHints(self)
859            self.Layout()
860    
861            ###########
862    
863            EVT_CHOICE(self, ID_PROPERTY_SELECT, self._OnFieldSelect)
864            EVT_TEXT(self, ID_PROPERTY_TITLE, self._OnTitleChanged)
865            EVT_BUTTON(self, wxID_OK, self._OnOK)
866            EVT_BUTTON(self, ID_PROPERTY_TRY, self._OnTry)
867            EVT_BUTTON(self, wxID_CANCEL, self._OnCloseBtn)
868            EVT_BUTTON(self, ID_PROPERTY_REVERT, self._OnRevert)
869    
870            EVT_BUTTON(self, ID_PROPERTY_ADD, self._OnAdd)
871            EVT_BUTTON(self, ID_PROPERTY_EDITSYM, self._OnEditSymbol)
872            EVT_BUTTON(self, ID_PROPERTY_REMOVE, self._OnRemove)
873            EVT_BUTTON(self, ID_PROPERTY_GENCLASS, self._OnGenClass)
874            EVT_BUTTON(self, ID_PROPERTY_MOVEUP, self._OnMoveUp)
875            EVT_BUTTON(self, ID_PROPERTY_MOVEDOWN, self._OnMoveDown)
876    
877            ######################
878    
879            text_title.SetFocus()
880            self.haveApplied = False
881    
882        def unsubscribe_messages(self):
883            """Unsubscribe from all messages."""
884            self.parent.Unsubscribe(MAP_REPLACED, self.map_replaced)
885            self.map.Unsubscribe(MAP_LAYERS_REMOVED, self.map_layers_removed)
886            self.layer.Unsubscribe(LAYER_SHAPESTORE_REPLACED,
887                                   self.layer_shapestore_replaced)
888    
889        def map_layers_removed(self, map):
890            """Subscribed to MAP_LAYERS_REMOVED. If this layer was removed,
891            Close self.
892            """
893            if self.layer not in self.map.Layers():
894                self.Close()
895    
896        def layer_shapestore_replaced(self, *args):
897            """Subscribed to the map's LAYER_SHAPESTORE_REPLACED message.
898    
899            Close self.
900            """
901            self.Close()
902    
903        def map_replaced(self, *args):
904            """Subscribed to the mainwindow's MAP_REPLACED message. Close self."""
905            self.Close()
906    
907        def EditSymbol(self, row):
908            """Open up a dialog where the user can select the properties
909            for a group.
910            """
911            table = self.classGrid.GetTable()
912            prop = table.GetValueAsCustom(row, COL_SYMBOL, None)
913    
914            # get a new ClassGroupProperties object and copy the
915            # values over to our current object
916            propDlg = SelectPropertiesDialog(self, prop, self.layer.ShapeType())
917    
918            self.Enable(False)
919            if propDlg.ShowModal() == wxID_OK:
920                new_prop = propDlg.GetClassGroupProperties()
921                table.SetValueAsCustom(row, COL_SYMBOL, None, new_prop)
922            self.Enable(True)
923            propDlg.Destroy()
924    
925        def _SetClassification(self, clazz):
926            """Called from the ClassGen dialog when a new classification has
927            been created and should be set in the table.
928            """
929            # FIXME: This could be implemented using a message
930    
931            self.fields.SetClientData(self.__cur_field, clazz)
932            self.classGrid.GetTable().SetClassification(clazz)
933    
934        def __BuildClassification(self, fieldIndex, copyClass=False, force=False):
935            """Pack the classification setting into a Classification object.
936            Returns (Classification, fieldName) where fieldName is the selected
937            field in the table that the classification should be used with.
938            """
939    
940    #       numRows = self.classGrid.GetNumberRows()
941    #       assert numRows > 0  # there should always be a default row
942    
943            if fieldIndex == 0:
944                fieldName = None
945                fieldType = None
946            else:
947                fieldName = self.fields.GetString(fieldIndex)
948                fieldType = self.layer.GetFieldType(fieldName)
949    
950            clazz = self.fields.GetClientData(fieldIndex)
951            if clazz is None or self.classGrid.GetTable().IsModified() or force:
952                clazz = self.classGrid.GetTable().GetClassification()
953                if copyClass:
954                    clazz = copy.deepcopy(clazz)
955    
956            return clazz, fieldName
957    
958        def __SetGridTable(self, fieldIndex, group = None):
959            """Set the table with the classification associated with the
960            selected field at fieldIndex. Select the specified group
961            if group is not None.
962            """
963    
964            clazz = self.fields.GetClientData(fieldIndex)
965    
966            if clazz is None:
967                clazz = Classification()
968                clazz.SetDefaultGroup(
969                    ClassGroupDefault(
970                        self.layer.GetClassification().
971                                   GetDefaultGroup().GetProperties()))
972    
973            fieldName = self.fields.GetString(fieldIndex)
974            fieldType = self.layer.GetFieldType(fieldName)
975                    
976            self.classGrid.CreateTable(clazz, fieldType,
977                                       self.layer.ShapeType(), group)
978    
979        def __SetFieldTypeText(self, fieldIndex):
980            """Set the field type string using the data type of the field
981            at fieldIndex.
982            """
983            fieldName = self.fields.GetString(fieldIndex)
984            fieldType = self.layer.GetFieldType(fieldName)
985    
986            assert Classifier.type2string.has_key(fieldType)
987    
988            text = Classifier.type2string[fieldType]
989    
990            self.fieldTypeText.SetLabel(_("Data Type: %s") % text)
991    
992        def __SelectField(self, newIndex, oldIndex = -1, group = None):
993            """This method assumes that the current selection for the
994            combo has already been set by a call to SetSelection().
995            """
996    
997            assert oldIndex >= -1
998    
999            if oldIndex != -1:
1000                clazz, name = self.__BuildClassification(oldIndex, force = True)
1001                self.fields.SetClientData(oldIndex, clazz)
1002    
1003            self.__SetGridTable(newIndex, group)
1004    
1005            self.__EnableButtons(EB_SELECT_FIELD)
1006    
1007            self.__SetFieldTypeText(newIndex)
1008    
1009        def __SetTitle(self, title):
1010            """Set the title of the dialog."""
1011            if title != "":
1012                title = ": " + title
1013    
1014            self.SetTitle(_("Layer Properties") + title)
1015    
1016        def _OnEditSymbol(self, event):
1017            """Open up a dialog for the user to select group properties."""
1018            sel = self.classGrid.GetCurrentSelection()
1019    
1020            if len(sel) == 1:
1021                self.EditSymbol(sel[0])
1022    
1023        def _OnFieldSelect(self, event):
1024            index = self.fields.GetSelection()
1025            self.__SelectField(index, self.__cur_field)
1026            self.__cur_field = index
1027    
1028        def _OnTry(self, event):
1029            """Put the data from the table into a new Classification and hand
1030               it to the layer.
1031            """
1032    
1033            if self.layer.HasClassification():
1034                clazz = self.fields.GetClientData(self.__cur_field)
1035    
1036                #
1037                # only build the classification if there wasn't one to
1038                # to begin with or it has been modified
1039                #
1040                self.classGrid.SaveEditControlValue()
1041                clazz, name = self.__BuildClassification(self.__cur_field, True)
1042    
1043                self.layer.SetClassificationColumn(name)
1044                self.layer.SetClassification(clazz)
1045    
1046            self.haveApplied = True
1047    
1048        def _OnOK(self, event):
1049            self._OnTry(event)
1050            self.Close()
1051    
1052        def OnClose(self, event):
1053            self.unsubscribe_messages()
1054            NonModalNonParentDialog.OnClose(self, event)
1055    
1056        def _OnCloseBtn(self, event):
1057            """Close is similar to Cancel except that any changes that were
1058            made and applied remain applied, but the currently displayed
1059            classification is discarded.
1060            """
1061    
1062            self.Close()
1063    
1064        def _OnRevert(self, event):
1065            """The layer's current classification stays the same."""
1066            if self.haveApplied:
1067                self.layer.SetClassificationColumn(self.originalClassField)
1068                self.layer.SetClassification(self.originalClass)
1069    
1070            #self.Close()
1071    
1072        def _OnAdd(self, event):
1073            self.classGrid.AppendRows()
1074    
1075        def _OnRemove(self, event):
1076            self.classGrid.DeleteSelectedRows()
1077    
1078        def _OnGenClass(self, event):
1079            """Open up a dialog for the user to generate classifications."""
1080    
1081            self.genDlg = ClassGenDialog(self, self.layer,
1082                              self.fields.GetString(self.__cur_field))
1083    
1084            EVT_CLOSE(self.genDlg, self._OnGenDialogClose)
1085    
1086            self.__EnableButtons(EB_GEN_CLASS)
1087    
1088            self.genDlg.Show()
1089    
1090        def _OnGenDialogClose(self, event):
1091            """Reenable buttons after the generate classification
1092            dialog is closed.
1093            """
1094            self.genDlg.Destroy()
1095            self.genDlg = None
1096            self.__EnableButtons(EB_GEN_CLASS)
1097    
1098        def _OnMoveUp(self, event):
1099            """When the user clicks MoveUp, try to move a group up one row."""
1100            sel = self.classGrid.GetCurrentSelection()
1101    
1102            if len(sel) == 1:
1103                i = sel[0]
1104                if i > 1:
1105                    table = self.classGrid.GetTable()
1106                    x = table.GetClassGroup(i - 1)
1107                    y = table.GetClassGroup(i)
1108                    table.SetClassGroup(i - 1, y)
1109                    table.SetClassGroup(i, x)
1110                    self.classGrid.ClearSelection()
1111                    self.classGrid.SelectRow(i - 1)
1112                    self.classGrid.MakeCellVisible(i - 1, 0)
1113    
1114        def _OnMoveDown(self, event):
1115            """When the user clicks MoveDown, try to move a group down one row."""
1116            sel = self.classGrid.GetCurrentSelection()
1117    
1118            if len(sel) == 1:
1119                i = sel[0]
1120                table = self.classGrid.GetTable()
1121                if 0 < i < table.GetNumberRows() - 1:
1122                    x = table.GetClassGroup(i)
1123                    y = table.GetClassGroup(i + 1)
1124                    table.SetClassGroup(i, y)
1125                    table.SetClassGroup(i + 1, x)
1126                    self.classGrid.ClearSelection()
1127                    self.classGrid.SelectRow(i + 1)
1128                    self.classGrid.MakeCellVisible(i + 1, 0)
1129    
1130        def _OnTitleChanged(self, event):
1131            """Update the dialog title when the user changed the layer name."""
1132            obj = event.GetEventObject()
1133    
1134            self.layer.SetTitle(obj.GetValue())
1135            self.__SetTitle(self.layer.Title())
1136    
1137            self.__EnableButtons(EB_LAYER_TITLE)
1138    
1139        def __EnableButtons(self, case):
1140            """Helper method that enables/disables the appropriate buttons
1141            based on the case provided. Cases are constants beginning with EB_.
1142            """
1143    
1144            list = {wxID_OK                 : True,
1145                    wxID_CANCEL             : True,
1146                    ID_PROPERTY_ADD         : True,
1147                    ID_PROPERTY_MOVEUP      : True,
1148                    ID_PROPERTY_MOVEDOWN    : True,
1149                    ID_PROPERTY_REMOVE      : True,
1150                    ID_PROPERTY_SELECT      : True,
1151                    ID_PROPERTY_FIELDTEXT   : True,
1152                    ID_PROPERTY_GENCLASS    : True,
1153                    ID_PROPERTY_EDITSYM     : True}
1154    
1155            if case == EB_LAYER_TITLE:  
1156                if self.layer.Title() == "":
1157                    list[wxID_OK] = False
1158                    list[wxID_CANCEL] = False
1159    
1160            elif case == EB_SELECT_FIELD:
1161                if self.fields.GetSelection() == 0:
1162                    list[ID_PROPERTY_GENCLASS] = False
1163                    list[ID_PROPERTY_ADD] = False
1164                    list[ID_PROPERTY_MOVEUP] = False
1165                    list[ID_PROPERTY_MOVEDOWN] = False
1166                    list[ID_PROPERTY_REMOVE] = False
1167    
1168            elif case == EB_GEN_CLASS:
1169                if self.genDlg is not None:
1170                    list[ID_PROPERTY_SELECT] = False
1171                    list[ID_PROPERTY_FIELDTEXT] = False
1172                    list[ID_PROPERTY_GENCLASS] = False
1173    
1174            for id, enable in list.items():
1175                win = self.FindWindowById(id)
1176                if win:
1177                    win.Enable(enable)
1178    
1179    ID_SELPROP_SPINCTRL_LINEWIDTH = 4002
1180    ID_SELPROP_PREVIEW = 4003
1181    ID_SELPROP_STROKECLR = 4004
1182    ID_SELPROP_FILLCLR = 4005
1183    ID_SELPROP_STROKECLRTRANS = 4006
1184    ID_SELPROP_FILLCLRTRANS = 4007
1185    ID_SELPROP_SPINCTRL_SIZE = 4008
1186    
1187    class SelectPropertiesDialog(wxDialog):
1188        """Dialog that allows the user to select group properties."""
1189    
1190        def __init__(self, parent, prop, shapeType):
1191            """Open the dialog with the initial prop properties and shapeType."""
1192    
1193            wxDialog.__init__(self, parent, -1, _("Select Properties"),
1194                              style = wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
1195    
1196            self.prop = ClassGroupProperties(prop)
1197    
1198            topBox = wxBoxSizer(wxVERTICAL)
1199    
1200            itemBox = wxBoxSizer(wxHORIZONTAL)
1201    
1202            # preview box
1203            previewBox = wxBoxSizer(wxVERTICAL)
1204            previewBox.Add(wxStaticText(self, -1, _("Preview:")),
1205                0, wxALIGN_LEFT | wxALL, 4)
1206    
1207            self.previewWin = ClassGroupPropertiesCtrl(
1208                self, ID_SELPROP_PREVIEW, self.prop, shapeType,
1209                (40, 40), wxSIMPLE_BORDER)
1210    
1211            self.previewWin.AllowEdit(False)
1212    
1213            previewBox.Add(self.previewWin, 1, wxGROW | wxALL, 4)
1214    
1215            itemBox.Add(previewBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
1216    
1217            # control box
1218            ctrlBox = wxBoxSizer(wxVERTICAL)
1219    
1220            lineColorBox = wxBoxSizer(wxHORIZONTAL)
1221            button = wxButton(self, ID_SELPROP_STROKECLR, _("Change Line Color"))
1222            button.SetFocus()
1223            lineColorBox.Add(button, 1, wxALL | wxGROW, 4)
1224            EVT_BUTTON(self, ID_SELPROP_STROKECLR, self._OnChangeLineColor)
1225    
1226            lineColorBox.Add(
1227                wxButton(self, ID_SELPROP_STROKECLRTRANS, _("Transparent")),
1228                1, wxALL | wxGROW, 4)
1229            EVT_BUTTON(self, ID_SELPROP_STROKECLRTRANS,
1230                       self._OnChangeLineColorTrans)
1231    
1232            ctrlBox.Add(lineColorBox, 0,
1233                        wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
1234    
1235            if shapeType != SHAPETYPE_ARC:
1236                fillColorBox = wxBoxSizer(wxHORIZONTAL)
1237                fillColorBox.Add(
1238                    wxButton(self, ID_SELPROP_FILLCLR, _("Change Fill Color")),
1239                    1, wxALL | wxGROW, 4)
1240                EVT_BUTTON(self, ID_SELPROP_FILLCLR, self._OnChangeFillColor)
1241                fillColorBox.Add(
1242                    wxButton(self, ID_SELPROP_FILLCLRTRANS, _("Transparent")),
1243                    1, wxALL | wxGROW, 4)
1244                EVT_BUTTON(self, ID_SELPROP_FILLCLRTRANS,
1245                           self._OnChangeFillColorTrans)
1246                ctrlBox.Add(fillColorBox, 0,
1247                            wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
1248    
1249            # Line width selection
1250            spinBox = wxBoxSizer(wxHORIZONTAL)
1251            spinBox.Add(wxStaticText(self, -1, _("Line Width: ")),
1252                    0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
1253            self.spinCtrl_linewidth = wxSpinCtrl(self,
1254                                                 ID_SELPROP_SPINCTRL_LINEWIDTH,
1255                                                 min=1, max=10,
1256                                                 value=str(prop.GetLineWidth()),
1257                                                 initial=prop.GetLineWidth())
1258    
1259            EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL_LINEWIDTH,
1260                         self._OnSpinLineWidth)
1261    
1262            spinBox.Add(self.spinCtrl_linewidth, 0, wxALIGN_LEFT | wxALL, 4)
1263            ctrlBox.Add(spinBox, 0, wxALIGN_RIGHT | wxALL, 0)
1264    
1265            # Size selection
1266            if shapeType == SHAPETYPE_POINT:
1267                spinBox = wxBoxSizer(wxHORIZONTAL)
1268                spinBox.Add(wxStaticText(self, -1, _("Size: ")),
1269                            0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
1270                self.spinCtrl_size = wxSpinCtrl(self, ID_SELPROP_SPINCTRL_SIZE,
1271                                                min=1, max=100,
1272                                                value=str(prop.GetSize()),
1273                                                initial=prop.GetSize())
1274    
1275                EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL_SIZE, self._OnSpinSize)
1276    
1277                spinBox.Add(self.spinCtrl_size, 0, wxALIGN_LEFT | wxALL, 4)
1278                ctrlBox.Add(spinBox, 0, wxALIGN_RIGHT | wxALL, 0)
1279    
1280    
1281            itemBox.Add(ctrlBox, 0, wxALIGN_RIGHT | wxALL | wxGROW, 0)
1282            topBox.Add(itemBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
1283    
1284          #          #
1285          # Control buttons:          # Control buttons:
1286          #          #
1287          buttonBox = wxBoxSizer(wxHORIZONTAL)          buttonBox = wxBoxSizer(wxHORIZONTAL)
1288          buttonBox.Add(wxButton(self, ID_CLASSIFY_OK, _("OK")),          button_ok = wxButton(self, wxID_OK, _("OK"))
1289                        0, wxALL, 4)          buttonBox.Add(button_ok, 0, wxRIGHT|wxEXPAND, 10)
1290          buttonBox.Add(wxButton(self, ID_CLASSIFY_CANCEL, _("Cancel")),          buttonBox.Add(wxButton(self, wxID_CANCEL, _("Cancel")),
1291                        0, wxALL, 4)                        0, wxRIGHT|wxEXPAND, 10)
1292          topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)          topBox.Add(buttonBox, 0, wxALIGN_RIGHT|wxBOTTOM|wxTOP, 10)
1293    
1294            button_ok.SetDefault()
1295    
1296          EVT_BUTTON(self, ID_CLASSIFY_OK, self.OnOK)          #EVT_BUTTON(self, wxID_OK, self._OnOK)
1297          EVT_BUTTON(self, ID_CLASSIFY_CANCEL, self.OnCancel)          #EVT_BUTTON(self, ID_SELPROP_CANCEL, self._OnCancel)
1298    
1299          self.SetAutoLayout(true)          self.SetAutoLayout(True)
1300          self.SetSizer(topBox)          self.SetSizer(topBox)
1301          topBox.Fit(self)          topBox.Fit(self)
1302          topBox.SetSizeHints(self)          topBox.SetSizeHints(self)
1303    
     def OnPropertySelect(self, event): pass  
   
1304      def OnOK(self, event):      def OnOK(self, event):
1305          self.EndModal(wxID_OK)          self.EndModal(wxID_OK)
1306    
1307      def OnCancel(self, event):      def OnCancel(self, event):
1308          self.EndModal(wxID_CANCEL)          self.EndModal(wxID_CANCEL)
1309    
1310        def _OnSpinLineWidth(self, event):
1311            self.prop.SetLineWidth(self.spinCtrl_linewidth.GetValue())
1312            self.previewWin.Refresh()
1313    
1314  class ClassRenderer(wxPyGridCellRenderer):      def _OnSpinSize(self, event):
1315            self.prop.SetSize(self.spinCtrl_size.GetValue())
1316            self.previewWin.Refresh()
1317    
1318      def __init__(self, shapeType):      def __GetColor(self, cur):
1319          wxPyGridCellRenderer.__init__(self)          dialog = ColorDialog(self)
1320            dialog.SetColor(cur)
1321    
1322            ret = None
1323            if dialog.ShowModal() == wxID_OK:
1324                ret = dialog.GetColor()
1325    
1326            dialog.Destroy()
1327    
1328            return ret
1329    
1330        def _OnChangeLineColor(self, event):
1331            clr = self.__GetColor(self.prop.GetLineColor())
1332            if clr is not None:
1333                self.prop.SetLineColor(clr)
1334            self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1335    
1336        def _OnChangeLineColorTrans(self, event):
1337            self.prop.SetLineColor(Transparent)
1338            self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1339    
1340        def _OnChangeFillColor(self, event):
1341            clr = self.__GetColor(self.prop.GetFill())
1342            if clr is not None:
1343                self.prop.SetFill(clr)
1344            self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1345    
1346        def _OnChangeFillColorTrans(self, event):
1347            self.prop.SetFill(Transparent)
1348            self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1349    
1350        def GetClassGroupProperties(self):
1351            return self.prop
1352    
1353    
1354    class ClassDataPreviewWindow(wxWindow):
1355        """A custom window that draws group properties using the correct shape."""
1356    
1357        def __init__(self, rect, prop, shapeType,
1358                           parent = None, id = -1, size = wxDefaultSize):
1359            """Draws the appropriate shape as specified with shapeType using
1360            prop properities.
1361            """
1362            if parent is not None:
1363                wxWindow.__init__(self, parent, id, (0, 0), size)
1364                EVT_PAINT(self, self._OnPaint)
1365    
1366            self.rect = rect
1367    
1368            self.prop = prop
1369          self.shapeType = shapeType          self.shapeType = shapeType
1370            self.previewer = ClassDataPreviewer()
1371    
1372      def Draw(self, grid, attr, dc, rect, row, col, isSelected):      def GetProperties():
1373          value = grid.GetTable().GetValueAsCustom(row, col, "")          return self.prop
1374          # XXX: check if value is a dictionary  
1375          stroke = value.GetStroke()      def _OnPaint(self, event):
1376          if stroke is None:          dc = wxPaintDC(self)
1377    
1378            # XXX: this doesn't seem to be having an effect:
1379            dc.DestroyClippingRegion()
1380    
1381            if self.rect is None:
1382                w, h = self.GetSize()
1383                rect = wxRect(0, 0, w, h)
1384            else:
1385                rect = self.rect
1386    
1387            self.previewer.Draw(dc, rect, self.prop, self.shapeType)
1388    
1389    class ClassDataPreviewer:
1390        """Class that actually draws a group property preview."""
1391    
1392        def Draw(self, dc, rect, prop, shapeType):
1393            """Draw the property.
1394    
1395            returns: (w, h) as adapted extend if the drawing size
1396            exceeded the given rect. This can only be the case
1397            for point symbols. If the symbol fits the given rect,
1398            None is returned.
1399            """
1400    
1401            assert dc is not None
1402            assert isinstance(prop, ClassGroupProperties)
1403    
1404            if rect is None:
1405                x = 0
1406                y = 0
1407                w, h = dc.GetSize()
1408            else:
1409                x = rect.GetX()
1410                y = rect.GetY()
1411                w = rect.GetWidth()
1412                h = rect.GetHeight()
1413    
1414            stroke = prop.GetLineColor()
1415            if stroke is Transparent:
1416              pen = wxTRANSPARENT_PEN              pen = wxTRANSPARENT_PEN
1417          else:          else:
1418              pen = wxPen(wxColour(stroke.red * 255,              pen = wxPen(Color2wxColour(stroke),
1419                                   stroke.green * 255,                          prop.GetLineWidth(),
                                  stroke.blue * 255),  
                         value.GetStrokeWidth(),  
1420                          wxSOLID)                          wxSOLID)
1421    
1422          stroke = value.GetFill()          stroke = prop.GetFill()
1423          if stroke is None:          if stroke is Transparent:
1424              brush = wxTRANSPARENT_BRUSH              brush = wxTRANSPARENT_BRUSH
1425          else:          else:
1426              brush = wxBrush(wxColour(stroke.red * 255,              brush = wxBrush(Color2wxColour(stroke), wxSOLID)
1427                                       stroke.green * 255,  
1428                                       stroke.blue * 255), wxSOLID)          dc.SetPen(pen)
1429            dc.SetBrush(brush)
1430    
1431            if shapeType == SHAPETYPE_ARC:
1432                dc.DrawSpline([wxPoint(x, y + h),
1433                               wxPoint(x + w/2, y + h/4),
1434                               wxPoint(x + w/2, y + h/4*3),
1435                               wxPoint(x + w, y)])
1436    
1437            elif shapeType == SHAPETYPE_POINT:
1438    
1439                dc.DrawCircle(x + w/2, y + h/2, prop.GetSize())
1440                circle_size =  prop.GetSize() * 2 + prop.GetLineWidth() * 2
1441                new_h = h
1442                new_w = w
1443                if h < circle_size: new_h = circle_size
1444                if w < circle_size: new_w = circle_size
1445                if new_h > h or new_w > w:
1446                    return (new_w, new_h)
1447    
1448            elif shapeType == SHAPETYPE_POLYGON:
1449                dc.DrawRectangle(x, y, w, h)
1450    
1451            return None
1452    
1453    class ClassRenderer(wxPyGridCellRenderer):
1454        """A wrapper class that can be used to draw group properties in a
1455        grid table.
1456        """
1457    
1458        def __init__(self, shapeType):
1459            wxPyGridCellRenderer.__init__(self)
1460            self.shapeType = shapeType
1461            self.previewer = ClassDataPreviewer()
1462    
1463        def Draw(self, grid, attr, dc, rect, row, col, isSelected):
1464            data = grid.GetTable().GetClassGroup(row)
1465    
1466          dc.SetClippingRegion(rect.GetX(), rect.GetY(),          dc.SetClippingRegion(rect.GetX(), rect.GetY(),
1467                               rect.GetWidth(), rect.GetHeight())                               rect.GetWidth(), rect.GetHeight())
# Line 167  class ClassRenderer(wxPyGridCellRenderer Line 1470  class ClassRenderer(wxPyGridCellRenderer
1470          dc.DrawRectangle(rect.GetX(), rect.GetY(),          dc.DrawRectangle(rect.GetX(), rect.GetY(),
1471                           rect.GetWidth(), rect.GetHeight())                           rect.GetWidth(), rect.GetHeight())
1472    
1473          dc.SetPen(pen)          if not isinstance(data, ClassGroupMap):
1474          dc.SetBrush(brush)              new_size = self.previewer.Draw(dc, rect, data.GetProperties(),
1475                                               self.shapeType)
1476                if new_size is not None:
1477                    (new_w, new_h) = new_size
1478                    grid.SetRowSize(row, new_h)
1479                    grid.SetColSize(col, new_h)
1480                    grid.ForceRefresh()
1481    
1482                    # now that we know the height, redraw everything
1483                    rect.SetHeight(new_h)
1484                    rect.SetWidth(new_w)
1485                    dc.DestroyClippingRegion()
1486                    dc.SetClippingRegion(rect.GetX(), rect.GetY(),
1487                                         rect.GetWidth(), rect.GetHeight())
1488                    dc.SetPen(wxPen(wxLIGHT_GREY))
1489                    dc.SetBrush(wxBrush(wxLIGHT_GREY, wxSOLID))
1490                    dc.DrawRectangle(rect.GetX(), rect.GetY(),
1491                                     rect.GetWidth(), rect.GetHeight())
1492                    self.previewer.Draw(dc, rect, data.GetProperties(),
1493                                        self.shapeType)
1494    
1495            if isSelected:
1496                dc.SetPen(wxPen(wxBLACK, 1, wxSOLID))
1497                dc.SetBrush(wxTRANSPARENT_BRUSH)
1498    
1499          if self.shapeType == SHAPETYPE_ARC:              dc.DrawRectangle(rect.GetX(), rect.GetY(),
1500              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)  
1501    
1502          dc.DestroyClippingRegion()          dc.DestroyClippingRegion()
1503    
1504    
1505    class ClassGroupPropertiesCtrl(wxWindow, wxControl):
1506        """A custom window and control that draw a preview of group properties
1507        and can open a dialog to modify the properties if the user double-clicks
1508        it.
1509        """
1510    
1511        def __init__(self, parent, id, props, shapeType,
1512                     size = wxDefaultSize, style = 0):
1513    
1514            wxWindow.__init__(self, parent, id, size = size, style = style)
1515    
1516            self.parent = parent
1517    
1518            self.SetProperties(props)
1519            self.SetShapeType(shapeType)
1520            self.AllowEdit(True)
1521    
1522            EVT_PAINT(self, self._OnPaint)
1523            EVT_LEFT_DCLICK(self, self._OnLeftDClick)
1524    
1525            self.previewer = ClassDataPreviewer()
1526    
1527        def _OnPaint(self, event):
1528            dc = wxPaintDC(self)
1529    
1530            # XXX: this doesn't seem to be having an effect:
1531            dc.DestroyClippingRegion()
1532    
1533            w, h = self.GetClientSize()
1534    
1535            self.previewer.Draw(dc,
1536                                wxRect(0, 0, w, h),
1537                                self.GetProperties(),
1538                                self.GetShapeType())
1539    
1540    
1541        def GetProperties(self):
1542            return self.props
1543    
1544        def SetProperties(self, props):
1545            self.props = props
1546            self.Refresh()
1547    
1548        def GetShapeType(self):
1549            return self.shapeType
1550    
1551        def SetShapeType(self, shapeType):
1552            self.shapeType = shapeType
1553            self.Refresh()
1554    
1555        def AllowEdit(self, allow):
1556            """Allow/Disallow double-clicking on the control."""
1557            self.allowEdit = allow
1558    
1559        def DoEdit(self):
1560            """Open the properties selector dialog."""
1561    
1562            if not self.allowEdit: return
1563    
1564            propDlg = SelectPropertiesDialog(self.parent,
1565                                             self.GetProperties(),
1566                                             self.GetShapeType())
1567    
1568            if propDlg.ShowModal() == wxID_OK:
1569                new_prop = propDlg.GetClassGroupProperties()
1570                self.SetProperties(new_prop)
1571                self.Refresh()
1572    
1573            propDlg.Destroy()
1574    
1575        def _OnLeftDClick(self, event):
1576            self.DoEdit()
1577    
1578    from Thuban.UI.mainwindow import layer_properties_dialogs
1579    layer_properties_dialogs.add(Layer, Classifier)
1580    layer_properties_dialogs.add(RasterLayer, Classifier)

Legend:
Removed from v.400  
changed lines
  Added in v.2386

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26