[Thuban-commits] r2688 - in trunk/thuban: . Doc/manual Resources/XML Thuban/Model Thuban/UI test

scm-commit@wald.intevation.org scm-commit at wald.intevation.org
Fri Jun 30 14:27:22 CEST 2006


Author: frank
Date: 2006-06-30 14:27:20 +0200 (Fri, 30 Jun 2006)
New Revision: 2688

Modified:
   trunk/thuban/ChangeLog
   trunk/thuban/Doc/manual/thuban-manual.xml
   trunk/thuban/Resources/XML/thuban-1.1.dtd
   trunk/thuban/Thuban/Model/classification.py
   trunk/thuban/Thuban/Model/load.py
   trunk/thuban/Thuban/Model/save.py
   trunk/thuban/Thuban/UI/classifier.py
   trunk/thuban/test/test_classification.py
   trunk/thuban/test/test_layer.py
   trunk/thuban/test/test_load.py
   trunk/thuban/test/test_save.py
Log:
New Classification "Pattern": Classify text attributes by regexp.

Modified: trunk/thuban/ChangeLog
===================================================================
--- trunk/thuban/ChangeLog	2006-06-29 13:50:16 UTC (rev 2687)
+++ trunk/thuban/ChangeLog	2006-06-30 12:27:20 UTC (rev 2688)
@@ -1,3 +1,50 @@
+2006-06-27 Frank Koormann <frank at intevation.de>
+
+	New Classification "Pattern": Classify text attributes by regexp.
+
+	* Thuban/Model/classification.py (class ClassGroupPattern): 
+	New, group is associated with a regular expression.
+	(Classification._compile_classification): Store compiled regexp and
+	original group for pattern.
+	(Classification.FindGroup): Added pattern.
+
+	* Thuban/UI/classifier.py 
+	(ClassGrid._OnLabelRightClicked, ClassGrid.labelPopup): 
+	New, add popup to select singleton/pattern.
+	(ClassTable.GetRowLabelValue, ClassTable.GetValueAsCustom,
+	ClassTable.SetValueAsCustom): Added pattern.
+	(ClassTable.__ParseInput): Autodetect singleton/pattern.
+
+	* Thuban/Model/save.py (SessionSaver.write_classification): 
+	Added pattern.
+
+	* Thuban/Model/load.py (SessionLoader.start_clpattern,
+	SessionLoader.end_clpattern): New, process pattern elements.
+
+	* test/test_classification.py (class TestClassGroupPattern): New.
+	(TestClassification.test_add_pattern, 
+	TestClassification.test_multiple_groups_textual,
+	TestClassification.test_deepcopy_textual): New.
+	(TestClassification.test_multiple_groups_numerical): 
+	Renamed test_multiple_groups.
+	(TestClassification.test_deepcopy_numerical): Renamed test_deepcopy.
+
+	* test/test_save.py (SaveSessionTest.testClassifiedLayer): 
+	Added pattern.
+
+	* test/test_load.py (ClassificationTest.TestLayers, TestClassification):
+	Added pattern.
+
+	* test/test_layer.py 
+	(TestLayerModification.test_set_classification_textual): New
+	(TestLayerModification.test_set_classification_numerical):
+	Renamed test_set_classification.
+
+	* Doc/manual/thuban-manual.xml: Added pattern to layer classification
+	description.
+
+	* Resources/XML/thuban-1.1.dtd: Added clpattern element and attribs.
+
 2006-06-29 Didrik Pinte <dpinte at itae.be>
 
 	* Thuban/version.py: Bugfix determination of SQLite, the wright one !

Modified: trunk/thuban/Doc/manual/thuban-manual.xml
===================================================================
--- trunk/thuban/Doc/manual/thuban-manual.xml	2006-06-29 13:50:16 UTC (rev 2687)
+++ trunk/thuban/Doc/manual/thuban-manual.xml	2006-06-30 12:27:20 UTC (rev 2688)
@@ -19,13 +19,13 @@
       </author>
 	</authorgroup>
     <copyright>
-      <year>2003, 2004, 2005</year>
+      <year>2003, 2004, 2005, 2006</year>
       <holder>Intevation GmbH</holder>
     </copyright>
    <revhistory>
 <!-- comment this first revision out when releasing a real version -->
      <revision>
