[Sugar-devel] [PATCH clock] Port to Cairo

Manuel Quiñones manuq at laptop.org
Tue Dec 27 11:26:44 EST 2011


This will ease the port to GTK+ 3, and it gains in nicer graphics,
sharp edges now have antialiasing.

Signed-off-by: Manuel Quiñones <manuq at laptop.org>
---
 clock.py |  182 ++++++++++++++++++++++++++++++++++----------------------------
 1 files changed, 100 insertions(+), 82 deletions(-)

diff --git a/clock.py b/clock.py
index 1893b77..7635b2e 100755
--- a/clock.py
+++ b/clock.py
@@ -70,6 +70,9 @@ import pygtk
 import gtk
 from gtk import gdk
 import pango
+import cairo
+import pangocairo
+import rsvg
 
 OLD_TOOLBAR = False
 try:
@@ -82,7 +85,6 @@ except ImportError:
 import math
 from datetime import datetime
 import threading
-import gc
 import re
 
 from pgettext import pgettext as _
@@ -90,6 +92,7 @@ from pgettext import pgettext as _
 from sugar.activity import activity
 from sugar.graphics.toggletoolbutton import ToggleToolButton
 from sugar.graphics.radiotoolbutton import RadioToolButton
+from sugar.graphics import style
 
 from speaker import Speaker
 from timewriter import TimeWriter
@@ -477,32 +480,29 @@ class ClockFace(gtk.DrawingArea):
         # The display mode of the clock
         self._mode = _MODE_SIMPLE_CLOCK
 
-        # SVG Background cache
-        self._cache_pixbuf = None
-        self._radius = -1
+        # SVG Background handle
+        self._svg_handle = None
 
-        # The graphic context used for drawings
-        self._gc = None
+        self._radius = -1
         self._line_width = 2
 
         # Color codes (approved colors for XO screen:
         # http://wiki.laptop.org/go/XO_colors)
-        colormap = self.get_colormap()
 
         # XO Medium Blue
-        self._COLOR_HOURS = colormap.alloc_color("#005FE4")
+        self._COLOR_HOURS = "#005FE4"
 
         # XO Medium Green
-        self._COLOR_MINUTES = colormap.alloc_color("#00B20D")
+        self._COLOR_MINUTES = "#00B20D"
 
         # XO Medium Red
-        self._COLOR_SECONDS = colormap.alloc_color("#E6000A")
+        self._COLOR_SECONDS = "#E6000A"
 
         # White
-        self._COLOR_WHITE = colormap.alloc_color("#FFFFFF")
+        self._COLOR_WHITE = "#FFFFFF"
 
         # Black
-        self._COLOR_BLACK = colormap.alloc_color("#000000")
+        self._COLOR_BLACK = "#000000"
 
         # gtk.Widget signals
         self.connect("expose-event", self._expose_cb)
@@ -538,10 +538,8 @@ class ClockFace(gtk.DrawingArea):
         self._height = allocation.height
         self._line_width = int(self._radius / 150)
 
-        # Reload the cached pixbuf
-        self._cache_pixbuf = gdk.pixbuf_new_from_file_at_size("clock.svg",
-          2 * self._radius, 2 * self._radius)
-        gc.collect()  # Reclaim memory from old pixbuf
+        # Reload the svg handle
+        self._svg_handle = rsvg.Handle(file="clock.svg")
 
         self.initialized = True
 
@@ -558,8 +556,6 @@ class ClockFace(gtk.DrawingArea):
             self.queue_resize()
 
         if self._active:
-            self._gc = self.window.new_gc()
-
             if self._mode == _MODE_NICE_CLOCK:
                 self._draw_nice_clock()
             elif self._mode == _MODE_SIMPLE_CLOCK:
@@ -608,32 +604,34 @@ class ClockFace(gtk.DrawingArea):
         seconds_length = 2 * self._radius / 60 * self._time.second
 
         # Fill background
