1
# -*- coding: UTF-8 -*-
2
"""Cell renderer for directed graph.
4
This module contains the implementation of a custom GtkCellRenderer that
5
draws part of the directed graph based on the lines suggested by the code
8
Because we're shiny, we use Cairo to do this, and because we're naughty
9
we cheat and draw over the bits of the TreeViewColumn that are supposed to
10
just be for the background.
13
__copyright__ = "Copyright © 2005 Canonical Ltd."
14
__author__ = "Scott James Remnant <scott@ubuntu.com>"
25
class CellRendererGraph(gtk.GenericCellRenderer):
26
"""Cell renderer for directed graph.
29
node (column, colour) tuple to draw revision node,
30
in_lines (start, end, colour) tuple list to draw inward lines,
31
out_lines (start, end, colour) tuple list to draw outward lines.
37
"node": ( gobject.TYPE_PYOBJECT, "node",
38
"revision node instruction",
39
gobject.PARAM_WRITABLE
41
"tags": ( gobject.TYPE_PYOBJECT, "tags",
42
"list of tags associated with the node",
43
gobject.PARAM_WRITABLE
45
"in-lines": ( gobject.TYPE_PYOBJECT, "in-lines",
46
"instructions to draw lines into the cell",
47
gobject.PARAM_WRITABLE
49
"out-lines": ( gobject.TYPE_PYOBJECT, "out-lines",
50
"instructions to draw lines out of the cell",
51
gobject.PARAM_WRITABLE
55
def do_set_property(self, property, value):
56
"""Set properties from GObject properties."""
57
if property.name == "node":
59
elif property.name == "tags":
61
elif property.name == "in-lines":
63
elif property.name == "out-lines":
64
self.out_lines = value
66
raise AttributeError, "no such property: '%s'" % property.name
68
def box_size(self, widget):
69
"""Calculate box size based on widget's font.
71
Cache this as it's probably expensive to get. It ensures that we
72
draw the graph at least as large as the text.
76
except AttributeError:
77
pango_ctx = widget.get_pango_context()
78
font_desc = widget.get_style().font_desc
79
metrics = pango_ctx.get_metrics(font_desc)
81
ascent = pango.PIXELS(metrics.get_ascent())
82
descent = pango.PIXELS(metrics.get_descent())
84
self._box_size = ascent + descent + 6
87
def set_colour(self, ctx, colour, bg, fg):
88
"""Set the context source colour.
90
Picks a distinct colour based on an internal wheel; the bg
91
parameter provides the value that should be assigned to the 'zero'
92
colours and the fg parameter provides the multiplier that should be
93
applied to the foreground colours.
95
mainline_color = ( 0.0, 0.0, 0.0 )
106
colour_rgb = mainline_color
108
colour_rgb = colours[colour % len(colours)]
110
red = (colour_rgb[0] * fg) or bg
111
green = (colour_rgb[1] * fg) or bg
112
blue = (colour_rgb[2] * fg) or bg
114
ctx.set_source_rgb(red, green, blue)
116
def on_get_size(self, widget, cell_area):
117
"""Return the size we need for this cell.
119
Each cell is drawn individually and is only as wide as it needs
120
to be, we let the TreeViewColumn take care of making them all
123
box_size = self.box_size(widget) + 1
125
width = box_size * (self.columns_len + 1)
128
# FIXME I have no idea how to use cell_area properly
129
return (0, 0, width, height)
131
def on_render(self, window, widget, bg_area, cell_area, exp_area, flags):
132
"""Render an individual cell.
134
Draws the cell contents using cairo, taking care to clip what we
135
do to within the background area so we don't draw over other cells.
136
Note that we're a bit naughty there and should really be drawing
137
in the cell_area (or even the exposed area), but we explicitly don't
140
We try and be a little clever, if the line we need to draw is going
141
to cross other columns we actually draw it as in the .---' style
142
instead of a pure diagonal ... this reduces confusion by an
145
ctx = window.cairo_create()
146
ctx.rectangle(bg_area.x, bg_area.y, bg_area.width, bg_area.height)
149
box_size = self.box_size(widget)
151
ctx.set_line_width(box_size / 8)
152
ctx.set_line_cap(cairo.LINE_CAP_ROUND)
154
# Draw lines into the cell
155
for start, end, colour in self.in_lines:
156
self.render_line (ctx, cell_area, box_size,
157
bg_area.y, bg_area.height,
160
# Draw lines out of the cell
161
for start, end, colour in self.out_lines:
162
self.render_line (ctx, cell_area, box_size,
163
bg_area.y + bg_area.height, bg_area.height,
166
# Draw the revision node in the right column
167
(column, colour) = self.node
168
ctx.arc(cell_area.x + box_size * column + box_size / 2,
169
cell_area.y + cell_area.height / 2,
170
box_size / 4, 0, 2 * math.pi)
172
self.set_colour(ctx, colour, 0.0, 0.5)
173
ctx.stroke_preserve()
175
self.set_colour(ctx, colour, 0.5, 1.0)
178
self.render_tags(ctx, widget.create_pango_context(), cell_area, box_size)
180
def render_line(self, ctx, cell_area, box_size, mid, height, start, end, colour):
182
x = cell_area.x + box_size * end + box_size / 2
183
ctx.move_to(x, mid + height / 3)
184
ctx.line_to(x, mid + height / 3)
185
ctx.move_to(x, mid + height / 6)
186
ctx.line_to(x, mid + height / 6)
189
x = cell_area.x + box_size * start + box_size / 2
190
ctx.move_to(x, mid - height / 3)
191
ctx.line_to(x, mid - height / 3)
192
ctx.move_to(x, mid - height / 6)
193
ctx.line_to(x, mid - height / 6)
195
startx = cell_area.x + box_size * start + box_size / 2
196
endx = cell_area.x + box_size * end + box_size / 2
198
ctx.move_to(startx, mid - height / 2)
200
if start - end == 0 :
201
ctx.line_to(endx, mid + height / 2)
203
ctx.curve_to(startx, mid - height / 5,
204
startx, mid - height / 5,
205
startx + (endx - startx) / 2, mid)
207
ctx.curve_to(endx, mid + height / 5,
208
endx, mid + height / 5 ,
209
endx, mid + height / 2)
211
self.set_colour(ctx, colour, 0.0, 0.65)
214
def render_tags(self, ctx, pango_ctx, cell_area, box_size):
215
# colour ID used in self.set_colour on the tags
218
(column, colour) = self.node
220
font_desc = pango.FontDescription()
221
font_desc.set_size(pango.SCALE * 7)
223
tag_layout = pango.Layout(pango_ctx)
224
tag_layout.set_font_description(font_desc)
226
# The width of the tag label stack
229
for tag_idx, tag in enumerate(self.tags):
230
tag_layout.set_text(" " + tag + " ")
231
text_width, text_height = tag_layout.get_pixel_size()
234
box_size * (column + 1.3) + width
237
cell_area.height / 2 - \
240
width += text_width + 5
242
# Draw the tag border
243
ctx.move_to(x0 - box_size / 3, y0 + text_height / 2)
245
ctx.line_to(x0 + text_width, y0)
246
ctx.line_to(x0 + text_width, y0 + text_height)
247
ctx.line_to(x0, y0 + text_height)
248
ctx.line_to(x0 - box_size / 3, y0 + text_height / 2)
251
ctx.arc(x0 - box_size / 12,
252
y0 + text_height / 2,
256
self.set_colour(ctx, TAG_COLOUR_ID, 0.0, 0.5)
257
ctx.stroke_preserve()
259
ctx.set_fill_rule (cairo.FILL_RULE_EVEN_ODD)
260
self.set_colour(ctx, TAG_COLOUR_ID, 0.5, 1.0)
264
self.set_colour(ctx, 0, 0.0, 0.0)
266
ctx.show_layout(tag_layout)