~ubuntu-branches/ubuntu/raring/virt-manager/raring-proposed

« back to all changes in this revision

Viewing changes to src/virtManager/graphwidgets.py

  • Committer: Bazaar Package Importer
  • Author(s): Guido Günther, Laurent Léonard, Guido Günther
  • Date: 2009-10-07 14:04:03 UTC
  • mfrom: (2.3.1 experimental)
  • mto: (2.3.2 experimental)
  • mto: This revision was merged to the branch mainline in revision 21.
  • Revision ID: james.westby@ubuntu.com-20091007140403-kl9yum4ocrar1acq
Tags: 0.8.0-2
[ Laurent Léonard ]
* [61651ae] Drop ${shlibs:Depends} from dependencies since there is no more
  C code.
* [3319434] Drop redo-patches target from debian/rules. Since gbp-pq
  is used now.
* [b04541e] Clean debian/rules. Drop commented post-patches target
  since there is no more autogen.sh.
* [145ba60] Update french translation.
* [799f18e] Update 0001-use-usr-share-gconf-for-schema-data.patch.
* [052f7bb] Remove XS-Python-Version field from debian/control. Since
  it is deprecated with pysupport.
* [bfb1611][1a66b27][a8ce142][4116d07][31ff60a] Fix some misspellings in
  french translation.