-        <revnumber>CVS version $Id$</revnumber>
+        <revnumber>SVN version $Id$</revnumber>
         <date></date>
         <revremark>Under development.</revremark>
      </revision>
@@ -1192,14 +1192,21 @@
         <section><title>Value</title>
             <para>
             The Value column of the classification table is the value that will
-            be matched when the map is being drawn. The type of data that can
+            be matched when the map is being drawn. The type of filter that can
             entered into this field depends on the type of data of the 
-            classification field. 
-            </para>
+            classification field:
+	    </para>
             <para>
             If the field is of type Text, anything entered
-            into the field is valid. The text will be compared literally to the
-            value of the shape attribute, including case sensitivity. 
+            into the field is valid. By default the text will be compared 
+            literally to the
+            value of the shape attribute, including case sensitivity.
+            Alternatively the comparison can be based on regular experessions.
+            Right-click on the row label to open a popup menu with the options
+            <guibutton>Singleton</guibutton> (literal comparison) and  
+            <guibutton>Pattern</guibutton> (regular expressions).  
+            </para>
+            <para> 
             If the type is Integer, then any valid integer may be entered. In 
             addition, with special syntax, a range of values can be entered.
             A range from <varname>start</varname> to <varname>end</varname> 
@@ -1210,7 +1217,7 @@
             Decimal. They represent any rational number and can be used in 
             ranges as well.
             </para>
-  	    </section>
+       </section>
   	    <section><title>Label</title>
     	    <para>
             By default, the text that is displayed for a group in the legend

Modified: trunk/thuban/Resources/XML/thuban-1.1.dtd
===================================================================
--- trunk/thuban/Resources/XML/thuban-1.1.dtd	2006-06-29 13:50:16 UTC (rev 2687)
+++ trunk/thuban/Resources/XML/thuban-1.1.dtd	2006-06-30 12:27:20 UTC (rev 2688)
@@ -179,13 +179,14 @@
 
 
 <!-- Classification data -->
-<!ELEMENT classification (clnull?, clpoint*, clrange*, clcont*)>
+<!ELEMENT classification (clnull?, clpoint*, clrange*, clpattern*, clcont*)>
 <!ATTLIST classification field CDATA>
 <!ATTLIST classification field_type CDATA>
 
 <!ELEMENT clnull (cldata*)>
 <!ELEMENT clpoint (cldata*)>
 <!ELEMENT clrange (cldata*)>
+<!ELEMENT clpattern (cldata*)>
 <!ELEMENT clcont (cldata*)>
 
 <!ATTLIST clnull label CDATA #IMPLIED>
@@ -198,6 +199,9 @@
 <!ATTLIST clrange range CDATA #IMPLIED>
 <!ATTLIST clrange label CDATA #IMPLIED>
 
+<!ATTLIST clpattern pattern CDATA #REQUIRED>
+<!ATTLIST clpattern label CDATA #IMPLIED>
+
 <!ATTLIST clcont rmin CDATA #REQUIRED>
 <!ATTLIST clcont rmax CDATA #REQUIRED>
 <!ATTLIST clcont dmin CDATA #REQUIRED>

Modified: trunk/thuban/Thuban/Model/classification.py
===================================================================
--- trunk/thuban/Thuban/Model/classification.py	2006-06-29 13:50:16 UTC (rev 2687)
+++ trunk/thuban/Thuban/Model/classification.py	2006-06-30 12:27:20 UTC (rev 2688)
@@ -1,7 +1,8 @@
-# Copyright (c) 2001, 2003, 2005 by Intevation GmbH
+# Copyright (c) 2001, 2003, 2005, 2006 by Intevation GmbH
 # Authors:
 # Jonathan Coles <jonathan at intevation.de>
 # Jan-Oliver Wagner <jan at intevation.de> (2005)
+# Frank Koormann <frank at intevation.de> (2006)
 #
 # This program is free software under the GPL (>=v2)
 # Read the file COPYING coming with Thuban for details.
@@ -22,6 +23,7 @@
 """
   
 import copy, operator, types
+import re
 
 from Thuban import _
 
@@ -109,6 +111,11 @@
 
             is true.
 
+          'pattern'
+            
+            The tuple contains the compiled regular expression object and 
+            the original group object.
+
         The compiled classification is bound to
         self._compile_classification.
         """
