Python bindings

Creating canvas items in Python
Grouping canvas items
Editing text
Example

DiaCanvas supports Python bindings. This is nice. A typical Python program looks like this:

#!/usr/bin/env python
                                                                                
import pygtk
pygtk.require('2.0')
import gtk
import diacanvas
                                                                                
canvas = diacanvas.Canvas()
box = diacanvas.CanvasBox()
box.set(border_width=0.3, color=diacanvas.color(200, 100, 100, 128))
canvas.root.add(box)
box.move(20, 20)
                                                                                
window = gtk.Window()
view = diacanvas.CanvasView(canvas=canvas)
view.show()
window.add(view)
window.connect("destroy", gtk.mainquit)
window.show()
                                                                                
gtk.main()
    

The Python DiaCanvas2 module is split up in one main module (diacanvas) and two submodules (shape and geometry). The diacanvas module contains the main canvas stuff, like Canvas, CanvasItem and CanvasView. The shape module is interesting if you want to create your own canvas items in Python, so is the geometry module.

In Python we do not have special types for points, rectangles and transformation matrixes, instead we use a simple tuple:

DiaPoint Tuple (x, y)
DiaRectangle Tuple (left, top, right, bottom)
gdouble[6] (affine matrix) Tuple (a0, a1, a2, a3, a4, a5)

There are a few methods that have been renamed or otherwise modified to suite Python:

C function Python method
dia_canvas_item_connect (item, handle): boolean DiaCanvasItem.connect_handle (self, handle): boolean
dia_canvas_item_disconnect (item, handle): boolean DiaCanvasItem.disconnect_handle (self, handle): boolean
DIA_SET_FLAGS (item, flags) DiaCanvasItem.set_flags (self, flags)
DIA_UNSET_FLAGS (item, flags) DiaCanvasItem.unset_flags (self, flags)
DIA_COLOR_A (r, g, b, a): int color (r, g, b, a=255): int
dia_shape_text_cursor_from_pos (shape, pos, *cursor): in shape.Text.cursor_from_pos (self, pos): (in, cursor)
dia_handle_get_pos_i (handle, *x, *y) Handle.get_pos_i (self): (x, y)
dia_handle_get_pos_w (handle, *x, *y) Handle.get_pos_w (self): (x, y)
dia_canvas_snap_to_grid (canvas, *x, *y) Canvas.snap_to_grid (self, x, y): (x, y)
dia_canvas_glue_handle (canvas, handle, dest_x, dest_y, *glue_x, *glue_y, *item): dist Canvas.glue_handle (self, handle, dest_x, dest_y): (dist, (glue_x, glue_y), item)
dia_canvas_item_affine_w2i (item, affine) DiaCanvasItem.affine_w2i(self): affine
dia_canvas_item_affine_i2w (item, affine) DiaCanvasItem.affine_i2w(self): affine
dia_canvas_item_affine_point_w2i (item, *x, *y) DiaCanvasItem.affine_point_w2i (self, x, y): (x, y)
dia_canvas_item_affine_point_i2w (item, *x, *y) DiaCanvasItem.affine_point_i2w (self, x, y): (x, y)
dia_rectangle_add_point (rect, point) geometry.rectangle_add_point (rect, point): new_rect
dia_distance_line_point (line_start, line_end, point, line_width, style, *point_on_line): distance geometry.distance_line_point (line_start, line_end, point, line_width, style): (distance, point_on_line)
dia_intersection_line_line (s1, e1, s2, e2, *intersect): boolean geometry.intersection_line_line (s1, e1, s2, e2): intersection point or None
dia_intersection_line_rectangle (line_start, line_end, rect, intersect[2]): n_intersect geometry.intersection_line_rectangle (start, end, rect): (point1, point2)[a]

[a] If no intersections, None is set in place of point1 and/or point2.

Some new methods and functions (and one new class) is defined:

DiaCanvasItem.set (self, name=value, ...) Set multiple properties at once.
DiaCanvasItem.set_bounds (self, bounds) Set the bounds of the canvas item.
DiaCanvasItem.shape_iter (self) Get a Python iterator to the first shape to be rendered.
set_callbacks(class) Initialize the GObject class to forward calls to the python object.
set_groupable(class) Add the CanvasGroupable interface to the class. You should inherit your class from CanvasGroupable too.
set_editable(class) Add the CanvasEditable interface to the class. You should inherit your class from CanvasEditable too.
CanvasEditable A abstract base class for the DiaCanvasEditable interface. You should call set_editable() too if you want to use this interface.
CanvasGroupable A wrapper class for the CanvasGroupable interface. This way you can inherit from the interface too. You should call set_groupable() too if you want to use groupability in your canvas item.
CanvasGroupable.groupable_iter Return an iterator which can be used to traverse the groups children.

Creating canvas items in Python

It is possible to create fully functional canvas items in Python. You have to define the callback methods in your python class and call set_callbacks() the first time you inherit from CanvasItem (or a predefined canvas item from the diacanvas module). Note that you have to call the parent's method from within your canvas item just like you do with normal python classes. All callback methods start with on_ followed by their name. See DiaCanvasItem for details.

The following callbacks can be defined:

on_update (self, affine): None Update the items state. You should try to do most of the update actions in this function, since it is called when the system is idle.
on_shape_iter(self): object Return an iterator object that points to the first shape to be rendered, e.g: iter([shape1, shape2]). You can also use generators for this purpose (using the yield statement). (Since 0.10.0)
on_point (self, x, y): distance Return the distance from point (x, y) to the item.
on_move (self, x, y, interactive): None Do something if the item moves. You might want to do a CanvasItem.request_update() on any sub-items.
on_handle_motion (self, handle, wx, wy, mask): None Do something if a handle is moved.
on_event (self, event): boolean Handle an event send to this item. It should return 1 if the event is handles and 0 otherwise.
on_glue (self, handle, wx, wy): (dist, (x, y)) This method is called if a connectable handle of another canvas item is moved. It should return the distance to the item (dist) and the point where the handle may connect (x,y). (wx, wy) is the position where the handle is moved to in world coordinates.
on_connect_handle (self, handle): boolean A handle wants to connect to the item. Return 1 if the handle is connected, 0 otherwise.
on_disconnect_handle (self, handle): boolean Like on_connect_handle(), but now the handle wants to disconnect.

Grouping canvas items

For canvas items to support grouping you should inherit your canvas item from CanvasGroupable. You should also set some groupability internals by calling set_groupable() on your canvas item class. Once you have done that you have to define the following methods:

on_groupable_add (self, item): boolean Add an item to the canvas item. Should return True on success, False on failure.
on_groupable_remove (self, item): boolean Idem, for removing items from the group.
on_groupable_iter(self): iter Return an iterator object of the first item in the group. You can return something like iter([item1, item2]) or create a generator object (using the yield statement). (Since 0.10.0)
on_groupable_length (self): int Return the amount of items held by the group.
on_groupable_pos (self, item): int Return the position of item in the group list, -1 on failure.

Editing text

For text to be edited on a DiaCanvasView, a canvas item should support the CanvasEditable interface. Also call set_editable(class) to make the interface actually work. Once you have done that you have to define the following methods:

on_editable_start_editing (self, shape) Start editing of the text represented by the shape, which must be a diacanvas.shape.Text.
on_editable_editing_done (self, shape, new_text) Inform that the editing is finished. shape is a diacanvas.shape.Text and new_text is the text after it has been edited.
on_editable_text_changed (self, shape, new_text) Notify the canvas item that the text has changed. shape is a diacanvas.shape.Text and new_text is the text as it is edited.

Example

Here is an example of a canvas item created in Python. The item is derived from DiaCanvasElement and has one line on top and one one on the bottom. It also has a child item which can be used to edit some text.

# 
"""Demo item for DiaCanvas2.
"""
# vim:sw=4:et

# generators can be used to iterate shapes and child objects
from __future__ import generators

import gobject
import diacanvas
import diacanvas.shape as shape

class DemoItem(diacanvas.CanvasElement, diacanvas.CanvasAbstractGroup):

    def __init__(self):
        self.__gobject_init__()

        # create our line shapes (a red and a green line)
        self.top_line = shape.Path()
        self.top_line.set_color(diacanvas.color(255, 0, 0))
        self.top_line.set_line_width(2)
        self.bottom_line = shape.Path()
        self.bottom_line.set_color(diacanvas.color(0, 255, 0))
        self.bottom_line.set_line_width(2)
        
        # create a text object (CanvasText is a composite object)
        self.text = diacanvas.CanvasText()
        # make the text a child of this canvas item
        self.add_construction(self.text)
        
    def on_update(self, affine):
        # create a line on the top
        # (line() takes one argument: a list of points)
        self.top_line.line([(0, 0), (self.width, 0)])

        # create a line on the bottom
        self.bottom_line.line([(0, self.height), (self.width, self.height)])

        # give the text the same width and height as out object
        self.text.set(width=self.width, height=self.height)
        
        # update the text
        self.update_child(self.text, affine)

        # update the parent
        diacanvas.CanvasElement.on_update(self, affine)

        # expand the boundries of this canvas item so the lines (with
        # width 2.0) are inside the boundries of this canvas item.
        self.expand_bounds(1.0)

    def on_shape_iter(self):
        # an iterator 
        yield self.top_line
        yield self.bottom_line
        # alternative:
        #return iter([self.top_line, self.bottom_line])

    def on_event(self, event):
        # make sure key events are send to our text object
        if event.type == diacanvas.EVENT_KEY_PRESS:
            self.text.focus()
            return self.text.on_event(event)
        else:
            return diacanvas.CanvasElement.on_event(self, event)

    # Groupable

    def on_groupable_add(self, item):
        """Add a new item. This is not allowed in this case.
        """
        return 0

    def on_groupable_remove(self, item):
        """Do not allow the text to be removed.
        """
        return 1

    def on_groupable_iter(self):
        """Return an iterator that can be used to traverse the children.
        """
        yield self.text
        # alternative:
        # return iter([self.text])

    def on_groupable_length(self):
        """Return the number of child objects, we have just the text object.
        """
        return 1

    def on_groupable_pos(self, item):
        """Return the position of the item wrt other child objects.
        (we have only one child).
        """
        if item == self.text:
            return 0
        else:
            return -1

gobject.type_register(DemoItem)
# add CanvasItem callbacks
diacanvas.set_callbacks(DemoItem)
# make the item groupable
diacanvas.set_groupable(DemoItem)

# end-of-file