-        self._gc.set_line_attributes(self._line_width, gdk.LINE_SOLID, \
-                gdk.CAP_BUTT, gdk.JOIN_BEVEL)
-        self._gc.set_foreground(self._COLOR_WHITE)
-        self.window.draw_rectangle(self._gc, True, \
-                int(self._center_x - 1.1 * self._radius), \
-                int(self._center_y - 0.8 * self._radius), \
-                int(2.2 * self._radius), \
-                int(0.55 * self._radius))
+        cr = self.window.cairo_create()
+        cr.set_source_rgba(*style.Color(self._COLOR_WHITE).get_rgba())
+        cr.rectangle(int(self._center_x - 1.1 * self._radius),
+                     int(self._center_y - 0.8 * self._radius),
+                     int(2.2 * self._radius),
+                     int(0.55 * self._radius))
+        cr.fill()
 
         h = int(0.15 * self._radius)
         x = int(self._center_x - self._radius)
 
         # Hours scale
-        self._gc.set_foreground(self._COLOR_HOURS)
+        cr.set_source_rgba(*style.Color(self._COLOR_HOURS).get_rgba())
         y = int(self._center_y - 0.75 * self._radius)
-        self.window.draw_rectangle(self._gc, True, x, y, hours_length, h)
+        cr.rectangle(x, y, hours_length, h)
+        cr.fill()
 
         # Minutes scale
-        self._gc.set_foreground(self._COLOR_MINUTES)
+        cr.set_source_rgba(*style.Color(self._COLOR_MINUTES).get_rgba())
         y = int(self._center_y - 0.60 * self._radius)
-        self.window.draw_rectangle(self._gc, True, x, y, minutes_length, h)
+        cr.rectangle(x, y, minutes_length, h)
+        cr.fill()
 
         # Seconds scale
-        self._gc.set_foreground(self._COLOR_SECONDS)
+        cr.set_source_rgba(*style.Color(self._COLOR_SECONDS).get_rgba())
         y = int(self._center_y - 0.45 * self._radius)
-        self.window.draw_rectangle(self._gc, True, x, y, seconds_length, h)
+        cr.rectangle(x, y, seconds_length, h)
+        cr.fill()
 
     def _draw_time(self):
         """Draw the time in colors (digital display).
@@ -655,9 +653,16 @@ class ClockFace(gtk.DrawingArea):
         markup_time = self._time.strftime(markup)
         #markup_time = time.strftime(markup)
 
-        self._gc.set_foreground(self._COLOR_BLACK)
+        cr = self.window.cairo_create()
+        cr = pangocairo.CairoContext(cr)
+        cr.set_source_rgba(*style.Color(self._COLOR_BLACK).get_rgba())
+        pango_layout = cr.create_layout()
         d = int(self._center_y + 0.3 * self._radius)
-        self._draw_markup(self._center_x, d, markup_time)
+        pango_layout.set_markup(markup_time)
+        dx, dy = pango_layout.get_pixel_size()
+        pango_layout.set_alignment(pango.ALIGN_CENTER)
+        cr.translate(self._center_x - dx / 2.0, d - dy / 2.0)
+        cr.show_layout(pango_layout)
 
     def _draw_simple_clock(self):
         """Draw the simple clock variants.