@@ -118,6 +125,9 @@
                 if not compiled or compiled[-1][0] != "singletons":
                     compiled.append(("singletons", {}))
                 compiled[-1][1].setdefault(group.GetValue(), group)
+            elif isinstance(group, ClassGroupPattern):
+                pattern = re.compile(group.GetPattern())
+                compiled.append(("pattern", (pattern, group)))
             elif isinstance(group, ClassGroupRange):
                 left, min, max, right = group.GetRangeTuple()
                 if left == "[":
@@ -267,6 +277,13 @@
                     lfunc, min, max, rfunc, g = params
                     if lfunc(min, value) and rfunc(max, value):
                         return g
+                elif typ == "pattern":
+                    # TODO: make pattern more robust. The following chrashes 
+                    # if accidently be applied on non-string columns. 
+                    # Usually the UI prevents this.
+                    p, g = params
+                    if p.match(value):
+                        return g
 
         return self.GetDefaultGroup()
 
@@ -747,6 +764,64 @@
     def __repr__(self): 
         return "(" + str(self.__range) + ClassGroup.__repr__(self) + ")"
 
+class ClassGroupPattern(ClassGroup):
+    """A Group that is associated with a reg exp pattern."""
+
+    def __init__(self, pattern = "", props = None, label = "", group = None):
+        """Constructor.
+
+        pattern -- the associated pattern.
+
+        props   -- a ClassGroupProperites object. If props is None a default
+                   set of properties is created.
+
+        label   -- a label for this group.
+        """
+        ClassGroup.__init__(self, label, props, group)
+
+        self.SetPattern(pattern)
+
+    def __copy__(self):
+        return ClassGroupPattern(self.GetPattern(), 
+                                   self.GetProperties(), 
+                                   self.GetLabel())
+
+    def __deepcopy__(self, memo):
+        return ClassGroupPattern(self.GetPattern(), group = self)
+
+    def GetPattern(self):
+        """Return the associated pattern."""
+        return self.__pattern
+
+    def SetPattern(self, pattern):
+        """Associate this Group with the given pattern."""
+        self.__pattern = pattern
+
+    def Matches(self, pattern):
+        """Check if the given pattern matches the associated Group pattern."""
+
+        """Returns True if the value matches, False otherwise."""
+
+        if re.match(self.__pattern, pattern):
+            return True
+        else:
+            return False
+
+    def GetDisplayText(self):
+        label = self.GetLabel()
+
+        if label != "": return label
+
+        return str(self.GetPattern())
+
+    def __eq__(self, other):
+        return ClassGroup.__eq__(self, other) \
+            and isinstance(other, ClassGroupPattern) \
+            and self.__pattern == other.__pattern
+
+    def __repr__(self): 
+        return "(" + repr(self.__pattern) + ", " + ClassGroup.__repr__(self) + ")"
+
 class ClassGroupMap(ClassGroup):
     """Currently, this class is not used."""
 

Modified: trunk/thuban/Thuban/Model/load.py
===================================================================
--- trunk/thuban/Thuban/Model/load.py	2006-06-29 13:50:16 UTC (rev 2687)
+++ trunk/thuban/Thuban/Model/load.py	2006-06-30 12:27:20 UTC (rev 2688)
@@ -33,7 +33,8 @@
 from Thuban.Model.proj import Projection
 from Thuban.Model.range import Range
 from Thuban.Model.classification import Classification, \
-    ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap, \
+    ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, \
+    ClassGroupPattern, ClassGroupMap, \
     ClassGroupProperties
 from Thuban.Model.data import DerivedShapeStore, ShapefileStore
 from Thuban.Model.table import DBFTable
@@ -136,6 +137,7 @@
             'clnull'        : ("start_clnull",         "end_clnull"),
             'clpoint'       : ("start_clpoint",        "end_clpoint"),
             'clrange'       : ("start_clrange",        "end_clrange"),
+            'clpattern'     : ("start_clpattern",      "end_clpattern"),
             'cldata'        : ("start_cldata",         "end_cldata"),
             'table'         : ("start_table",          "end_table"),
             'labellayer'    : ("start_labellayer",     None),
@@ -615,6 +617,20 @@
         self.aLayer.GetClassification().AppendGroup(self.cl_group)
         del self.cl_group, self.cl_prop
 