* [6e23aff] Clean build dependencies.
* [b71c8c9] Bump Debhelper version to 7.
* [c614f09] Bump Standards-Version to 3.8.3.
* [99a52c2] Clean debian/rules.
* [52f3b63] Add clean target in debian/rules. To clean automatically
  generated files: - debian/pycompat (see #424898)
* [d5e2ef7] Remove tests/Makefile in the debian/rules clean target. To get a
  clean Debian diff at rebuild time, the file is automatically generated.
* [7da6d9b] Don't close connection on all libvirt errors. Pulled from
  upstream 1c886d1863f7.

[ Guido Günther ]
* upload to unstable
* [464fb65] make package arch all since there is no more C code.
* [25f7663] switch to python-support and use python-distutils
* [c4da06a] bump python-libvirt dependency so we get all of VMs new
  features.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# This program is free software; you can redistribute it and/or modify
 
3
# it under the terms of the GNU General Public License as published by
 
4
# the Free Software Foundation; either version 2 of the License, or
 
5
# (at your option) any later version.
 
6
#
 
7
# This program is distributed in the hope that it will be useful,
 
8
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
9
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
10
# GNU General Public License for more details.
 
11
#
 
12
# You should have received a copy of the GNU General Public License
 
13
# along with this program; if not, write to the Free Software
 
14
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 
15
# MA 02110-1301 USA.
 
16
#
 
17
 
 
18
import gobject
 
19
import gtk.glade
 
20
import cairo
 
21
 
 
22
# For debugging
 
23
def rect_print(name, rect):
 
24
    print ("%s: height=%d, width=%d, x=%d, y=%d" %
 
25
           (name, rect.height, rect.width, rect.x, rect.y))
 
26
 
 
27
# For gproperties info, see:
 
28
# http://www.pygtk.org/docs/pygtk/class-gtkcontainer.html#function-gtk--container-class-install-child-property
 
29
 
 
30
def _line_helper(cairo_ct, cell_area, points, for_fill=False):
 
31
 
 
32
    bottom_baseline = cell_area.y + cell_area.height
 
33
    last_was_zero = False
 
34
    last_point = None
 
35
 
 
36
    for index in range(0, len(points)):
 
37
        x, y = points[index]
 
38
 
 
39
        # If stats value == 0, we don't want to draw a line
 
40
        is_zero = bool(y == bottom_baseline)
 
41
 
 
42
        # If the line is for filling, alter the coords so that fill covers
 
43
        # the same area as the parent sparkline: by default, fill is one pixel
 
44
        # short
 
45
        if for_fill:
 
46
            if index == 0:
 
47
                x -= 1
 
48
            elif index == (len(points) - 1):
 
49
                x += 1
 
50
            elif last_was_zero and is_zero:
 
51
                y += 1
 
52
 
 
53
        if index == 0:
 
54
            cairo_ct.move_to(x, y)
 
55
        elif last_was_zero and is_zero and not for_fill:
 
56
            cairo_ct.move_to(x, y)
 
57
        else:
 
58
            cairo_ct.line_to(x, y)
 
59
            last_point = (x, y)
 
60
 
 
61
        last_was_zero = is_zero
 
62
 
 
63
    return last_point
 
64
 
 
65
def draw_line(cairo_ct, cell_area, points):
 
66
    if not len(points):
 
67
        return
 
68
 
 
69
    last_point = _line_helper(cairo_ct, cell_area, points)
 
70
    if not last_point:
 
71
        # Nothing to draw
 
72
        return
 
73
 
 
74
    # Paint the line
 
75
    cairo_ct.stroke()
 
76
 
 
77
def draw_fill(cairo_ct, cell_area, points, taper=False):
 
78
    if not len(points):
 
79
        return
 
80
 
 
81
    last_point = _line_helper(cairo_ct, cell_area, points, for_fill = True)
 
82
    if not last_point:
 
83
        # Nothing to draw
 
84
        #return
 
85
        pass
 
86
 
 
87
    baseline_y = cell_area.height + cell_area.y + 1
 
88
    if taper:
 
89
        x = cell_area.width + cell_area.x
 
90
    else:
 
91
        x = points[-1][0]
 
92
 
 
93
    # Box out the area to fill
 
94
    cairo_ct.line_to(x + 1, baseline_y)
 
95
    cairo_ct.line_to(cell_area.x - 1, baseline_y)
 
96
 
 
97
    # Paint the fill
 
98
    cairo_ct.fill()
 
99
 
 
100
 
 
101
class CellRendererSparkline(gtk.CellRenderer):
 
102
    __gsignals__ = {
 
103
    }
 
104
 
 
105
    __gproperties__ = {
 
106
        # 'name' : (gobject.TYPE_*,
 
107
        #           nickname, long desc, (type related args), mode)
 
108
        # Type related args can be min, max for int (etc.), or default value
 
109
        # for strings and bool
 
110
        'data_array' : (gobject.TYPE_PYOBJECT, "Data Array",
 
111
                        "Array of data points for the graph",
 
112
                        gobject.PARAM_READWRITE),
 
113
        'reversed': (gobject.TYPE_BOOLEAN, "Reverse data",
 
114
                     "Process data from back to front.",
 
115
                     0, gobject.PARAM_READWRITE),
 
116
    }
 
117
 
 
118
    def __init__(self):
 
119
        gtk.CellRenderer.__init__(self)
 
120
 
 
121
        self.data_array = []
 
122
        self.num_sets = 0
 
123
        self.filled = True
 
124
        self.reversed = False
 
125
        self.rgb = None
 
126
 
 
127
    def do_render(self, window, widget, background_area, cell_area,
 
128
                  expose_area, flags):
 
129
        # window            : gtk.gdk.Window (not plain window)
 
130
        # widget            : Parent widget (manager treeview)
 
131
        # background_area   : GdkRectangle: entire cell area
 
132
        # cell_area         : GdkRectangle: area normally rendered by cell
 
133
        # expose_area       : GdkRectangle: area that needs updating
 
134
        # flags             : flags that affect rendering
 
135
        # flags = gtk.CELL_RENDERER_SELECTED, gtk.CELL_RENDERER_PRELIT,
 
136
        #         gtk.CELL_RENDERER_INSENSITIVE or gtk.CELL_RENDERER_SORTED
 
137
 
 
138
        # Indent of the gray border around the graph
 
139
        BORDER_PADDING = 2
 
140
        # Indent of graph from border
 
141
        GRAPH_INDENT = 2
 
142
        GRAPH_PAD = (BORDER_PADDING + GRAPH_INDENT)
 
143
 
 
144
        # We don't use yalign, since we expand to the entire height
 
145
        #yalign = self.get_property("yalign")
 
146
        xalign = self.get_property("xalign")
 
147
 
 
148
        # Set up graphing bounds
 
149
        graph_x      = (cell_area.x + GRAPH_PAD)
 
150
        graph_y      = (cell_area.y + GRAPH_PAD)
 
151
        graph_width  = (cell_area.width - (GRAPH_PAD * 2))
 
152
        graph_height = (cell_area.height - (GRAPH_PAD * 2))
 
153
 
 
154
        # XXX: This needs to be smarter, we need to either center the graph
 
155
        #      or have some way of making it variable sized
 
156
        pixels_per_point = (graph_width / ((len(self.data_array) or 1) - 1))
 
157
 
 
158
        # Graph width needs to be some multiple of the amount of data points
 
159
        # we have
 
160
        graph_width = (pixels_per_point * ((len(self.data_array) or 1) - 1))
 
161
 
 
162
        # Recalculate border width based on the amount we are graphing
 
163
        #border_width = graph_width + GRAPH_PAD
 
164
        border_width = graph_width + (GRAPH_INDENT * 2)
 
165
 
 
166
        # Align the widget
 
167
        empty_space = cell_area.width - border_width - (BORDER_PADDING * 2)
 
168
        if empty_space:
 
169
            xalign_space = int(empty_space * xalign)
 
170
            cell_area.x += xalign_space
 
171
            graph_x += xalign_space
 
172
 
 
173
        cairo_ct = window.cairo_create()
 
174
        cairo_ct.set_line_width(3)
 
175
        cairo_ct.set_line_cap(cairo.LINE_CAP_ROUND)
 
176
 
 
177
        # Draw gray graph border
 
178
        cairo_ct.set_source_rgb(0.8828125, 0.8671875, 0.8671875)
 
179
        cairo_ct.rectangle(cell_area.x + BORDER_PADDING,
 
180
                           cell_area.y + BORDER_PADDING,
 
181
                           border_width,
 
182
                           cell_area.height - (BORDER_PADDING * 2))
 
183
        cairo_ct.stroke()
 
184
 
 
185
        # Fill in white box inside graph outline
 
186
        cairo_ct.set_source_rgb(1, 1, 1)
 
187
        cairo_ct.rectangle(cell_area.x + BORDER_PADDING,
 
188
                           cell_area.y + BORDER_PADDING,
 
189
                           border_width,
 
190
                           cell_area.height - (BORDER_PADDING * 2))
 
191
        cairo_ct.fill()
 
192
 
 
193
        def get_y(index):
 
194
            baseline_y = graph_y + graph_height
 
195
 
 
196
            if self.reversed:
 
197
                n = (len(self.data_array) - index - 1)
 
198
            else:
 
199
                n = index
 
200
 
 
201
            val = self.data_array[n]
 
202
            y = baseline_y - (graph_height * val)
 
203
 
 
204
            y = max(graph_y, y)
 
205
            y = min(graph_y + graph_height, y)
 
206
            return y
 
207
 
 
208
        points = []
 
209
        for index in range(0, len(self.data_array)):
 
210
            x = int(((index * pixels_per_point) + graph_x))
 
211
            y = int(get_y(index))
 
212
 
 
213
            points.append((x, y))
 
214
 
 
215
 
 
216
        cell_area.x = graph_x
 
217
        cell_area.y = graph_y
 
218
        cell_area.width = graph_width
 
219
        cell_area.height = graph_height
 
220
 
 
221
        # Set color to dark blue for the actual sparkline
 
222
        cairo_ct.set_line_width(2)
 
223
        cairo_ct.set_source_rgb(0.421875, 0.640625, 0.73046875)
 
224
        draw_line(cairo_ct, cell_area, points)
 
225
 
 
226
        # Set color to light blue for the fill
 
227
        cairo_ct.set_source_rgba(0.71484375, 0.84765625, 0.89453125, .5)
 
228
        draw_fill(cairo_ct, cell_area, points)
 
229
 
 
230
        # Stop clipping
 
231
        cairo_ct.clip()
 
232
        cairo_ct.save()
 
233
        cairo_ct.restore()
 
234
        del(cairo_ct)
 
235
        return
 
236
 
 
237
    def do_get_size(self, widget, cell_area=None):
 
238
        FIXED_WIDTH = len(self.data_array)
 
239
        FIXED_HEIGHT = 15
 
240
        xpad = self.get_property("xpad")
 
241
        ypad = self.get_property("ypad")
 
242
 
 
243
        if cell_area:
 
244
            # XXX: What to do here?
 
245
            xoffset = 0
 
246
            yoffset = 0
 
247
        else:
 
248
            xoffset = 0
 
249
            yoffset = 0
 
250
 
 
251
        width = ((xpad * 2) + FIXED_WIDTH)
 
252
        height = ((ypad * 2) + FIXED_HEIGHT)
 
253
 
 
254
        return (xoffset, yoffset, width, height)
 
255
 
 
256
    def _sanitize_param_spec_name(self, name):
 
257
        # Why this is made necessary, I have no idea
 
258
        return name.replace("-", "_")
 
259
 
 
260
    def do_get_property(self, param_spec):
 
261
        name = self._sanitize_param_spec_name(param_spec.name)
 
262
        return getattr(self, name)
 
263
 
 
264
    def do_set_property(self, param_spec, value):
 
265
        name = self._sanitize_param_spec_name(param_spec.name)
 
266
        setattr(self, name, value)
 
267
 
 
268
class Sparkline(gtk.DrawingArea):
 
269
    __gsignals__ = {}
 
270
 
 
271
    __gproperties__ = {
 
272
        # 'name' : (gobject.TYPE_*,
 
273
        #           nickname, long desc, (type related args), mode)
 
274
        # Type related args can be min, max for int (etc.), or default value
 
275
        # for strings and bool
 
276
        'data_array' : (gobject.TYPE_PYOBJECT, "Data Array",
 
277
                        "Array of data points for the graph",
 
278
                        gobject.PARAM_READWRITE),
 
279
        'filled': (gobject.TYPE_BOOLEAN, 'Filled', 'the foo of the object',
 
280
                   1,
 
281
                   gobject.PARAM_READWRITE),
 
282
        'num_sets': (gobject.TYPE_INT, "Number of sets",
 
283
                     "Number of data sets to graph",
 
284
                     1, 2, 1, gobject.PARAM_READWRITE),
 
285
        'reversed': (gobject.TYPE_BOOLEAN, "Reverse data",
 
286
                     "Process data from back to front.",
 
287
                     0, gobject.PARAM_READWRITE),
 
288
        'rgb': (gobject.TYPE_PYOBJECT, "rgb array", "List of rgb values",
 
289
                gobject.PARAM_READWRITE),
 
290
    }
 
291
 
 
292
    def __init__(self):
 
293
        gtk.DrawingArea.__init__(self)
 
294
 
 
295
        self._data_array = []
 
296
        self.num_sets = 1
 
297
        self.filled = True
 
298
        self.reversed = False
 
299
        self.rgb = []
 
300
 
 
301
        self.connect("expose-event", self.do_expose)
 
302
 
 
303
    def set_data_array(self, val):
 
304
        self._data_array = val
 
305
        self.queue_draw()
 
306
    def get_data_array(self):
 
307
        return self._data_array
 
308
    data_array = property(get_data_array, set_data_array)
 
309
 
 
310
 
 
311
    def do_expose(self, widget, event):
 
312
        # widget    : This widget
 
313
        # event     : GdkEvent
 
314
        # cell_area : GdkRectangle: area normally rendered by cell
 
315
        # window            : gtk.gdk.Window (not plain window)
 
316
 
 
317
        # cell_area : GdkRectangle: area normally rendered by cell
 
318
        cell_area = widget.allocation
 
319
 
 
320
        # window            : gtk.gdk.Window (not plain window)
 
321
        window = widget.window
 
322
 
 
323
        points_per_set = (len(self.data_array) / self.num_sets)
 
324
        pixels_per_point = (float(cell_area.width) /
 
325
                            (float((points_per_set - 1) or 1)))
 
326
 
 
327
        # Mid-color graphics context (gtk.GC)
 
328
        # This draws the light gray backing rectangle
 
329
        mid_gc = widget.style.mid_gc[widget.state]
 
330
        window.draw_rectangle(mid_gc, True, 0, 0,
 
331
                              cell_area.width - 1,
 
332
                              cell_area.height - 1)
 
333
 
 
334
        # Foreground-color graphics context
 
335
        # This draws the black border
 
336
        fg_gc = widget.style.fg_gc[widget.state]
 
337
        window.draw_rectangle(fg_gc, False, 0, 0,
 
338
                              cell_area.width - 1,
 
339
                              cell_area.height - 1)
 
340
 
 
341
        # This draws the marker ticks
 
342
        max_ticks = 4
 
343
        dark_gc = widget.style.dark_gc[widget.state]
 
344
        for index in range(0, max_ticks):
 
345
            window.draw_line(dark_gc, 1,
 
346
                             (cell_area.height / max_ticks) * index,
 
347
                             cell_area.width - 2,
 
348
                             (cell_area.height / max_ticks) * index)
 
349
 
 
350
        # Draw the actual sparkline
 
351
        def get_y(dataset, index):
 
352
            baseline_y = cell_area.height
 
353
 
 
354
            n = dataset * points_per_set
 
355
            if self.reversed:
 
356
                n += (points_per_set - index - 1)
 
357
            else:
 
358
                n += index
 
359
 
 
360
            val = self.data_array[n]
 
361
            return baseline_y - ((cell_area.height - 1) * val)
 
362
 
 
363
        cairo_ct = window.cairo_create()
 
364
        cairo_ct.save()
 
365
        cairo_ct.rectangle(0, 0, cell_area.width, cell_area.height)
 
366
        cairo_ct.clip()
 
367
        cairo_ct.set_line_width(2)
 
368
 
 
369
        for dataset in range(0, self.num_sets):
 
370
            if len(self.rgb) == (self.num_sets * 3):
 
371
                cairo_ct.set_source_rgb(self.rgb[(dataset * 3)],
 
372
                                        self.rgb[(dataset * 3) + 1],
 
373
                                        self.rgb[(dataset * 1) + 2])
 
374
            points = []
 
375
            for index in range(0, points_per_set):
 
376
                x = index * pixels_per_point
 
377
                y = get_y(dataset, index)
 
378
 
 
379
                points.append((int(x), int(y)))
 
380
 
 
381
 
 
382
            if self.num_sets == 1:
 
383
                pass
 
384
 
 
385
            draw_line(cairo_ct, cell_area, points)
 
386
            if self.filled:
 
387
                draw_fill(cairo_ct, cell_area, points, taper=True)
 
388
 
 
389
        # Stop clipping
 
390
        cairo_ct.restore()
 
391
        del(cairo_ct)
 
392
        return 0
 
393
 
 
394
    def do_size_request(self, requisition):
 
395
        # Requisition: a GtkRequisition instance
 
396
        width = len(self.data_array) / self.num_sets
 
397
        height = 20
 
398
 
 
399
        requisition.width = width
 
400
        requisition.height = height
 
401
 
 
402
    def _sanitize_param_spec_name(self, name):
 
403
        # Why this is made necessary, I have no idea
 
404
        return name.replace("-", "_")
 
405
 
 
406
    def do_get_property(self, param_spec):
 
407
        name = self._sanitize_param_spec_name(param_spec.name)
 
408
        return getattr(self, name)
 
409
 
 
410
    def do_set_property(self, param_spec, value):
 
411
        name = self._sanitize_param_spec_name(param_spec.name)
 
412
        setattr(self, name, value)
 
413
 
 
414
gobject.type_register(Sparkline)
 
415
gobject.type_register(CellRendererSparkline)