[pygtk] Add floating-point support to gtk.gdk.Color



commit 15b7265a00d405d6115f45ba0b1f26019ef1a52b
Author: Paul Pogonyshev <pogonyshev gmx net>
Date:   Tue May 12 00:47:51 2009 +0300

    Add floating-point support to gtk.gdk.Color
    
    Make constructor accept floating-point arguments.  Add 'red_float',
    'green_float' and 'blue_float' read-write properties.  Test and
    document new features.  Part of bug #546019.
---
 docs/reference/pygtk-gdkcolor.xml |   50 +++++++++
 gtk/gdk-base-types.defs           |    5 +
 gtk/gdkcolor.override             |  220 +++++++++++++++++++++++++++++++------
 tests/test_color.py               |   21 ++++
 4 files changed, 260 insertions(+), 36 deletions(-)

diff --git a/docs/reference/pygtk-gdkcolor.xml b/docs/reference/pygtk-gdkcolor.xml
index 60d3ab5..82bd82b 100644
--- a/docs/reference/pygtk-gdkcolor.xml
+++ b/docs/reference/pygtk-gdkcolor.xml
@@ -51,6 +51,10 @@ linkend="constructor-gdkcolor">gtk.gdk.Color</link></methodname>
   <refsect1>
     <title>Attributes</title>
 
+    <note>
+      <para>Floating-point attributes are available in PyGTK 2.16 and above.</para>
+    </note>
+
     <blockquote role="properties">
       <informaltable pgwide="1" frame="none">
 	<tgroup cols="3">
@@ -84,11 +88,31 @@ linkend="constructor-gdkcolor">gtk.gdk.Color</link></methodname>
 	      <entry>The value of the blue component of the color</entry>
 	    </row>
 
+	    <row valign="top">
+	      <entry>"red_float"</entry>
+	      <entry>Read-Write</entry>
+	      <entry>The value of the red component of the color as a float in the range 0.0--1.0</entry>
+	    </row>
+
+	    <row valign="top">
+	      <entry>"green_float"</entry>
+	      <entry>Read-Write</entry>
+	      <entry>The value of the green component of the color as a float in the range 0.0--1.0</entry>
+	    </row>
+
+	    <row valign="top">
+	      <entry>"blue_float"</entry>
+	      <entry>Read-Write</entry>
+	      <entry>The value of the blue component of the color as a float in the range 0.0--1.0</entry>
+	    </row>
+
 	</tbody>
       </tgroup>
       </informaltable>
     </blockquote>
 
+    <para>For details on how assignment to <literal>*_float</literal> attributes work,
+      see <link linkend="constructor-gdkcolor">constructor documentation</link>.</para>
   </refsect1>
 
   <refsect1>
@@ -189,6 +213,12 @@ object</simpara></listitem>
       <para>Second form of the constructor is available in PyGTK 2.14 and above.</para>
     </note>
 
+    <note>
+      <para><parameter>red</parameter>, <parameter>green</parameter>
+      and <parameter>blue</parameter> can be floating-point numbers since PyGTK
+      2.16.</para>
+    </note>
+
     <para>Creates a new <link
 linkend="class-gdkcolor"><classname>gtk.gdk.Color</classname></link> object
 with the color component values specified by <parameter>red</parameter>,
@@ -200,6 +230,26 @@ allocated.</para>
     <para>Second form of the constructor is analogous to
       <link linkend="function-gdk--color-parse"><function>gtk.gdk.color_parse</function></link>.</para>
 
+    <para>Starting with PyGTK 2.16, <parameter>red</parameter>, <parameter>green</parameter> and
+      <parameter>blue</parameter> parameters can also be floating-point numbers in the
+      range 0.0--1.0.  Either all specified values must be integers or floats -- mixing is
+      not allowed as this would be too error-prone.  Values outside the valid range
+      0.0--1.0 are clamped, so e.g. 3.14 is the same as 1.0.</para>
+
+    <para>Note that internally values are still stored as integers, so values of
+      corresponding <literal>*_float</literal> attribute will not necessarily be the same
+      as the value used as argument for the constructor.  They will be as close as
+      permitted by 16-bit color component storage used by <classname>GdkColor</classname>
+      though.</para>
+
+    <para>All of the following expressions create a bright green color:</para>
+
+    <programlisting>
+  gtk.gdk.Color(0, 65535, 0)
+  gtk.gdk.Color(green=1.0)
+  gtk.gdk.Color('#0f0')
+    </programlisting>
+
   </refsect1>
   
     <refsect1>