+
+    def start_clpattern(self, name, qname, attrs):
+        pattern = attrs.get((None, 'pattern'), "")
+
+        self.cl_group = ClassGroupPattern(self.encode(pattern))
+        self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
+        self.cl_prop = ClassGroupProperties()
+
+    def end_clpattern(self, name, qname):
+        self.cl_group.SetProperties(self.cl_prop)
+        self.aLayer.GetClassification().AppendGroup(self.cl_group)
+        del self.cl_group, self.cl_prop
+
+
     def start_cldata(self, name, qname, attrs):
         self.cl_prop.SetLineColor(
             parse_color(attrs.get((None, 'stroke'), "None")))

Modified: trunk/thuban/Thuban/Model/save.py
===================================================================
--- trunk/thuban/Thuban/Model/save.py	2006-06-29 13:50:16 UTC (rev 2687)
+++ trunk/thuban/Thuban/Model/save.py	2006-06-30 12:27:20 UTC (rev 2688)
@@ -23,7 +23,8 @@
 from Thuban.Model.layer import Layer, RasterLayer
 
 from Thuban.Model.classification import \
-    ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap
+    ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, \
+    ClassGroupPattern, ClassGroupMap
 from Thuban.Model.transientdb import AutoTransientTable, TransientJoinedTable
 from Thuban.Model.table import DBFTable, FIELDTYPE_STRING
 from Thuban.Model.data import DerivedShapeStore, FileShapeStore, \
@@ -342,6 +343,11 @@
                 open_el  = 'clrange label="%s" range="%s"' \
                           % (self.encode(g.GetLabel()), str(g.GetRange()))
                 close_el = 'clrange'
+            elif isinstance(g, ClassGroupPattern):
+                open_el  = 'clpattern label="%s" pattern="%s"' \
+                          % (self.encode(g.GetLabel()), str(g.GetPattern()))
+                close_el = 'clpattern'
+
             else:
                 assert False, _("Unsupported group type in classification")
                 continue

Modified: trunk/thuban/Thuban/UI/classifier.py
===================================================================
--- trunk/thuban/Thuban/UI/classifier.py	2006-06-29 13:50:16 UTC (rev 2687)
+++ trunk/thuban/Thuban/UI/classifier.py	2006-06-30 12:27:20 UTC (rev 2688)
@@ -2,7 +2,7 @@
 # Authors:
 # Jan-Oliver Wagner <jan at intevation.de> (2003-2004)
 # Martin Schulze <joey at infodrom.org> (2004)
-# Frank Koormann <frank at intevation.de> (2003)
+# Frank Koormann <frank at intevation.de> (2003, 2006)
 # Bernhard Herzog <bh at intevation.de> (2003)
 # Jonathan Coles <jonathan at intevation.de> (2003)
 #
@@ -16,6 +16,7 @@
 # $Id$
 
 import copy
+import re
 
 from Thuban.Model.table import FIELDTYPE_INT, FIELDTYPE_DOUBLE, \
      FIELDTYPE_STRING
@@ -30,7 +31,7 @@
 from Thuban.Model.range import Range
 from Thuban.Model.classification import \
     Classification, ClassGroupDefault, \
-    ClassGroupSingleton, ClassGroupRange, ClassGroupMap, \
+    ClassGroupSingleton, ClassGroupPattern, ClassGroupRange, ClassGroupMap, \
     ClassGroupProperties
 
 from Thuban.Model.color import Transparent
@@ -84,7 +85,9 @@
         EVT_GRID_SELECT_CELL(self, self._OnSelectedCell)
         EVT_GRID_COL_SIZE(self, self._OnCellResize)
         EVT_GRID_ROW_SIZE(self, self._OnCellResize)
+        EVT_GRID_LABEL_RIGHT_CLICK(self, self._OnLabelRightClicked)
 
+
     #def GetCellAttr(self, row, col):
         #print "GetCellAttr ", row, col
         #wxGrid.GetCellAttr(self, row, col)
@@ -256,6 +259,58 @@
         self.FitInside()
         event.Skip()
 