@@ -671,22 +676,17 @@ class ClockFace(gtk.DrawingArea):
         The simple clock background is a white disk, with hours and minutes
         ticks, and the hour numbers.
         """
+        cr = self.window.cairo_create()
+        cr.set_line_width(4 * self._line_width)
+
         # Simple clock background
-        self._gc.set_foreground(self._COLOR_WHITE)
-        x_delta = self._center_x - self._radius
-        y_delta = self._center_y - self._radius
-
-        self.window.draw_arc(self._gc, True, x_delta, y_delta,
-          2 * self._radius, 2 * self._radius, 0, 360 * 64)
-        self._gc.set_foreground(self.get_style().fg[gtk.STATE_NORMAL])
-        self._gc.set_line_attributes(4 * self._line_width,
-          gdk.LINE_SOLID, gdk.CAP_ROUND, gdk.JOIN_ROUND)
-        self.window.draw_arc(self._gc, False, x_delta, y_delta,
-          2 * self._radius, 2 * self._radius, 0, 360 * 64)
+        cr.set_source_rgba(*style.Color(self._COLOR_WHITE).get_rgba())
+        cr.arc(self._width / 2, self._height / 2, self._radius, 0, 2 * math.pi)
+        cr.fill_preserve()
+        cr.set_source_rgba(*style.Color(self._COLOR_BLACK).get_rgba())
+        cr.stroke()
 
         # Clock ticks
-        self._gc.set_line_attributes(4 * self._line_width,
-          gdk.LINE_SOLID, gdk.CAP_ROUND, gdk.JOIN_ROUND)
         for i in xrange(60):
             if i % 15 == 0:
                 inset = 0.175 * self._radius
@@ -697,22 +697,28 @@ class ClockFace(gtk.DrawingArea):
 
             cos = math.cos(i * math.pi / 30.0)
             sin = math.sin(i * math.pi / 30.0)
-            self.window.draw_line(self._gc,
-              int(self._center_x + (self._radius - inset) * cos),
-              int(self._center_y + (self._radius - inset) * sin),
-              int(self._center_x + self._radius * cos),
-              int(self._center_y + self._radius * sin))
+            cr.move_to(int(self._center_x + (self._radius - inset) * cos),
+                       int(self._center_y + (self._radius - inset) * sin))
+            cr.line_to(int(self._center_x + self._radius * cos),
+                       int(self._center_y + self._radius * sin))
+            cr.stroke()
 
     def _draw_nice_background(self):
         """Draw the nice clock background.
 
         The background has been loaded from the clock.svg file to a