diff --git a/gtk/gdk-base-types.defs b/gtk/gdk-base-types.defs
index 89d0c94..f6799bf 100644
--- a/gtk/gdk-base-types.defs
+++ b/gtk/gdk-base-types.defs
@@ -228,6 +228,11 @@
     '("guint16" "red")
     '("guint16" "green")
     '("guint16" "blue")
+    ;; Fake.  Only defined since codegen currently doesn't support
+    ;; defining attributes in '.override' files.
+    '("gfloat" "red_float")
+    '("gfloat" "green_float")
+    '("gfloat" "blue_float")
   )
 )
 
diff --git a/gtk/gdkcolor.override b/gtk/gdkcolor.override
index 3cb5329..f46b878 100644
--- a/gtk/gdkcolor.override
+++ b/gtk/gdkcolor.override
@@ -32,25 +32,19 @@ _wrap_gdk_color_new(PyGBoxed *self,
 {
     static char *kwlist1[] = {"red", "green", "blue", "pixel", NULL };
     static char *kwlist2[] = { "spec", NULL };
-    int red = 0, green = 0, blue = 0;
-    unsigned int pixel = 0;
-    const char *spec;
+    PyObject *red = Py_None, *green = Py_None, *blue = Py_None;
+    const char *spec = NULL;
     GdkColor colour;
 
-    if (PyArg_ParseTupleAndKeywords(args, kwargs, "|iiik:gdk.Color", kwlist1,
-                                    &red, &green, &blue, &pixel)) {
-        colour.red = red;
-        colour.green = green;
-        colour.blue = blue;
-        colour.pixel = pixel;
-        goto success;
-    }
+    /* Note: this constructor has become quite complicated, because it
+     * is heavily overloaded.  Additionally, we try to optimize a
+     * little. */
 
-    PyErr_Clear();
-    
-    if (PyArg_ParseTupleAndKeywords(args, kwargs, "s:color_parse|gdk.Color", kwlist2,
+    if (PyArg_ParseTupleAndKeywords(args, kwargs, "|s:gdk.Color", kwlist2,
                                     &spec)) {
-        if (!gdk_color_parse(spec, &colour)) {
+        if (!spec)
+            memset(&colour, 0, sizeof(colour));
+        else if (!gdk_color_parse(spec, &colour)) {
             PyErr_SetString(PyExc_ValueError,
                             "unable to parse colour specification");
             return -1;
@@ -60,6 +54,77 @@ _wrap_gdk_color_new(PyGBoxed *self,
     }
 
     PyErr_Clear();
+
+    if (PyArg_ParseTupleAndKeywords(args, kwargs, "|OOOk:gdk.Color", kwlist1,
+                                    &red, &green, &blue, &colour.pixel)) {
+        /* We don't allow mixing floats and non-floats as that is too
+         * error-prone.  All non-floats are deemed integers in case
+         * they have __int__() method. */
+        int have_floats = 0;
+        int have_nonfloats = 0;
+
+        if (red == Py_None)
+            colour.red = 0;
+        else {
+            if (PyFloat_Check(red)) {
+                have_floats = 1;
+                colour.red = MIN(MAX(0.0, PyFloat_AsDouble(red)), 1.0) * 65535.0;
+            }
+            else {
+                have_nonfloats = 1;
+                colour.red = PyInt_AsLong(red);
+            }
+        }
+
+        if (PyErr_Occurred())
+            return -1;
+
+        if (green == Py_None)
+            colour.green = 0;
+        else {
+            if (PyFloat_Check(green)) {
+                if (have_nonfloats)
+                    goto mixed_types_error;
+                have_floats = 1;
+                colour.green = MIN(MAX(0.0, PyFloat_AsDouble(green)), 1.0) * 65535.0;
+            }
+            else {
+                if (have_floats)
+                    goto mixed_types_error;
+                have_nonfloats = 1;
+                colour.green = PyInt_AsLong(green);
+            }
+        }
+
+        if (PyErr_Occurred())
+            return -1;
+
+        if (blue == Py_None)
+            colour.blue = 0;
+        else {
+            if (PyFloat_Check(blue)) {
+                if (have_nonfloats)
+                    goto mixed_types_error;
+                colour.blue = MIN(MAX(0.0, PyFloat_AsDouble(blue)), 1.0) * 65535.0;
+            }
+            else {
+                if (have_floats)
+                    goto mixed_types_error;
+                colour.blue = PyInt_AsLong(blue);
+            }
+        }
+
+        if (PyErr_Occurred())
+            return -1;
+
+        goto success;
+
+    mixed_types_error:
+        PyErr_SetString(PyExc_TypeError, "arguments must either be all integers or all floats");
+        return -1;
+    }
+
+    PyErr_Clear();
     PyErr_SetString(PyExc_TypeError, "Usage:\n"
                     "  gtk.gdk.Color(red, green, blue, pixel)  [all are optional]\n"
                     "  gtk.gdk.Color(spec)                     [see gtk.gdk.color_parse()]");
@@ -72,35 +137,118 @@ _wrap_gdk_color_new(PyGBoxed *self,
     
     return 0;
 }
+
 %%
-override-slot GdkColor.tp_setattr
+override-attr GdkColor.red
 static int
-_wrap_gdk_color_tp_setattr(PyGBoxed *self, char *attr, PyObject *value)
+_wrap_gdk_color__set_red(PyObject *self, PyObject *value, void *closure)
 {
-    if (value == NULL) {
-        PyErr_SetString(PyExc_TypeError, "can't delete attributes");
+    long red = PyInt_AsLong(value);
+    if (red == -1 && PyErr_Occurred())
         return -1;
+    else {
+        pyg_boxed_get(self, GdkColor)->red = red;
+        return 0;
     }
+}
+%%
+override-attr GdkColor.blue
+static int
+_wrap_gdk_color__set_blue(PyObject *self, PyObject *value, void *closure)
+{
+    long blue = PyInt_AsLong(value);
+    if (blue == -1 && PyErr_Occurred())
+        return -1;
+    else {
+        pyg_boxed_get(self, GdkColor)->blue = blue;
+        return 0;
+    }
+}
+%%
+override-attr GdkColor.green
+static int
+_wrap_gdk_color__set_green(PyObject *self, PyObject *value, void *closure)
+{
+    long green = PyInt_AsLong(value);
+    if (green == -1 && PyErr_Occurred())
+        return -1;
+    else {
+        pyg_boxed_get(self, GdkColor)->green = green;
+        return 0;
+    }
+}
+%%
+override-attr GdkColor.pixel
+static int
+_wrap_gdk_color__set_pixel(PyObject *self, PyObject *value, void *closure)
+{
+    long pixel = PyInt_AsLong(value);
+    if (pixel == -1 && PyErr_Occurred())
+        return -1;
+    else {
+        pyg_boxed_get(self, GdkColor)->pixel = pixel;
+        return 0;
+    }
+}
+%%
+override-attr GdkColor.red_float
 
-    if (PyInt_Check(value)) {
-        int i = PyInt_AsLong(value);
-        if (!strcmp(attr, "red")) {
-            pyg_boxed_get(self, GdkColor)->red = i;
-            return 0;
-        } else if (!strcmp(attr, "green")) {
-            pyg_boxed_get(self, GdkColor)->green = i;       
-            return 0;
-        } else if (!strcmp(attr, "blue")) {
-            pyg_boxed_get(self, GdkColor)->blue = i;
-            return 0;
-        } else if (!strcmp(attr, "pixel")) {
-            pyg_boxed_get(self, GdkColor)->pixel = i;
-            return 0;
-        } 
+static PyObject *
+_wrap_gdk_color__get_red_float(PyObject *self, void *closure)
+{
+    return PyFloat_FromDouble(pyg_boxed_get(self, GdkColor)->red / 65535.0);
+}
+
+static int
+_wrap_gdk_color__set_red_float(PyObject *self, PyObject *value, void *closure)
+{
+    double red = PyFloat_AsDouble(value);
+    if (red == -1 && PyErr_Occurred())
+        return -1;
+    else {
+        pyg_boxed_get(self, GdkColor)->red = MIN(MAX(0.0, red), 1.0) * 65535.0;
+        return 0;
     }
+}
+%%
+override-attr GdkColor.green_float
 
-    PyErr_SetString(PyExc_AttributeError, "could not write attribute");
-    return -1;
+static PyObject *
+_wrap_gdk_color__get_green_float(PyObject *self, void *closure)
+{
+    return PyFloat_FromDouble(pyg_boxed_get(self, GdkColor)->green / 65535.0);
+}
+
+static int
+_wrap_gdk_color__set_green_float(PyObject *self, PyObject *value, void *closure)
+{
+    double green = PyFloat_AsDouble(value);
+    if (green == -1 && PyErr_Occurred())
+        return -1;
+    else {
+        pyg_boxed_get(self, GdkColor)->green = MIN(MAX(0.0, green), 1.0) * 65535.0;
+        return 0;
+    }
+}
+%%
+override-attr GdkColor.blue_float
+
+static PyObject *
+_wrap_gdk_color__get_blue_float(PyObject *self, void *closure)
+{
+    return PyFloat_FromDouble(pyg_boxed_get(self, GdkColor)->blue / 65535.0);
+}
+
+static int
+_wrap_gdk_color__set_blue_float(PyObject *self, PyObject *value, void *closure)
+{
+    double blue = PyFloat_AsDouble(value);
+    if (blue == -1 && PyErr_Occurred())
+        return -1;
+    else {
+        pyg_boxed_get(self, GdkColor)->blue = MIN(MAX(0.0, blue), 1.0) * 65535.0;
+        return 0;
+    }
 }
 %%
 override gdk_color_parse kwargs
diff --git a/tests/test_color.py b/tests/test_color.py
index 72ad669..d8484ae 100644
--- a/tests/test_color.py
+++ b/tests/test_color.py
@@ -15,6 +15,14 @@ class Tests(unittest.TestCase):
         self.assertEqual(c.green, 2)
         self.assertEqual(c.blue, 3)
 
+        c = gtk.gdk.Color(1.0, 0.7, 0.2)
+        self.assertAlmostEqual(c.red_float, 1.0, 4)
+        self.assertAlmostEqual(c.green_float, 0.7, 4)
+        self.assertAlmostEqual(c.blue_float, 0.2, 4)
+
+        # Mixing integers and floats is not allowed.
+        self.assertRaises(TypeError, lambda: gtk.gdk.Color(0, 0.5))
+
         c = gtk.gdk.Color(pixel=0xffff)
         self.assertEqual(c.pixel, 0xffff)
 
@@ -36,6 +44,19 @@ class Tests(unittest.TestCase):
 
         self.assertRaises(TypeError, lambda: gtk.gdk.Color([]))
 
+    def test_float_attribute(self):
+        c = gtk.gdk.Color(0, 10000, 65535)
+        self.assertAlmostEqual(c.red_float, 0.0)
+        self.assertAlmostEqual(c.green_float, 10000.0 / 65535.0)
+        self.assertAlmostEqual(c.blue_float, 1.0)
+
+        c.red_float = 0.57
+        self.assert_(c.red == int(0.57 * 65535) or c.red == int(0.57 * 65535) + 1)
+        self.assertAlmostEqual(c.red_float, 0.57, 4)
+
+        c.green = 12345
+        self.assertAlmostEqual(c.green_float, 12345.0 / 65535.0)
+
     def test_equal(self):
         self.assertEqual(gtk.gdk.Color(0, 0, 0), gtk.gdk.Color(0, 0, 0))
         self.assertEqual(gtk.gdk.Color(100, 200, 300), gtk.gdk.Color(100, 200, 300))



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]