+    def _OnLabelRightClicked(self, event):
+        """Process right click on label, raise popup for row labels."""
+        row, col = event.GetRow(), event.GetCol()
+        if col == -1:
+            self.labelPopup(event, row)
+
+    def labelPopup(self, event, row):
+        """Raise grid label popup."""
+        # check if row label is Pattern or Singleton
+        label = self.GetRowLabelValue(row)
+        if (label == _("Pattern") or label == _("Singleton")):
+            xe,ye = event.GetPosition()
+            x=self.GetRowSize(row)/2
+            menu = wxMenu()
+            patternID = wxNewId()
+            singletonID = wxNewId()
+
+            def _SetSingleton(event, self=self, row=row):
+                table = self.GetTable()
+                group = table.clazz.GetGroup(row - 1)
+                if not isinstance(group, ClassGroupSingleton):
+                    ngroup = ClassGroupSingleton(
+                                group.GetPattern(),
+                                group.GetProperties(),
+                                group.GetLabel()
+                                )
+                    table.SetClassGroup(row, ngroup)
+                         
+            def _SetPattern(event, self=self, row=row):
+                table = self.GetTable()
+                group = table.clazz.GetGroup(row - 1)
+                if not isinstance(group, ClassGroupPattern):
+                    try:
+                        re.compile(group.GetValue())
+                    except:
+                        pass
+                    else:
+                        ngroup = ClassGroupPattern(
+                                group.GetValue(),
+                                group.GetProperties(),
+                                group.GetLabel()
+                                )
+                        table.SetClassGroup(row, ngroup)
+
+            menu.Append(singletonID, _("Singleton"))
+            EVT_MENU(self, singletonID, _SetSingleton)
+            if self.GetTable().fieldType == FIELDTYPE_STRING:
+                menu.Append(patternID, _("Pattern"))
+                EVT_MENU(self, patternID, _SetPattern)
+            self.PopupMenu(menu, wxPoint(x,ye))
+            menu.Destroy()
+
 class ClassTable(wxPyGridTableBase):
     """Represents the underlying data structure for the grid."""
 
@@ -409,6 +464,7 @@
             group = self.clazz.GetGroup(row - 1)
             if isinstance(group, ClassGroupDefault):   return _("Default")
             if isinstance(group, ClassGroupSingleton): return _("Singleton")
+            if isinstance(group, ClassGroupPattern):   return _("Pattern")
             if isinstance(group, ClassGroupRange):     return _("Range")
             if isinstance(group, ClassGroupMap):       return _("Map")
 
@@ -472,6 +528,8 @@
             return _("DEFAULT")
         elif isinstance(group, ClassGroupSingleton):
             return group.GetValue()
+        elif isinstance(group, ClassGroupPattern):
+            return group.GetPattern()
         elif isinstance(group, ClassGroupRange):
             return group.GetRange()
 
@@ -483,13 +541,22 @@
            (string, number, or range)
 
         Returns a tuple (type, data) where type is 0 if data is
