Activity Team/Sample code/Ruler

Ruler is a simple activity that uses Cairo graphics. It is also an example of how to use both the new (0.86+) and old (pre-0,86) toolbar, and simple Journal interaction. Not the most beautiful code, but it works.

Misc. imports. Note that we catch an exception if the new 0.86 toolbars aren't available.

import pygtk
pygtk.require('2.0')
import gtk
import gobject
import cairo
import os.path

import sugar
from sugar.activity import activity
try: # 0.86+ toolbar widgets
    from sugar.bundle.activitybundle import ActivityBundle
    from sugar.activity.widgets import ActivityToolbarButton
    from sugar.activity.widgets import StopButton
    from sugar.graphics.toolbarbox import ToolbarBox
    from sugar.graphics.toolbarbox import ToolbarButton
except ImportError:
    pass
from sugar.graphics.toolbutton import ToolButton
from sugar.graphics.menuitem import MenuItem
from sugar.graphics.icon import Icon
from sugar.datastore import datastore
try:
    from sugar.graphics import style
    GRID_CELL_SIZE = style.GRID_CELL_SIZE
except:
    GRID_CELL_SIZE = 0

import logging
_logger = logging.getLogger("ruler-activity")

from gettext import gettext as _

import util
import show_rulers
import show_grids
import show_checkers
import show_angles

The Cairo canvas event handler. All the drawing happens in here.


# Create a GTK+ widget on which we will draw using Cairo
class MyCanvas(gtk.DrawingArea):

    def __init__(self):
        gtk.DrawingArea.__init__(self)
        self._draw_ruler = False
        self._object = None
        self.connect('expose-event', self.__expose_event_cb)
        self.dpi = 200

    def __expose_event_cb(self, drawing_area, event):
        cr = self.window.cairo_create()

        if self._draw_ruler:
            # draw lines to create a star
            self._object.draw(cr,self._dpi)

        # Restrict Cairo to the exposed area; avoid extra work
        cr.rectangle(event.area.x, event.area.y,
                     event.area.width, event.area.height)
        cr.clip()

    def add_a_ruler(self,r):
        self._draw_ruler = True
        self._object = r
        self.queue_draw()     

    def get_dpi(self):
        return self._dpi

    def set_dpi(self, dpi):
        self._dpi = dpi

The main activity.

#
# Sugar activity
#
class RulerActivity(activity.Activity):

    def __init__(self, handle):
        super(RulerActivity,self).__init__(handle)

        _font = 'helvetica 12'
        _font_bold = 'helvetica bold 12'

        #
        # We need a canvas
        #
        self._canvas = MyCanvas()
        self.set_canvas(self._canvas)
        self._canvas.show()

How to find out the size of the canvas.


        _width = gtk.gdk.screen_width()
        _height = gtk.gdk.screen_height()-GRID_CELL_SIZE

Check to see if there is metadata available. Since we are not using file data, we don't need to use read_file

        # Read the dpi from the Journal
        try:
            dpi = self.metadata['dpi']
            _logger.debug("Read dpi: " + str(dpi))
            self._canvas.set_dpi(int(dpi))
        except:
            if os.path.exists('/sys/power/olpc-pm'):
                self._canvas.set_dpi(200) # OLPC XO
            else:
                self._canvas.set_dpi(100) # Just a guess

Initialize the subclasses used for drawing.


        # Create instances of our graphics
        self._r = show_rulers.ScreenOfRulers(_font,_font_bold,_width,_height)
        self._gcm = show_grids.ScreenGrid_cm(_font,_font_bold,_width,_height)
        self._gmm = show_grids.ScreenGrid_mm(_font,_font_bold,_width,_height)
        self._a90 = show_angles.Angles90(_font,_font_bold,_width,_height)
        self._a360 = show_angles.Angles360(_font,_font_bold,_width,_height)
        self._c = show_checkers.ScreenOfCircles(_font,_font_bold,_width,_height)

        # start with a ruler
        self._current = self._r
        self._canvas.add_a_ruler(self._current)

        # other settings
        self._grids_mode = "cm"
        self._angles_mode = "90"

We'll get a NameError if the new toolbars are not available.

        #
        # We need some toolbars
        #
        try:
            # Use 0.86 toolbar design
            toolbar_box = ToolbarBox()

            # Buttons added to the Activity toolbar
            activity_button = ActivityToolbarButton(self)
            toolbar_box.toolbar.insert(activity_button, 0)
            activity_button.show()

            # Show rulers
            self.rulers = ToolButton( "ruler" )
            self.rulers.set_tooltip(_('Ruler'))
            self.rulers.props.sensitive = True
            self.rulers.connect('clicked', self._rulers_cb)
            toolbar_box.toolbar.insert(self.rulers, -1)
            self.rulers.show()

            # Show grids
            self.grids = ToolButton( "grid-a" )
            self.grids.set_tooltip(_('Grid'))
            self.grids.props.sensitive = True
            self.grids.connect('clicked', self._grids_cb)
            toolbar_box.toolbar.insert(self.grids, -1)
            self.grids.show()

            # Show angles
            self.angles = ToolButton( "angles-90" )
            self.angles.set_tooltip(_('Angles'))
            self.angles.props.sensitive = True
            self.angles.connect('clicked', self._angles_cb)
            toolbar_box.toolbar.insert(self.angles, -1)
            self.angles.show()

            # Show checker
            self.checker = ToolButton( "checker" )
            self.checker.set_tooltip(_('Checker'))
            self.checker.props.sensitive = True
            self.checker.connect('clicked', self._checker_cb)
            toolbar_box.toolbar.insert(self.checker, -1)
            self.checker.show()

            separator = gtk.SeparatorToolItem()
            separator.show()
            toolbar_box.toolbar.insert(separator, -1)

            dpi = self._canvas.get_dpi()
            self._dpi_spin_adj = gtk.Adjustment(dpi, 72, 200, 2, 32, 0)
            self._dpi_spin = gtk.SpinButton(self._dpi_spin_adj, 0, 0)
            self._dpi_spin_id = self._dpi_spin.connect('value-changed',
                                                       self._dpi_spin_cb)
            self._dpi_spin.set_numeric(True)
            self._dpi_spin.show()
            self.tool_item_dpi = gtk.ToolItem()
            self.tool_item_dpi.add(self._dpi_spin)
            toolbar_box.toolbar.insert(self.tool_item_dpi, -1)
            self.tool_item_dpi.show()

            separator = gtk.SeparatorToolItem()
            separator.props.draw = False
            separator.set_expand(True)
            separator.show()
            toolbar_box.toolbar.insert(separator, -1)

            # The ever-present Stop Button
            stop_button = StopButton(self)
            stop_button.props.accelerator = '<Ctrl>Q'
            toolbar_box.toolbar.insert(stop_button, -1)
            stop_button.show()

            self.set_toolbar_box(toolbar_box)
            toolbar_box.show()