-        pixbuf, and we just draw this pixbuf onto the pixmap where we
-        will be drawing the hands.
-        """
-        # We draw the background from the SVG pixbuf
-        self.window.draw_pixbuf(None, self._cache_pixbuf,
-          0, 0, self._center_x - self._radius, self._center_y - self._radius)
+        rsvg handle, and we just transform this handle and render it
+        with cairo.
+        """
+        # We transform the background SVG
+        cr = self.window.cairo_create()
+        scale_x = self._radius * 2.0 / self._svg_handle.props.width
+        scale_y = self._radius * 2.0 / self._svg_handle.props.height
+        matrix = cairo.Matrix(xx=scale_x, yy=scale_y,
+                              x0=self._center_x - self._radius,
+                              y0=self._center_y - self._radius)
+        cr.transform(matrix)
+        self._svg_handle.render_cairo(cr)
 
     def _draw_nice_clock(self):
         """Draw the nice clock.
@@ -727,44 +733,50 @@ class ClockFace(gtk.DrawingArea):
         minutes = self._time.minute
         seconds = self._time.second
 
+        cr = self.window.cairo_create()
+        cr.set_line_cap(cairo.LINE_CAP_ROUND)
+
         # Hour hand:
         # The hour hand is rotated 30 degrees (pi/6 r) per hour +
         # 1/2 a degree (pi/360) per minute
-        self._gc.set_foreground(self._COLOR_HOURS)
-        self._gc.set_line_attributes(8 * self._line_width,
-          gdk.LINE_SOLID, gdk.CAP_ROUND, gdk.JOIN_ROUND)
-        self.window.draw_line(self._gc, self._center_x, self._center_y,
-          int(self._center_x + self._radius * 0.5 *
-          math.sin(math.pi / 6 * hours + math.pi / 360 * minutes)),
-          int(self._center_y + self._radius * 0.5 *
-          - math.cos(math.pi / 6 * hours + math.pi / 360 * minutes)))
+        cr.set_source_rgba(*style.Color(self._COLOR_HOURS).get_rgba())
+        cr.set_line_width(8 * self._line_width)
+        cr.move_to(self._center_x, self._center_y)
+        cr.line_to(int(self._center_x + self._radius * 0.5 *
+            math.sin(math.pi / 6 * hours + math.pi / 360 * minutes)),
+            int(self._center_y + self._radius * 0.5 *
+            - math.cos(math.pi / 6 * hours + math.pi / 360 * minutes)))
+        cr.stroke()
 
         # Minute hand:
         # The minute hand is rotated 6 degrees (pi/30 r) per minute
-        self._gc.set_foreground(self._COLOR_MINUTES)
-        self._gc.set_line_attributes(6 * self._line_width,
-          gdk.LINE_SOLID, gdk.CAP_ROUND, gdk.JOIN_ROUND)
-        self.window.draw_line(self._gc, self._center_x, self._center_y,
-                int(self._center_x + self._radius * 0.8 *
+        cr.set_source_rgba(*style.Color(self._COLOR_MINUTES).get_rgba())
+        cr.set_line_width(6 * self._line_width)
+        cr.move_to(self._center_x, self._center_y)
+        cr.line_to(int(self._center_x + self._radius * 0.8 *
                 math.sin(math.pi / 30 * minutes)),
-                int(self._center_y + self._radius * 0.8 *
+                   int(self._center_y + self._radius * 0.8 *
                 - math.cos(math.pi / 30 * minutes)))
+        cr.stroke()
 
         # Seconds hand:
         # Operates identically to the minute hand
-        self._gc.set_foreground(self._COLOR_SECONDS)
-        self._gc.set_line_attributes(2 * self._line_width,
-          gdk.LINE_SOLID, gdk.CAP_ROUND, gdk.JOIN_ROUND)
-        self.window.draw_line(self._gc, self._center_x, self._center_y,
-                int(self._center_x + self._radius * 0.7 *
+        cr.set_source_rgba(*style.Color(self._COLOR_SECONDS).get_rgba())
+        cr.set_line_width(2 * self._line_width)
+        cr.move_to(self._center_x, self._center_y)
+        cr.line_to(int(self._center_x + self._radius * 0.7 *
                 math.sin(math.pi / 30 * seconds)),
                 int(self._center_y + self._radius * 0.7 *
                 - math.cos(math.pi / 30 * seconds)))
+        cr.stroke()
 
     def _draw_numbers(self):
         """Draw the numbers of the hours.
         """
-        self._gc.set_foreground(self._COLOR_HOURS)
+        cr = self.window.cairo_create()
+        cr = pangocairo.CairoContext(cr)
+        cr.set_source_rgba(*style.Color(self._COLOR_HOURS).get_rgba())
+        pango_layout = cr.create_layout()
 
         for i in xrange(12):
             # TRANS: The format of the font used to print hour
@@ -772,10 +784,16 @@ class ClockFace(gtk.DrawingArea):
             hour_number = _("Hour Number",
               '<markup><span lang="en" ' +
               'font_desc="Sans Bold 20">%d</span></markup>') % (i + 1)
-            self._draw_markup(self._center_x + 0.75 * \
-            self._radius * math.cos((i - 2) * math.pi / 6.0), \
-            self._center_y + 0.75 * self._radius * \
-            math.sin((i - 2) * math.pi / 6.0), hour_number)
+            cr.save()
+            dx, dy = pango_layout.get_pixel_size()
+            cr.translate(- dx / 2.0 + self._center_x + 0.75 *
+                self._radius * math.cos((i - 2) * math.pi / 6.0),
+                - dy / 2.0 + self._center_y + 0.75 * self._radius *
+                math.sin((i - 2) * math.pi / 6.0))
+            pango_layout.set_markup(hour_number)
+            cr.update_layout(pango_layout)
+            cr.show_layout(pango_layout)
+            cr.restore()
 
     def _redraw_canvas(self):
         """Force a redraw of the clock on the screen.
-- 
1.7.7.4



More information about the Sugar-devel mailing list