-        a singleton value, or 1 if is a range
+        a singleton value, 1 if is a range or 2 if it is a pattern.
         """
 
         type = self.fieldType
 
         if type == FIELDTYPE_STRING:
-            return (0, value)
+            # Approach: if we can compile the value as an expression, 
+            # make it a pattern, else a singleton.
+            # This is quite crude, however I don't have a better idea:
+            # How to distinct the singleton "Thuban" from the pattern "Thuban"?
+            try:
+                re.compile(value)
+            except:
+                return (0, value)
+            else:
+                return (2, value)
         elif type in (FIELDTYPE_INT, FIELDTYPE_DOUBLE):
             if type == FIELDTYPE_INT:
                 # the float call allows the user to enter 1.0 for 1
@@ -573,6 +640,11 @@
                             ngroup = ClassGroupRange(props = props)
                             changed = True
                         ngroup.SetRange(dataInfo[1])
+                    elif dataInfo[0] == 2:
+                        if not isinstance(group, ClassGroupPattern):
+                            ngroup = ClassGroupPattern(props = props)
+                            changed = True
+                        ngroup.SetPattern(dataInfo[1])
                     else:
                         assert False
                         pass

Modified: trunk/thuban/test/test_classification.py
===================================================================
--- trunk/thuban/test/test_classification.py	2006-06-29 13:50:16 UTC (rev 2687)
+++ trunk/thuban/test/test_classification.py	2006-06-30 12:27:20 UTC (rev 2688)
@@ -24,7 +24,7 @@
 from Thuban.Model.classification import \
     Classification, ClassGroup, \
     ClassGroupDefault, ClassGroupSingleton, ClassGroupRange,\
-    ClassGroupProperties
+    ClassGroupPattern, ClassGroupProperties
 from Thuban.Model.messages import CLASS_CHANGED
 
 from Thuban.Model.range import Range
@@ -246,6 +246,55 @@
         self.assertEqual(group, groupCopy)
 
 
+class TestClassGroupPattern(unittest.TestCase):
+
+    def test(self):
+        """Test ClassGroupPattern"""
+
+        defProps = ClassGroupProperties()
+        newProps = ClassGroupProperties()
+        newProps.SetLineColor(Color(.25, .5, .75))
+        newProps.SetLineWidth(5)
+        newProps.SetFill(Color(.12, .24, .36))
+
+        # test empty constructor
+        group = ClassGroupPattern()
+
+        self.assertEqual(group.GetPattern(), "")
+        self.assertEqual(group.GetProperties(), defProps)
+        self.assertEqual(group.GetLabel(), "")
+
+        # test SetProperties()/GetProperties()
+        group.SetProperties(newProps)
+        self.assertEqual(group.GetProperties(), newProps)
+
+        # test SetPattern()
+        group.SetPattern("A")
+        self.assertEqual(group.GetPattern(), "A")
+
+        # test Matches()
+        self.assertEqual(group.Matches("CBA"), False)
+        self.assertEqual(group.Matches("ABC"), True)
+
+        group.SetPattern("a")
+        self.assertNotEqual(group.GetPattern(), "A")
+
+        # test Matches()
+        self.assertEqual(group.Matches("Abc"), False)
+        self.assertEqual(group.Matches("aBC"), True)
+
+        group.SetPattern("hallo")
+        self.assertEqual(group.GetPattern(), "hallo")
+
+        # test Matches()
+        self.assertEqual(group.Matches("HALLO"), False)
+        self.assertEqual(group.Matches("hallo"), True)
+
+        # test copy
+        groupCopy = copy.copy(group)
+        self.assertEqual(group, groupCopy)
+
+
 class TestClassification(unittest.TestCase, support.SubscriberMixin):
 
     """Test cases for Classification"""
@@ -345,8 +394,20 @@
         self.assertEquals(self.clazz.FindGroup(10),
                           self.clazz.GetDefaultGroup())
 
-    def test_multiple_groups(self):
-        """Test Classification with multiple groups"""
+    def test_add_pattern(self):
+        """Test Classification.AppendGroup(ClassGroupPattern())"""
+        self.assertEquals(self.clazz.FindGroup(5),
+                          self.clazz.GetDefaultGroup())
+
+        s = ClassGroupPattern("A")
+        self.clazz.AppendGroup(s)
+        self.check_messages([(CLASS_CHANGED,)])
+        self.assertEquals(self.clazz.FindGroup("A"), s)
+        self.assertEquals(self.clazz.FindGroup("B"),
+                          self.clazz.GetDefaultGroup())
+
+    def test_multiple_groups_numerical(self):
+        """Test numerical Classification with multiple groups"""
         # two singletons matching 1 to test whether they're tested in
         # the right order. Use a non default fill on the second to make
         # it compare unequal to the first.
@@ -376,6 +437,28 @@
         self.assertEquals(self.clazz.FindGroup(10),
                           self.clazz.GetDefaultGroup())
 
+    def test_multiple_groups_textual(self):
+        """Test textual Classification with multiple groups"""
+        # A singleton and a pattern matching 'A' to test whether 
+        # they're tested in the right order. Use a non default fill 
+        # on the pattern to make it compare unequal to the first.
+        s = ClassGroupSingleton("A")
+        p = ClassGroupPattern("A")
+        p.GetProperties().SetFill(blue)
+        # Sanity check: are they considered different?
+        self.assertNotEqual(s, p)
+
+        self.clazz.AppendGroup(s)
+        self.clazz.AppendGroup(p)
+        self.check_messages([(CLASS_CHANGED,), (CLASS_CHANGED,)])
+
+        self.assertEquals(self.clazz.FindGroup("bca"),
+                          self.clazz.GetDefaultGroup())
+        self.assertEquals(self.clazz.FindGroup("A"), s)
+        self.assertEquals(self.clazz.FindGroup("Abc"), p)
+        self.assertEquals(self.clazz.FindGroup("abc"),
+                          self.clazz.GetDefaultGroup())
+
     def test_insert_group(self):
         """Test Classification.InsertGroup()"""
         s1 = ClassGroupSingleton(1)
@@ -424,8 +507,8 @@
         self.assertEquals(self.clazz.FindGroup(1), s1)
         self.check_messages([(CLASS_CHANGED,)])
 
-    def test_deepcopy(self):
-        """Test deepcopy(Classification())"""
+    def test_deepcopy_numerical(self):
+        """Test deepcopy(numerical Classification())"""
         self.clazz.AppendGroup(ClassGroupSingleton(5))
         self.clazz.AppendGroup(ClassGroupRange((-10, 10)))
 
@@ -436,7 +519,19 @@
         for i in range(clazz.GetNumGroups()):
             self.assertEquals(clazz.GetGroup(i), self.clazz.GetGroup(i))
 
+    def test_deepcopy_textual(self):
+        """Test deepcopy(textual Classification())"""
+        self.clazz.AppendGroup(ClassGroupSingleton("A"))
+        self.clazz.AppendGroup(ClassGroupPattern("B"))
 
+        clazz = copy.deepcopy(self.clazz)
+
+        self.assertEquals(clazz.GetNumGroups(), self.clazz.GetNumGroups())
+
+        for i in range(clazz.GetNumGroups()):
+            self.assertEquals(clazz.GetGroup(i), self.clazz.GetGroup(i))
+
+
     def test_iterator(self):
         """Test Classification iteration"""
         groups = [ClassGroupSingleton(5), ClassGroupSingleton(5),

Modified: trunk/thuban/test/test_layer.py
===================================================================
--- trunk/thuban/test/test_layer.py	2006-06-29 13:50:16 UTC (rev 2687)
+++ trunk/thuban/test/test_layer.py	2006-06-30 12:27:20 UTC (rev 2688)
@@ -32,7 +32,7 @@
 from Thuban.Model.proj import Projection
 from Thuban.Model.data import DerivedShapeStore
 from Thuban.Model.classification import Classification, ClassGroupSingleton, \
-     ClassGroupRange
+     ClassGroupRange, ClassGroupPattern
 from Thuban.Model.color import Color
 
 import Thuban.Model.resource
@@ -416,8 +416,8 @@
         # the layer.
         self.failIf(self.layer.WasModified())
 
-    def test_set_classification(self):
-        """Test Layer.SetClassification"""
+    def test_set_classification_numerical(self):
+        """Test Layer.SetClassification numerical"""
         classification = Classification()
         classification.AppendGroup(ClassGroupRange((0.0, 0.1)))
 
@@ -438,7 +438,29 @@
         self.check_messages([(self.layer, LAYER_CHANGED)])
         self.failUnless(self.layer.WasModified())
 
+    def test_set_classification_textual(self):
+        """Test Layer.SetClassification textual"""
+        classification = Classification()
+        classification.AppendGroup(ClassGroupPattern("I"))
 
+        self.layer.SetClassification(classification)
+        self.layer.SetClassificationColumn("POPYCOUN")
+
+        self.check_messages([(self.layer, LAYER_CHANGED),
+                             (self.layer, LAYER_CHANGED)])
+        self.failUnless(self.layer.WasModified())
+
+        self.clear_messages()
+        self.layer.UnsetModified()
+
+        # change only the classification column. This should issue a
+        # LAYER_CHANGED message as well.
+        self.layer.SetClassificationColumn("POPYREG")
+
+        self.check_messages([(self.layer, LAYER_CHANGED)])
+        self.failUnless(self.layer.WasModified())
+
+
     def test_tree_info(self):
         """Test Layer.TreeInfo"""
         self.assertEquals(self.layer.TreeInfo(),

Modified: trunk/thuban/test/test_load.py
===================================================================
--- trunk/thuban/test/test_load.py	2006-06-29 13:50:16 UTC (rev 2687)
+++ trunk/thuban/test/test_load.py	2006-06-30 12:27:20 UTC (rev 2688)
@@ -46,7 +46,7 @@
      LoadCancelled
 from Thuban.Model.color import Transparent
 from Thuban.Model.classification import ClassGroupProperties, ClassGroupRange,\
-    ClassGroupSingleton, ClassGroupDefault
+    ClassGroupSingleton, ClassGroupPattern, ClassGroupDefault
 from Thuban.Model.postgisdb import ConnectionError
 from Thuban.Model.table import DBFTable, MemoryTable, \
      FIELDTYPE_DOUBLE, FIELDTYPE_INT, FIELDTYPE_STRING, \
@@ -175,6 +175,9 @@
                 elif data[CLASSES][i][GROUP_TYPE] == "single":
                     g = ClassGroupSingleton(data[CLASSES][i][GROUP_DATA],
                                           props, data[CLASSES][i][GROUP_LABEL])
+                elif data[CLASSES][i][GROUP_TYPE] == "pattern":
+                    g = ClassGroupPattern(data[CLASSES][i][GROUP_DATA],
+                                          props, data[CLASSES][i][GROUP_LABEL])
 
                 eq(group, g)
 
@@ -456,6 +459,8 @@
         filename="../../Data/iceland/political.shp"/>
     <fileshapesource filetype="shapefile" id="D138504492"
         filename="../../Data/iceland/political.shp"/>
+    <fileshapesource filetype="shapefile" id="D123456789"
+        filename="../../Data/iceland/cultural_landmark-point.shp"/>
     <map title="Test Map">
         <projection name="">
             <parameter value="zone=26"/>
@@ -498,6 +503,19 @@
                 </clpoint>
             </classification>
         </layer>
+        <layer shapestore="D123456789" visible="true" title="My Layer 3">
+            <classification field="CLPTLABEL" field_type="string">
+                <clnull label="">
+                    <cldata stroke="#000000" size="5" stroke_width="2" fill="None"/>
+                </clnull>
+                <clpoint label="" value="FARM">
+                    <cldata stroke="#111111" size="5" stroke_width="1" fill="None"/>
+                </clpoint>
+                <clpattern label="" pattern="BUI">
+                    <cldata stroke="#000000" size="5" stroke_width="1" fill="None"/>
+                </clpattern>
+            </classification>
+        </layer>
     </map>
 </session>
 '''
@@ -529,7 +547,15 @@
                           ("range", (-1, 0), "",
                             ("#000000", 1, "None")),
                           ("single", -.5, "",
-                            ("#000000", 1, "None"))])]
+                            ("#000000", 1, "None"))]),
+                     ("My Layer 3", 2,
+                         [("default", (), "",
+                            ("#000000", 2, "None")),
+                          ("single", "FARM", "",
+                            ("#111111", 1, "None")),
+                          ("pattern", "BUI", "",
+                            ("#000000", 1, "None"))]),
+                    ]
 
         self.TestLayers(map.Layers(), expected)
 

Modified: trunk/thuban/test/test_save.py
===================================================================
--- trunk/thuban/test/test_save.py	2006-06-29 13:50:16 UTC (rev 2687)
+++ trunk/thuban/test/test_save.py	2006-06-30 12:27:20 UTC (rev 2688)
@@ -37,7 +37,7 @@
 from Thuban.Model.data import DerivedShapeStore, SHAPETYPE_ARC
 
 from Thuban.Model.classification import ClassGroupSingleton, ClassGroupRange, \
-    ClassGroupProperties
+    ClassGroupPattern, ClassGroupProperties
 
 from Thuban.Model.range import Range
 
@@ -342,6 +342,9 @@
             ClassGroupProperties(),
             internal_from_unicode(u'\xdcml\xe4uts'))) # Uemlaeuts
 
+        # Pattern
+        clazz.AppendGroup(ClassGroupPattern("BUI", ClassGroupProperties(),
+                                            "pattern")) 
 
         filename = self.temp_file_name("%s.thuban" % self.id())
         save_session(session, filename)
@@ -390,6 +393,9 @@
                              label="\xc3\x9cml\xc3\xa4uts">
                             <cldata fill="None" stroke="#000000" stroke_width="1"/>
                         </clpoint>
+                        <clpattern pattern="BUI" label="pattern">
+                            <cldata fill="None" stroke="#000000" stroke_width="1"/>
+                        </clpattern>
                     </classification>
                 </layer>
             </map>



More information about the Thuban-commits mailing list