The old toolbars...

        except NameError:
            # Use pre-0.86 toolbar design
            toolbox = activity.ActivityToolbox(self)
            self.set_toolbox(toolbox)

            self.projectToolbar = ProjectToolbar(self)
            toolbox.add_toolbar( _('Rulers'), self.projectToolbar )

            toolbox.show()
            toolbox.set_current_toolbar(1)

Without this show_all() the toolbars won't be drawn.


        self.show_all() 

Button call-backs that talk to the Cairo canvas

    #
    # Button callbacks
    #
    def _rulers_cb(self, button):
        self._current = self._r
        self._canvas.add_a_ruler(self._current)
        return True

    def _grids_cb(self, button):
        if self._grids_mode == "cm":
            self._current = self._gcm
            self.grids.set_icon("grid-c")
            self._grids_mode = "mm"
        else:
            self._current = self._gmm
            self.grids.set_icon("grid-a")
            self._grids_mode = "cm"
        self._canvas.add_a_ruler(self._current)
        return True

    def _angles_cb(self, button):
        if self._angles_mode == "90":
            self._current = self._a90
            self.angles.set_icon("angles-360")
            self._angles_mode = "360"
        else:
            self._current = self._a360
            self.angles.set_icon("angles-90")
            self._angles_mode = "90"
        self._canvas.add_a_ruler(self._current)
        return True

    def _checker_cb(self, button):
        self._current = self._c
        self._canvas.add_a_ruler(self._current)
        return True

    def _dpi_spin_cb(self, button):
        self._canvas.set_dpi(self._dpi_spin.get_value_as_int())
        self._canvas.add_a_ruler(self._current)
        return

Saving metadata to the Journal: write_file gets called automatically when your activity is exited.

    """
    Write the dpi to the Journal
    """
    def write_file(self, file_path):
        dpi =  self._canvas.get_dpi()
        _logger.debug("Write dpi: " + str(dpi))
        self.metadata['dpi'] = str(dpi)

This code handles the pre-0.86 toolbars

#
# Project toolbar for pre-0.86 toolbars
#
class ProjectToolbar(gtk.Toolbar):

    def __init__(self, pc):
        gtk.Toolbar.__init__(self)
        self.activity = pc

        # Ruler
        self.activity.rulers = ToolButton( "ruler" )
        self.activity.rulers.set_tooltip(_('Ruler'))
        self.activity.rulers.props.sensitive = True
        self.activity.rulers.connect('clicked', self.activity._rulers_cb)
        self.insert(self.activity.rulers, -1)
        self.activity.rulers.show()

        # Grid
        self.activity.grids = ToolButton( "grid-a" )
        self.activity.grids.set_tooltip(_('Grid'))
        self.activity.grids.props.sensitive = True
        self.activity.grids.connect('clicked', self.activity._grids_cb)
        self.insert(self.activity.grids, -1)
        self.activity.grids.show()

        # Angles
        self.activity.angles = ToolButton( "angles-90" )
        self.activity.angles.set_tooltip(_('Angles'))
        self.activity.angles.props.sensitive = True
        self.activity.angles.connect('clicked', self.activity._angles_cb)
        self.insert(self.activity.angles, -1)
        self.activity.angles.show()

        # Checker
        self.activity.checker = ToolButton( "checker" )
        self.activity.checker.set_tooltip(_('Checker'))
        self.activity.checker.props.sensitive = True
        self.activity.checker.connect('clicked', self.activity._checker_cb)
        self.insert(self.activity.checker, -1)
        self.activity.checker.show()

        separator = gtk.SeparatorToolItem()
        separator.set_draw(True)
        self.insert(separator, -1)
        separator.show()

        dpi = self.activity._canvas.get_dpi()
        self.activity._dpi_spin_adj = gtk.Adjustment(dpi, 72, 200, 2, 32, 0)
        self.activity._dpi_spin = \
            gtk.SpinButton(self.activity._dpi_spin_adj, 0, 0)
        self.activity._dpi_spin_id = self.activity._dpi_spin.connect(
                                    'value-changed', self.activity._dpi_spin_cb)
        self.activity._dpi_spin.set_numeric(True)
        self.activity._dpi_spin.show()
        self.activity.tool_item_dpi = gtk.ToolItem()
        self.activity.tool_item_dpi.add(self.activity._dpi_spin)
        self.insert(self.activity.tool_item_dpi, -1)
        self.activity.tool_item_dpi.show()