/[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 374 by jan, Mon Jan 27 14:20:02 2003 UTC revision 576 by jonathan, Mon Mar 31 18:31:17 2003 UTC
# Line 1  Line 1 
1  # Copyright (c) 2001 by Intevation GmbH  # Copyright (c) 2001, 2003 by Intevation GmbH
2  # Authors:  # Authors:
3  # Jonathan Coles <[email protected]>  # Jonathan Coles <[email protected]>
4  #  #
# Line 9  Line 9 
9    
10  __version__ = "$Revision$"  __version__ = "$Revision$"
11    
12    import copy
13    
14    from Thuban.Model.table import FIELDTYPE_INT, FIELDTYPE_DOUBLE, \
15         FIELDTYPE_STRING
16    
17  from wxPython.wx import *  from wxPython.wx import *
18  from wxPython.grid import *  from wxPython.grid import *
19    
20  from Thuban import _  from Thuban import _
21    from Thuban.common import *
22    from Thuban.UI.common import *
23    
24    from Thuban.Model.classification import *
25    
26    from Thuban.Model.color import Color
27    
28    from Thuban.Model.layer import Layer, SHAPETYPE_ARC, SHAPETYPE_POLYGON, SHAPETYPE_POINT
29    
30    from dialogs import NonModalDialog
31    
32    # widget id's
33  ID_PROPERTY_SELECT = 4010  ID_PROPERTY_SELECT = 4010
34  ID_CLASS_TABLE = 40011  ID_CLASS_TABLE = 40011
35    
36  ID_CLASSIFY_OK = 4001  ID_CLASSIFY_OK = 4001
37  ID_CLASSIFY_CANCEL = 4002  ID_CLASSIFY_CANCEL = 4002
38    ID_CLASSIFY_ADD = 4003
39    ID_CLASSIFY_GENRANGE = 4004
40    ID_CLASSIFY_REMOVE = 4005
41    ID_CLASSIFY_MOVEUP = 4006
42    ID_CLASSIFY_MOVEDOWN = 4007
43    ID_CLASSIFY_APPLY = 4008
44    
45    # table columns
46    COL_SYMBOL = 0
47    COL_VALUE  = 1
48    COL_LABEL  = 2
49    
50    # indices into the client data lists in Classifier.fields
51    FIELD_CLASS = 0
52    FIELD_TYPE = 1
53    FIELD_NAME = 2
54    
55    #
56    # this is a silly work around to ensure that the table that is
57    # passed into SetTable is the same that is returned by GetTable
58    #
59    import weakref
60    class ClassGrid(wxGrid):
61    
62    
63        def __init__(self, parent):
64            """Constructor.
65    
66            parent -- the parent window
67    
68            clazz -- the working classification that this grid should
69                     use for display.
70            """
71    
72            #wxGrid.__init__(self, parent, ID_CLASS_TABLE, size = (340, 160))
73            wxGrid.__init__(self, parent, ID_CLASS_TABLE)
74            #self.SetTable(ClassTable(fieldData, layer.ShapeType(), self), True)
75    
76            self.currentSelection = []
77    
78            EVT_GRID_CELL_LEFT_DCLICK(self, self._OnCellDClick)
79            EVT_GRID_RANGE_SELECT(self, self._OnSelectedRange)
80            EVT_GRID_SELECT_CELL(self, self._OnSelectedCell)
81    
82            #print "123123123: ", ('Show' in dir(self))
83    
84        #def Show(self):
85            #print "SHOW!"
86    
87        #def Refresh(self):
88            #self.Show()
89        #def Update(self):
90            #self.Show()
91    
92        def CreateTable(self, clazz, shapeType, group = None):
93    
94            assert(isinstance(clazz, Classification))
95    
96            self.shapeType = shapeType
97            table = self.GetTable()
98            if table is None:
99                w = self.GetDefaultColSize() * 3 + self.GetDefaultRowLabelSize()
100                h = self.GetDefaultRowSize() * 4 + self.GetDefaultColLabelSize()
101                self.SetDimensions(-1, -1, w, h)
102                self.SetSizeHints(w, h, -1, -1)
103                table = ClassTable(self)
104                self.SetTable(table, True)
105    
106    
107            self.SetSelectionMode(wxGrid.wxGridSelectRows)
108            self.ClearSelection()
109    
110            #print "8------------------"
111            table.Reset(clazz, self.shapeType, group)
112            #print "9------------------"
113    
114    #   def Show(self, show = True):
115    #       print "SHOW!"
116    #       wxGrid.Show(self, show)
117    
118    #       sel = self.GetCurrentSelection()
119    
120    #       print "( 1"
121    #       if len(sel) == 1:
122    #           print "( 2"
123    #           self.MakeCellVisible(sel[0], 0)
124    
125        def GetCurrentSelection(self):
126            """Return the currently highlighted rows as an increasing list
127               of row numbers."""
128            #print "@@ ", self.currentSelection
129            sel = copy.copy(self.currentSelection)
130            sel.sort()
131            return sel
132    
133        def GetSelectedRows(self):
134            return self.GetCurrentSelection()
135    
136        def SetCellRenderer(self, row, col):
137            raise ValueError(_("Must not allow setting of renderer in ClassGrid!"))
138    
139        #
140        # [Set|Get]Table is taken from http://wiki.wxpython.org
141        # they are needed as a work around to ensure that the table
142        # that is passed to SetTable is the one that is returned
143        # by GetTable.
144        #
145        def SetTable(self, object, *attributes):
146            self.tableRef = weakref.ref(object)
147            return wxGrid.SetTable(self, object, *attributes)
148    
149        def GetTable(self):
150            try:
151                return self.tableRef()
152            except:
153                return None
154    
155        def DeleteSelectedRows(self):
156            """Deletes all highlighted rows.
157      
158            If only one row is highlighted then after it is deleted the
159            row that was below the deleted row is highlighted."""
160    
161            sel = self.GetCurrentSelection()
162    
163            # nothing to do
164            if len(sel) == 0: return
165    
166            # if only one thing is selected check if it is the default
167            # data row, because we can't remove that
168            if len(sel) == 1:
169                #group = self.GetTable().GetValueAsCustom(sel[0], COL_SYMBOL, None)
170                group = self.GetTable().GetClassGroup(sel[0])
171                if isinstance(group, ClassGroupDefault):
172                    wxMessageDialog(self,
173                                    "The Default group cannot be removed.",
174                                    style = wxOK | wxICON_EXCLAMATION).ShowModal()
175                    return
176            
177    
178            self.ClearSelection()
179    
180            # we need to remove things from the bottom up so we don't
181            # change the indexes of rows that will be deleted next
182            sel.reverse()
183    
184            #
185            # actually remove the rows
186            #
187            table = self.GetTable()
188            for row in sel:
189                table.DeleteRows(row)
190    
191            #
192            # if there was only one row selected highlight the row
193            # that was directly below it, or move up one if the
194            # deleted row was the last row.
195            #
196            if len(sel) == 1:
197                r = sel[0]
198                if r > self.GetNumberRows() - 1:
199                    r = self.GetNumberRows() - 1
200                self.SelectRow(r)
201            
202    
203        def SelectGroup(self, group, makeVisible = True):
204            if group is None: return
205    
206            assert(isinstance(group, ClassGroup))
207    
208            table = self.GetTable()
209    
210            assert(table is not None)
211    
212    
213            #print "-- ", group
214            for i in range(table.GetNumberRows()):
215                g = table.GetClassGroup(i)
216                #print "1", g
217                if g is group:
218                    #print "2"
219                    self.SelectRow(i)
220                    if makeVisible:
221                        #print "3"
222                        self.MakeCellVisible(i, 0)
223                    break
224    
225            
226    
227    #
228    # XXX: This isn't working, and there is no way to deselect rows wxPython!
229    #
230    #   def DeselectRow(self, row):
231    #       self.ProcessEvent(
232    #           wxGridRangeSelectEvent(-1,
233    #                                  wxEVT_GRID_RANGE_SELECT,
234    #                                  self,
235    #                                  (row, row), (row, row),
236    #                                  sel = False))
237    
238        def _OnCellDClick(self, event):
239            """Handle a double on a cell."""
240    
241            r = event.GetRow()
242            c = event.GetCol()
243            if c == COL_SYMBOL:
244                prop = self.GetTable().GetValueAsCustom(r, c, None)
245                #prop = group.GetProperties()
246    
247                # get a new ClassGroupProperties object and copy the
248                # values over to our current object
249                propDlg = SelectPropertiesDialog(NULL, prop, self.shapeType)
250                if propDlg.ShowModal() == wxID_OK:
251                    new_prop = propDlg.GetClassGroupProperties()
252                    #prop.SetProperties(new_prop)
253                    self.GetTable().SetValueAsCustom(r, c, None, new_prop)
254                propDlg.Destroy()
255    
256        #
257        # _OnSelectedRange() and _OnSelectedCell() were borrowed
258        # from http://wiki.wxpython.org to keep track of which
259        # cells are currently highlighted
260        #
261        def _OnSelectedRange(self, event):
262            """Internal update to the selection tracking list"""
263            if event.Selecting():
264                for index in range( event.GetTopRow(), event.GetBottomRow()+1):
265                    if index not in self.currentSelection:
266                        #print "    ", index
267                        self.currentSelection.append( index )
268            else:
269                for index in range( event.GetTopRow(), event.GetBottomRow()+1):
270                    while index in self.currentSelection:
271                        #print "    ", index
272                        self.currentSelection.remove( index )
273            #self.ConfigureForSelection()
274    
275            #print self.GetCurrentSelection()
276            event.Skip()
277    
278        def _OnSelectedCell( self, event ):
279            """Internal update to the selection tracking list"""
280            #print "selecting cell: ", event.GetRow()
281            self.currentSelection = [ event.GetRow() ]
282            #self.ConfigureForSelection()
283            event.Skip()
284    
285    class ClassTable(wxPyGridTableBase):
286        """Represents the underlying data structure for the grid."""
287    
288        NUM_COLS = 3
289    
290        __col_labels = [_("Symbol"), _("Value"), _("Label")]
291    
292    
293        def __init__(self, view = None):
294        #def __init__(self, clazz, shapeType, view = None):
295            """Constructor.
296    
297            shapeType -- the type of shape that the layer uses
298    
299            view -- a wxGrid object that uses this class for its table
300            """
301    
302            wxPyGridTableBase.__init__(self)
303    
304            self.SetView(view)
305            self.tdata = []
306    
307            #self.Reset(clazz, shapeType)
308    
309        def Reset(self, clazz, shapeType, group = None):
310            """Reset the table with the given data.
311    
312            This is necessary because wxWindows does not allow a grid's
313            table to change once it has been intially set and so we
314            need a way of modifying the data.
315    
316            clazz -- the working classification that this table should
317                     use for display. This may be different from the
318                     classification in the layer.
319    
320            shapeType -- the type of shape that the layer uses
321            """
322    
323            assert(isinstance(clazz, Classification))
324    
325            self.GetView().BeginBatch()
326    
327            self.fieldType = clazz.GetFieldType()
328            self.shapeType = shapeType
329    
330            old_len = len(self.tdata)
331    
332            self.tdata = []
333    
334            #print "9------------------"
335            #
336            # copy the data out of the classification and into our
337            # array
338            #
339            row = -1
340            for g in clazz:
341                ng = copy.deepcopy(g)
342                self.__SetRow(-1, ng)
343                if g is group:
344                    row = self.GetNumberRows() - 1
345                    #print "selecting row..."
346    
347            #print "10------------------"
348    
349            self.__Modified(-1)
350    
351            self.__NotifyRowChanges(old_len, len(self.tdata))
352            #print "11------------------"
353    
354            if row > -1:
355                self.GetView().ClearSelection()
356                self.GetView().SelectRow(row)
357                self.GetView().MakeCellVisible(row, 0)
358                
359            self.GetView().EndBatch()
360    
361        def __NotifyRowChanges(self, curRows, newRows):
362            #
363            # silly message processing for updates to the number of
364            # rows and columns
365            #
366            if newRows > curRows:
367                msg = wxGridTableMessage(self,
368                            wxGRIDTABLE_NOTIFY_ROWS_APPENDED,
369                            newRows - curRows)    # how many
370                self.GetView().ProcessTableMessage(msg)
371                self.GetView().FitInside()
372            elif newRows < curRows:
373                msg = wxGridTableMessage(self,
374                            wxGRIDTABLE_NOTIFY_ROWS_DELETED,
375                            curRows - newRows,    # position
376                            curRows - newRows)    # how many
377                self.GetView().ProcessTableMessage(msg)
378                self.GetView().FitInside()
379    
380        def __SetRow(self, row, group):
381            """Set a row's data to that of the group.
382    
383            The table is considered modified after this operation.
384    
385            row -- if row is -1 or greater than the current number of rows
386                   then group is appended to the end.
387            """
388    
389            # either append or replace
390            if row == -1 or row >= self.GetNumberRows():
391                self.tdata.append(group)
392            else:
393                self.tdata[row] = group
394    
395            self.__Modified()
396    
397        def GetColLabelValue(self, col):
398            """Return the label for the given column."""
399            return self.__col_labels[col]
400    
401        def GetRowLabelValue(self, row):
402            """Return the label for the given row."""
403    
404            group = self.tdata[row]
405            if isinstance(group, ClassGroupDefault):   return _("Default")
406            if isinstance(group, ClassGroupSingleton): return _("Singleton")
407            if isinstance(group, ClassGroupRange):     return _("Range")
408            if isinstance(group, ClassGroupMap):       return _("Map")
409    
410            assert(False) # shouldn't get here
411            return _("")
412    
413        def GetNumberRows(self):
414            """Return the number of rows."""
415            return len(self.tdata)
416    
417        def GetNumberCols(self):
418            """Return the number of columns."""
419            return self.NUM_COLS
420    
421        def IsEmptyCell(self, row, col):
422            """Determine if a cell is empty. This is always false."""
423            return False
424    
425        def GetValue(self, row, col):
426            """Return the object that is used to represent the given
427               cell coordinates. This may not be a string."""
428            return self.GetValueAsCustom(row, col, None)
429    
430        def SetValue(self, row, col, value):
431            """Assign 'value' to the cell specified by 'row' and 'col'.
432    
433            The table is considered modified after this operation.
434            """
435    
436            self.SetValueAsCustom(row, col, None, value)
437            self.__Modified()
438          
439        def GetValueAsCustom(self, row, col, typeName):
440            """Return the object that is used to represent the given
441               cell coordinates. This may not be a string.
442    
443            typeName -- unused, but needed to overload wxPyGridTableBase
444            """
445    
446            group = self.tdata[row]
447    
448            if col == COL_SYMBOL:
449                return group.GetProperties()
450    
451            if col == COL_LABEL:
452                return group.GetLabel()
453    
454            # col must be COL_VALUE
455            assert(col == COL_VALUE)
456    
457            if isinstance(group, ClassGroupDefault):
458                return _("DEFAULT")
459            elif isinstance(group, ClassGroupSingleton):
460                return group.GetValue()
461            elif isinstance(group, ClassGroupRange):
462                return _("%s - %s") % (group.GetMin(), group.GetMax())
463    
464            assert(False) # shouldn't get here
465            return None
466    
467        def __ParseInput(self, value):
468            """Try to determine what kind of input value is
469               (string, number, or range)
470    
471            Returns a tuple of length one if there is a single
472            value, or of length two if it is a range.
473            """
474    
475            type = self.fieldType
476    
477            if type == FIELDTYPE_STRING:
478                return (value,)
479            elif type == FIELDTYPE_INT or type == FIELDTYPE_DOUBLE:
480    
481                if type == FIELDTYPE_INT:
482                    conv = lambda p: int(float(p))
483                else:
484                    conv = lambda p: p
485    
486                #
487                # first try to take the input as a single number
488                # if there's an exception try to break it into
489                # a range seperated by a '-'. take care to ignore
490                # a leading '-' as that could be for a negative number.
491                # then try to parse the individual parts. if there
492                # is an exception here, let it pass up to the calling
493                # function.
494                #
495                try:
496                    return (conv(Str2Num(value)),)
497                except ValueError:
498                    i = value.find('-')
499                    if i == 0:
500                        i = value.find('-', 1)
501    
502                    return (conv(Str2Num(value[:i])), conv(Str2Num(value[i+1:])))
503    
504            assert(False) # shouldn't get here
505            return (0,)
506                
507    
508        def SetValueAsCustom(self, row, col, typeName, value):
509            """Set the cell specified by 'row' and 'col' to 'value'.
510    
511            If column represents the value column, the input is parsed
512            to determine if a string, number, or range was entered.
513            A new ClassGroup may be created if the type of data changes.
514    
515            The table is considered modified after this operation.
516    
517            typeName -- unused, but needed to overload wxPyGridTableBase
518            """
519    
520            assert(col >= 0 and col < self.GetNumberCols())
521            assert(row >= 0 and row < self.GetNumberRows())
522    
523            group = self.tdata[row]
524    
525            mod = True # assume the data will change
526    
527            if col == COL_SYMBOL:
528                group.SetProperties(value)
529            elif col == COL_LABEL:
530                group.SetLabel(value)
531            elif col == COL_VALUE:
532                if isinstance(group, ClassGroupDefault):
533                    # not allowed to modify the default value
534                    pass
535                elif isinstance(group, ClassGroupMap):
536                    # something special
537                    pass
538                else: # SINGLETON, RANGE
539                    try:
540                        dataInfo = self.__ParseInput(value)
541                    except ValueError:
542                        # bad input, ignore the request
543                        mod = False
544                    else:
545    
546                        changed = False
547                        ngroup = group
548                        props = group.GetProperties()
549    
550                        #
551                        # try to update the values, which may include
552                        # changing the underlying group type if the
553                        # group was a singleton and a range was entered
554                        #
555                        if len(dataInfo) == 1:
556                            if not isinstance(group, ClassGroupSingleton):
557                                ngroup = ClassGroupSingleton(prop = props)
558                                changed = True
559                            ngroup.SetValue(dataInfo[0])
560                        elif len(dataInfo) == 2:
561                            if not isinstance(group, ClassGroupRange):
562                                ngroup = ClassGroupRange(prop = props)
563                                changed = True
564                            ngroup.SetRange(dataInfo[0], dataInfo[1])
565                        else:
566                            assert(False)
567                            pass
568    
569                        if changed:
570                            ngroup.SetLabel(group.GetLabel())
571                            self.SetClassGroup(row, ngroup)
572            else:
573                assert(False) # shouldn't be here
574                pass
575    
576            if mod:
577                self.__Modified()
578                self.GetView().Refresh()
579    
580        def GetAttr(self, row, col, someExtraParameter):
581            """Returns the cell attributes"""
582    
583  class Classifier(wxDialog):          attr = wxGridCellAttr()
584            #attr = wxPyGridTableBase.GetAttr(self, row, col, someExtraParameter)
585    
586            if col == COL_SYMBOL:
587                # we need to create a new renderer each time, because
588                # SetRenderer takes control of the parameter
589                attr.SetRenderer(ClassRenderer(self.shapeType))
590                attr.SetReadOnly()
591    
592            return attr
593    
594        def GetClassGroup(self, row):
595            """Return the ClassGroup object representing row 'row'."""
596    
597            return self.tdata[row] # self.GetValueAsCustom(row, COL_SYMBOL, None)
598    
599        def SetClassGroup(self, row, group):
600            self.__SetRow(row, group)
601            self.GetView().Refresh()
602    
603        def __Modified(self, mod = True):
604            """Adjust the modified flag.
605    
606            mod -- if -1 set the modified flag to False, otherwise perform
607                   an 'or' operation with the current value of the flag and
608                   'mod'
609            """
610    
611            if mod == -1:
612                self.modified = False
613            else:
614                self.modified = mod or self.modified
615    
616        def IsModified(self):
617            """True if this table is considered modified."""
618            return self.modified
619    
620        def DeleteRows(self, pos, numRows = 1):
621            """Deletes 'numRows' beginning at row 'pos'.
622    
623            The row representing the default group is not removed.
624    
625            The table is considered modified if any rows are removed.
626            """
627    
628            assert(pos >= 0)
629            old_len = len(self.tdata)
630            for row in range(pos, pos - numRows, -1):
631                group = self.GetClassGroup(row)
632                if not isinstance(group, ClassGroupDefault):
633                    self.tdata.pop(row)
634                    self.__Modified()
635            
636      def __init__(self, parent, layer):          if self.IsModified():
637          wxDialog.__init__(self, parent, -1, _("Classify"),              self.__NotifyRowChanges(old_len, len(self.tdata))
638                            style = wxRESIZE_BORDER)  
639        def AppendRows(self, numRows = 1):
640            """Append 'numRows' empty rows to the end of the table.
641    
642            The table is considered modified if any rows are appended.
643            """
644    
645            old_len = len(self.tdata)
646            for i in range(numRows):
647                np = ClassGroupSingleton()
648                self.__SetRow(-1, np)
649    
650            if self.IsModified():
651                self.__NotifyRowChanges(old_len, len(self.tdata))
652    
653    
654    class Classifier(NonModalDialog):
655    
656        def __init__(self, parent, name, layer, group = None):
657            NonModalDialog.__init__(self, parent, name,
658                                    _("Classifier: %s") % layer.Title())
659    
660            panel = wxPanel(self, -1, size=(100, 100))
661    
662            self.layer = layer
663    
664            self.originalClass = self.layer.GetClassification()
665            field = self.originalClass.GetField()
666            fieldType = self.originalClass.GetFieldType()
667    
668          topBox = wxBoxSizer(wxVERTICAL)          topBox = wxBoxSizer(wxVERTICAL)
669            panelBox = wxBoxSizer(wxVERTICAL)
670    
671            #panelBox.Add(wxStaticText(panel, -1, _("Layer: %s") % layer.Title()),
672                #0, wxALIGN_LEFT | wxALL, 4)
673            panelBox.Add(wxStaticText(panel, -1,
674                                    _("Layer Type: %s") % layer.ShapeType()),
675                0, wxALIGN_LEFT | wxALL, 4)
676    
         propertyBox = wxBoxSizer(wxHORIZONTAL)  
         propertyBox.Add(wxStaticText(self, -1, _("Property")),  
             0, wxALIGN_CENTER | wxALL, 4)  
677    
678          self.properties = wxComboBox(self, ID_PROPERTY_SELECT, "",          #
679            # make field combo box
680            #
681            self.fields = wxComboBox(panel, ID_PROPERTY_SELECT, "",
682                                       style = wxCB_READONLY)                                       style = wxCB_READONLY)
683    
684          self.num_cols = layer.table.field_count()          self.num_cols = layer.table.field_count()
685            # just assume the first field in case one hasn't been
686            # specified in the file.
687            self.__cur_field = 0
688    
689            self.fields.Append("<None>")
690            self.fields.SetClientData(0, None)
691    
692          for i in range(self.num_cols):          for i in range(self.num_cols):
693              type, name, len, decc = layer.table.field_info(i)              type, name, len, decc = layer.table.field_info(i)
694              self.properties.Append(name)              self.fields.Append(name)
695    
696                if name == field:
697                    self.__cur_field = i + 1
698                    self.fields.SetClientData(i + 1, self.originalClass)
699                else:
700                    self.fields.SetClientData(i + 1, None)
701    
702    
703            ###########
704    
705          propertyBox.Add(self.properties, 0, wxALL, 4)          self.fieldTypeText = wxStaticText(panel, -1, "")
706          EVT_COMBOBOX(self, ID_PROPERTY_SELECT, self.OnPropertySelect)          panelBox.Add(self.fieldTypeText, 0,
707                         wxGROW | wxALIGN_LEFT | wxALL | wxADJUST_MINSIZE, 4)
708    
709            propertyBox = wxBoxSizer(wxHORIZONTAL)
710            propertyBox.Add(wxStaticText(panel, -1, _("Field: ")),
711                0, wxALIGN_LEFT | wxALL, 4)
712            propertyBox.Add(self.fields, 1, wxGROW|wxALL, 4)
713            EVT_COMBOBOX(self, ID_PROPERTY_SELECT, self._OnFieldSelect)
714    
715            panelBox.Add(propertyBox, 0, wxGROW, 4)
716    
717    
718            #
719            # Control Box
720            #
721            controlBox = wxBoxSizer(wxHORIZONTAL)
722    
         topBox.Add(propertyBox, 0, 0)  
723    
724            ###########
725            #
726            # Control buttons:
727            #
728            self.controlButtons = []
729    
730            controlButtonBox = wxBoxSizer(wxVERTICAL)
731    
732            button = wxButton(panel, ID_CLASSIFY_ADD, _("Add"))
733            controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
734            self.controlButtons.append(button)
735    
736            button = wxButton(panel, ID_CLASSIFY_GENRANGE, _("Generate Ranges"))
737            controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
738            self.controlButtons.append(button)
739    
740            button = wxButton(panel, ID_CLASSIFY_MOVEUP, _("Move Up"))
741            controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
742            self.controlButtons.append(button)
743    
744            button = wxButton(panel, ID_CLASSIFY_MOVEDOWN, _("Move Down"))
745            controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
746            self.controlButtons.append(button)
747    
748            controlButtonBox.Add(60, 20, 0, wxGROW | wxALL | wxALIGN_BOTTOM, 4)
749    
750            button = wxButton(panel, ID_CLASSIFY_REMOVE, _("Remove"))
751            controlButtonBox.Add(button, 0, wxGROW | wxALL | wxALIGN_BOTTOM, 4)
752            self.controlButtons.append(button)
753    
754    
755            ###########
756          #          #
757          # Classification data table          # Classification data table
758          #          #
759    
760          table = wxPyGridTableBase()          self.classGrid = ClassGrid(panel)
761          tableBox = wxGridSizer(25)          #self.__SetGridTable(self.__cur_field, group)
762          self.classTable = wxGrid(self, ID_CLASS_TABLE)          #self.fields.SetSelection(self.__cur_field)
         self.classTable.CreateGrid(10, 2)  
         self.classTable.SetTable(table, true)  
         #table.SetNumberRows(10)  
         #table.SetNumberCols(2)  
         table.SetColLabelValue(0, _("Class"))  
         table.SetColLabelValue(1, _("Value"))  
         #self.classTable.SetColLabelValue(0, _("Class"))  
         #self.classTable.SetColLabelValue(1, _("Value"))  
         #self.classTable.SetCellValue(1, 1, _("Value"))  
763    
764          tableBox.Add(self.classTable, 0, wxALL, 4)          # calling __SelectField after creating the classGrid fills in the
765                    # grid with the correct information
766          topBox.Add(self.classTable, 0, 0)          #print "2------------------"
767            self.__SelectField(self.__cur_field, group = group)
768    
769            #self.classGrid.SelectGroup(group)
770    
771            controlBox.Add(self.classGrid, 1, wxGROW, 0)
772    
773    
774    
775            controlBox.Add(controlButtonBox, 0, wxGROW, 10)
776            panelBox.Add(controlBox, 1, wxGROW, 10)
777    
778            EVT_BUTTON(self, ID_CLASSIFY_ADD, self._OnAdd)
779            EVT_BUTTON(self, ID_CLASSIFY_REMOVE, self._OnRemove)
780            EVT_BUTTON(self, ID_CLASSIFY_GENRANGE, self._OnGenRange)
781            EVT_BUTTON(self, ID_CLASSIFY_MOVEUP, self._OnMoveUp)
782            EVT_BUTTON(self, ID_CLASSIFY_MOVEDOWN, self._OnMoveDown)
783    
784            ###########
785    
786            buttonBox = wxBoxSizer(wxHORIZONTAL)
787            buttonBox.Add(wxButton(panel, ID_CLASSIFY_OK, _("OK")),
788                          0, wxALL, 4)
789            buttonBox.Add(60, 20, 0, wxALL, 4)
790            buttonBox.Add(wxButton(panel, ID_CLASSIFY_APPLY, _("Apply")),
791                          0, wxALL, 4)
792            buttonBox.Add(60, 20, 0, wxALL, 4)
793            buttonBox.Add(wxButton(panel, ID_CLASSIFY_CANCEL, _("Cancel")),
794                          0, wxALL, 4)
795            panelBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 0)
796    
797            EVT_BUTTON(self, ID_CLASSIFY_OK, self._OnOK)
798            EVT_BUTTON(self, ID_CLASSIFY_APPLY, self._OnApply)
799            EVT_BUTTON(self, ID_CLASSIFY_CANCEL, self._OnCancel)
800    
801            ###########
802    
803    
804            panel.SetAutoLayout(True)
805            panel.SetSizer(panelBox)
806            panelBox.SetSizeHints(panel)
807    
808            topBox.Add(panel, 1, wxGROW, 0)
809            panelBox.SetSizeHints(self)
810            self.SetAutoLayout(True)
811            self.SetSizer(topBox)
812    
813            #print "1------------------"
814            #self.Fit()
815            ######################
816    
817            self.haveApplied = False
818    
819        def __BuildClassification(self, fieldIndex):
820    
821            numRows = self.classGrid.GetNumberRows()
822            assert(numRows > 0) # there should always be a default row
823    
824            clazz = Classification()
825            if fieldIndex == 0:
826                fieldName = None
827                fieldType = None
828            else:
829                fieldName = self.fields.GetString(fieldIndex)
830                fieldType = self.layer.GetFieldType(fieldName)
831    
832            clazz.SetField(fieldName)
833            clazz.SetFieldType(fieldType)
834    
835    
836            table = self.classGrid.GetTable()
837            clazz.SetDefaultGroup(table.GetClassGroup(0))
838    
839            for i in range(1, numRows):
840                clazz.AddGroup(table.GetClassGroup(i))
841    
842            return clazz
843    
844        def __SetGridTable(self, fieldIndex, group = None):
845    
846            clazz = self.fields.GetClientData(fieldIndex)
847    
848            if clazz is None:
849                clazz = Classification()
850                clazz.SetDefaultGroup(
851                    ClassGroupDefault(
852                        self.layer.GetClassification().
853                                   GetDefaultGroup().GetProperties()))
854    
855                fieldName = self.fields.GetString(fieldIndex)
856                fieldType = self.layer.GetFieldType(fieldName)
857                clazz.SetFieldType(fieldType)
858                    
859            #print "6------------------"
860            self.classGrid.CreateTable(clazz, self.layer.ShapeType(), group)
861            #print "7------------------"
862    
863    
864    
865        type2string = {None:             _("None"),
866                       FIELDTYPE_STRING: _("Text"),
867                       FIELDTYPE_INT:    _("Integer"),
868                       FIELDTYPE_DOUBLE: _("Decimal")}
869    
870        def __SetFieldTypeText(self, fieldIndex):
871            fieldName = self.fields.GetString(fieldIndex)
872            fieldType = self.layer.GetFieldType(fieldName)
873    
874            assert(Classifier.type2string.has_key(fieldType))
875    
876            text = Classifier.type2string[fieldType]
877    
878            self.fieldTypeText.SetLabel(_("Field Type: %s") % text)
879    
880        def __SelectField(self, newIndex, oldIndex = -1, group = None):
881    
882            #print "3------------------"
883    
884            assert(oldIndex >= -1)
885    
886            self.fields.SetSelection(newIndex)
887    
888            if oldIndex != -1:
889                clazz = self.__BuildClassification(oldIndex)
890                self.fields.SetClientData(oldIndex, clazz)
891    
892            #print "4------------------"
893            self.__SetGridTable(newIndex, group)
894            #print "5------------------"
895    
896            enabled = newIndex != 0
897    
898            for b in self.controlButtons:
899                b.Enable(enabled)
900    
901            self.__SetFieldTypeText(newIndex)
902    
903    
904        def _OnFieldSelect(self, event):
905            index = self.fields.GetSelection()
906            self.__SelectField(index, self.__cur_field)
907            self.__cur_field = index
908    
909        def _OnApply(self, event):
910            """Put the data from the table into a new Classification and hand
911               it to the layer.
912            """
913    
914            clazz = self.fields.GetClientData(self.__cur_field)
915    
916            #
917            # only build the classification if there wasn't one to
918            # to begin with or it has been modified
919            #
920            if clazz is None or self.classGrid.GetTable().IsModified():
921                clazz = self.__BuildClassification(self.__cur_field)
922    
923            self.layer.SetClassification(clazz)
924    
925            self.haveApplied = True
926    
927        def _OnOK(self, event):
928            self._OnApply(event)
929            self.OnClose(event)
930    
931        def _OnCancel(self, event):
932            """The layer's current classification stays the same."""
933            if self.haveApplied:
934                self.layer.SetClassification(self.originalClass)
935    
936            self.OnClose(event)
937    
938        def _OnAdd(self, event):
939            self.classGrid.AppendRows()
940    
941        def _OnRemove(self, event):
942            self.classGrid.DeleteSelectedRows()
943    
944        def _OnGenRange(self, event):
945            print "Classifier._OnGenRange()"
946    
947        def _OnMoveUp(self, event):
948            sel = self.classGrid.GetCurrentSelection()
949    
950            #print "sel: ", sel
951    
952            if len(sel) == 1:
953                i = sel[0]
954                if i > 1:
955                    table = self.classGrid.GetTable()
956                    x = table.GetClassGroup(i - 1)
957                    y = table.GetClassGroup(i)
958                    table.SetClassGroup(i - 1, y)
959                    table.SetClassGroup(i, x)
960                    self.classGrid.ClearSelection()
961                    self.classGrid.SelectRow(i - 1)
962                    self.classGrid.MakeCellVisible(i - 1, 0)
963    
964        def _OnMoveDown(self, event):
965            sel = self.classGrid.GetCurrentSelection()
966    
967            if len(sel) == 1:
968                i = sel[0]
969                table = self.classGrid.GetTable()
970                if 0 < i < table.GetNumberRows() - 1:
971                    x = table.GetClassGroup(i)
972                    y = table.GetClassGroup(i + 1)
973                    table.SetClassGroup(i, y)
974                    table.SetClassGroup(i + 1, x)
975                    self.classGrid.ClearSelection()
976                    self.classGrid.SelectRow(i + 1)
977                    self.classGrid.MakeCellVisible(i + 1, 0)
978    
979    
980    ID_SELPROP_OK = 4001
981    ID_SELPROP_CANCEL = 4002
982    ID_SELPROP_SPINCTRL = 4002
983    ID_SELPROP_PREVIEW = 4003
984    ID_SELPROP_STROKECLR = 4004
985    ID_SELPROP_FILLCLR = 4005
986    ID_SELPROP_STROKECLRTRANS = 4006
987    ID_SELPROP_FILLCLRTRANS = 4007
988    
989    class SelectPropertiesDialog(wxDialog):
990    
991        def __init__(self, parent, prop, shapeType):
992            wxDialog.__init__(self, parent, -1, _("Select Properties"),
993                              style = wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
994    
995            self.prop = ClassGroupProperties(prop)
996    
997            topBox = wxBoxSizer(wxVERTICAL)
998    
999            itemBox = wxBoxSizer(wxHORIZONTAL)
1000    
1001            # preview box
1002            previewBox = wxBoxSizer(wxVERTICAL)
1003            previewBox.Add(wxStaticText(self, -1, _("Preview:")),
1004                0, wxALIGN_LEFT | wxALL, 4)
1005            self.previewWin = ClassDataPreviewWindow(None, self.prop, shapeType,
1006                                                self, ID_SELPROP_PREVIEW, (40, 40))
1007            previewBox.Add(self.previewWin, 1, wxGROW, 15)
1008    
1009            itemBox.Add(previewBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
1010    
1011            # control box
1012            ctrlBox = wxBoxSizer(wxVERTICAL)
1013    
1014            lineColorBox = wxBoxSizer(wxHORIZONTAL)
1015            lineColorBox.Add(
1016                wxButton(self, ID_SELPROP_STROKECLR, _("Change Line Color")),
1017                1, wxALL | wxGROW, 4)
1018            EVT_BUTTON(self, ID_SELPROP_STROKECLR, self._OnChangeLineColor)
1019    
1020            lineColorBox.Add(
1021                wxButton(self, ID_SELPROP_STROKECLRTRANS, _("Transparent")),
1022                1, wxALL | wxGROW, 4)
1023            EVT_BUTTON(self, ID_SELPROP_STROKECLRTRANS,
1024                       self._OnChangeLineColorTrans)
1025    
1026            ctrlBox.Add(lineColorBox, 0,
1027                        wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
1028    
1029            if shapeType != SHAPETYPE_ARC:
1030                fillColorBox = wxBoxSizer(wxHORIZONTAL)
1031                fillColorBox.Add(
1032                    wxButton(self, ID_SELPROP_FILLCLR, _("Change Fill Color")),
1033                    1, wxALL | wxGROW, 4)
1034                EVT_BUTTON(self, ID_SELPROP_FILLCLR, self._OnChangeFillColor)
1035                fillColorBox.Add(
1036                    wxButton(self, ID_SELPROP_FILLCLRTRANS, _("Transparent")),
1037                    1, wxALL | wxGROW, 4)
1038                EVT_BUTTON(self, ID_SELPROP_FILLCLRTRANS,
1039                           self._OnChangeFillColorTrans)
1040                ctrlBox.Add(fillColorBox, 0,
1041                            wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
1042    
1043            spinBox = wxBoxSizer(wxHORIZONTAL)
1044            spinBox.Add(wxStaticText(self, -1, _("Line Width: ")),
1045                    0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
1046            self.spinCtrl = wxSpinCtrl(self, ID_SELPROP_SPINCTRL,
1047                                       min=1, max=10,
1048                                       value=str(prop.GetLineWidth()),
1049                                       initial=prop.GetLineWidth())
1050    
1051            EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL, self._OnSpin)
1052    
1053            spinBox.Add(self.spinCtrl, 0, wxALIGN_LEFT | wxALL, 4)
1054    
1055            ctrlBox.Add(spinBox, 0, wxALIGN_RIGHT | wxALL, 0)
1056            itemBox.Add(ctrlBox, 0, wxALIGN_RIGHT | wxALL | wxGROW, 0)
1057            topBox.Add(itemBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
1058    
1059          #          #
1060          # Control buttons:          # Control buttons:
1061          #          #
1062          buttonBox = wxBoxSizer(wxHORIZONTAL)          buttonBox = wxBoxSizer(wxHORIZONTAL)
1063          buttonBox.Add(wxButton(self, ID_CLASSIFY_OK, _("OK")),          buttonBox.Add(wxButton(self, ID_SELPROP_OK, _("OK")),
1064                        0, wxALL, 4)                        0, wxALL, 4)
1065          buttonBox.Add(wxButton(self, ID_CLASSIFY_CANCEL, _("Cancel")),          buttonBox.Add(wxButton(self, ID_SELPROP_CANCEL, _("Cancel")),
1066                        0, wxALL, 4)                        0, wxALL, 4)
1067          topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)          topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)
1068                                                                                    
1069          EVT_BUTTON(self, ID_CLASSIFY_OK, self.OnOK)          EVT_BUTTON(self, ID_SELPROP_OK, self._OnOK)
1070          EVT_BUTTON(self, ID_CLASSIFY_CANCEL, self.OnCancel)          EVT_BUTTON(self, ID_SELPROP_CANCEL, self._OnCancel)
1071                                                                                    
1072          self.SetAutoLayout(true)          self.SetAutoLayout(True)
1073          self.SetSizer(topBox)          self.SetSizer(topBox)
1074          topBox.Fit(self)          topBox.Fit(self)
1075          topBox.SetSizeHints(self)          topBox.SetSizeHints(self)
1076    
1077      def OnPropertySelect(self, event): pass      def _OnOK(self, event):
   
     def OnOK(self, event):  
1078          self.EndModal(wxID_OK)          self.EndModal(wxID_OK)
1079    
1080      def OnCancel(self, event):      def _OnCancel(self, event):
1081          self.EndModal(wxID_CANCEL)          self.EndModal(wxID_CANCEL)
1082    
1083        def _OnSpin(self, event):
1084            self.prop.SetLineWidth(self.spinCtrl.GetValue())
1085            self.previewWin.Refresh()
1086    
1087        def __GetColor(self, cur):
1088            dialog = wxColourDialog(self)
1089            dialog.GetColourData().SetColour(Color2wxColour(cur))
1090            ret = None
1091            if dialog.ShowModal() == wxID_OK:
1092                ret = wxColour2Color(dialog.GetColourData().GetColour())
1093    
1094            dialog.Destroy()
1095    
1096            return ret
1097            
1098        def _OnChangeLineColor(self, event):
1099            clr = self.__GetColor(self.prop.GetLineColor())
1100            if clr is not None:
1101                self.prop.SetLineColor(clr)
1102            self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1103    
1104        def _OnChangeLineColorTrans(self, event):
1105            self.prop.SetLineColor(Color.None)
1106            self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1107            
1108        def _OnChangeFillColor(self, event):
1109            clr = self.__GetColor(self.prop.GetFill())
1110            if clr is not None:
1111                self.prop.SetFill(clr)
1112            self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1113    
1114        def _OnChangeFillColorTrans(self, event):
1115            self.prop.SetFill(Color.None)
1116            self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1117    
1118        def GetClassGroupProperties(self):
1119            return self.prop
1120    
1121    
1122    class ClassDataPreviewWindow(wxWindow):
1123    
1124        def __init__(self, rect, prop, shapeType,
1125                           parent = None, id = -1, size = wxDefaultSize):
1126            if parent is not None:
1127                wxWindow.__init__(self, parent, id, (0, 0), size)
1128                EVT_PAINT(self, self._OnPaint)
1129    
1130            self.rect = rect
1131    
1132            self.prop = prop
1133            self.shapeType = shapeType
1134            self.previewer = ClassDataPreviewer()
1135    
1136        def _OnPaint(self, event):
1137            dc = wxPaintDC(self)
1138    
1139            # XXX: this doesn't seem to be having an effect:
1140            dc.DestroyClippingRegion()
1141    
1142            if self.rect is None:
1143                w, h = self.GetSize()
1144                rect = wxRect(0, 0, w, h)
1145            else:
1146                rect = self.rect
1147    
1148            self.previewer.Draw(dc, rect, self.prop, self.shapeType)
1149    
1150    class ClassDataPreviewer:
1151    
1152        def Draw(self, dc, rect, prop, shapeType):
1153    
1154            assert(dc is not None)
1155            assert(isinstance(prop, ClassGroupProperties))
1156    
1157            if rect is None:
1158                x = 0
1159                y = 0
1160                w, h = dc.GetSize()
1161            else:
1162                x = rect.GetX()
1163                y = rect.GetY()
1164                w = rect.GetWidth()
1165                h = rect.GetHeight()
1166    
1167            stroke = prop.GetLineColor()
1168            if stroke is Color.None:
1169                pen = wxTRANSPARENT_PEN
1170            else:
1171                pen = wxPen(Color2wxColour(stroke),
1172                            prop.GetLineWidth(),
1173                            wxSOLID)
1174    
1175            stroke = prop.GetFill()
1176            if stroke is Color.None:
1177                brush = wxTRANSPARENT_BRUSH
1178            else:
1179                brush = wxBrush(Color2wxColour(stroke), wxSOLID)
1180    
1181            dc.SetPen(pen)
1182            dc.SetBrush(brush)
1183    
1184            if shapeType == SHAPETYPE_ARC:
1185                dc.DrawSpline([wxPoint(x, y + h),
1186                               wxPoint(x + w/2, y + h/4),
1187                               wxPoint(x + w/2, y + h/4*3),
1188                               wxPoint(x + w, y)])
1189    
1190            elif shapeType == SHAPETYPE_POINT:
1191    
1192                dc.DrawCircle(x + w/2, y + h/2,
1193                              (min(w, h) - prop.GetLineWidth())/2)
1194    
1195            elif shapeType == SHAPETYPE_POLYGON:
1196                dc.DrawRectangle(x, y, w, h)
1197    
1198    class ClassRenderer(wxPyGridCellRenderer):
1199    
1200        def __init__(self, shapeType):
1201            wxPyGridCellRenderer.__init__(self)
1202            self.shapeType = shapeType
1203            self.previewer = ClassDataPreviewer()
1204    
1205        def Draw(self, grid, attr, dc, rect, row, col, isSelected):
1206            data = grid.GetTable().GetClassGroup(row)
1207    
1208            dc.SetClippingRegion(rect.GetX(), rect.GetY(),
1209                                 rect.GetWidth(), rect.GetHeight())
1210            dc.SetPen(wxPen(wxLIGHT_GREY))
1211            dc.SetBrush(wxBrush(wxLIGHT_GREY, wxSOLID))
1212            dc.DrawRectangle(rect.GetX(), rect.GetY(),
1213                             rect.GetWidth(), rect.GetHeight())
1214    
1215            if not isinstance(data, ClassGroupMap):
1216                self.previewer.Draw(dc, rect, data.GetProperties(), self.shapeType)
1217    
1218            if isSelected:
1219                dc.SetPen(wxPen(wxColour(0 * 255, 0 * 255, 0 * 255),
1220                          4, wxSOLID))
1221                dc.SetBrush(wxTRANSPARENT_BRUSH)
1222                dc.DrawRectangle(rect.GetX(), rect.GetY(),
1223                                 rect.GetWidth(), rect.GetHeight())
1224    
1225            dc.DestroyClippingRegion()
1226    

Legend:
Removed from v.374  
changed lines
  Added in v.576

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26