The original version of the GNOME canvas was based on the canvas widget from the Tk toolkit. From the standpoint of the application programmer, the canvas presents the following characteristics:
A canvas appears as a normal GTK+ widget with its own GDK window (or X window).
The programmer can insert graphical items into the canvas. The canvas provides several predefined item types, including lines, rectangles, ellipses, polygons, and text. Canvas items can be manipulated after they are created and inserted into the canvas. Common operations include changing the color of an item or moving it to a different position.
Canvas items receive events just as if they were normal X windows or other widgets, and the programmer can bind these events to actions. Common actions include moving an item when the user drags it with the mouse, or changing an item's color when the mouse enters its visible area.
Canvas items are normal GTK+ objects. Custom item types can be created by simply deriving a new object class from the base GnomeCanvasItemClass. Items emit events in the form of GTK+ signals, just like other widgets.
The canvas takes care of all drawing operations so that it never flickers, and so that the user does not have to worry about repainting the items he or she wants to display.
The canvas allows for hierarchical drawings by using nested groups of items. It uses recursive bounding boxes that allow culling of items for efficient repainting. Operations such as moving or deleting a group apply to all the items inside the group. This makes it easy to create and manipulate hierarchical graphics displays.
Canvas items are normal GTK+ objects. All canvas items are ultimately derived from an abstract GnomeCanvasItemClass that defines the basic operations all canvas items must provide, like painting the item or deciding whether a point is inside it. Using the GTK+ object system provides several advantages:
No extra work is involved in wrapping canvas items for different language bindings, so the canvas is usable from languages like Scheme, Perl, Python, and C++.
Canvas items use the GTK+ signal/slot mechanism to emit events, making it easy to define behavior based on the events that items receive.
One can associate arbitrary data items to canvas items by using the GTK+ dataset mechanism.
All the attributes of items (line style, color, position) are configured using the GTK+ object argument mechanism. Since items may have many configurable attributes, using the object argument mechanism allows us to minimize the number of API entry points, and also makes it easier to create language bindings for the canvas.
Items can be hidden and shown at any time, as well as modified using affine transformations. An affine transformation is a mapping of the plane onto itself, specified as a 3×3 matrix that can be multiplied by a vector that specifies a 2D point to transform it to a different coordinate system. The user can specify an arbitrary affine transformation matrix to be applied to an item. Convenience functions are provided for common operations like translating, scaling, and rotating and item.
Items can be organized in the canvas using a tree hierarchy. Items can be groups (nodes in the tree), or terminal items (leaves in the tree). Groups can contain any number of children, which can be leaf items or other groups. A GnomeCanvasGroupClass is provided to manage groups of items. Items can be nested to an arbitrary depth inside the canvas.
A canvas has a single root group. Simple drawings or diagrams can be created by inserting all leaf items directly under the root. Complex schematics and hierarchical drawings can be created by nesting groups together. For example, a circuit editor may use small groups of basic items to create logic gates. More complex components could be created by combining the groups that represent logic gates, and complete circuits could, in turn, be composed of these components.
Operations on a group apply to all of its children; for example, moving a group produces the same visual effect as moving each child individually.
A canvas item must know how to compute the distance between a point and itself, so that the canvas can tell whether the mouse is inside an item or not. For efficiency, items keep a rectangular bounding box that lets the canvas ignore an item if a point is tested to be outside the item's bounds, which can be done very quickly. In turn, a canvas group will make its bounding box big enough to encompass all of its children's bounding boxes, allowing for efficient recursive culling of items.
Items inside a group are stacked on top of each other, and items that are higher up in the stack obscure the items below them. An item can be raised or lowered in its parent group's stack.
The canvas does not define any default behavior for items. Instead, the programmer can create signal connections to the event signal in items and define the appropriate behavior.
Items get the normal user-initiated events, such as mouse button press/release events, mouse motion events, mouse enter/leave events, key press/release events, and focus in/out events.
When an event signal is emitted for an item, it is propagated upwards in the item hierarchy until it is marked as handled by one of the event handlers. This works in the same way as event propagation in the GTK+ widget system.
The canvas uses an update/render process when something requires a change in appearance. This process goes as follows:
A state change happens in a canvas item, usually from direct manipulation through the user interface.
The canvas item requests an update from the canvas. The item is thus marked as “requiring an update”. The canvas installs an idle handler on the GTK+ main loop.
The application keeps running, possibly requesting updates for other items, until it gets back to the GTK+ main loop. This is where idle handlers are run.
The idle handler for the canvas is run. Here, the canvas calls the update method of each item that requested an update. The update method may then queue a redraw of a certain area of the item. This area is represented as a microtile array, to be described later.
The canvas decomposes the final microtile array into a list of rectangles that need repainting.
The canvas calls the draw or render method of each item that intersects one of these rectangles, depending on whether the canvas is in Xlib or Libart modes, respectively.
The canvas is now fully updated and redrawn, and the application continues running.
This method has some important characteristics. First, all updates and redraw requests are delayed until the idle loop. This ensures that the canvas will not try to repaint itself until the last state change to an item has happened, and may also reduce the number of update-related operations that need to be performed. For example, changing the coordinates of a polygon's vertices several times is equivalent to changing them just once to the final position. Also, items are asked to draw themselves onto a temporary buffer (a GDK pixmap in the case of the Xlib renderer, or an RGB buffer in the case of the Libart renderer). This completely eliminates flicker.