3
* Copyright (C) 2004-2008 Jean-Yves Lefort <jylefort@brutele.be>
5
* This program is free software; you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License as published by
7
* the Free Software Foundation; either version 3 of the License, or
8
* (at your option) any later version.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License along
16
* with this program; if not, write to the Free Software Foundation, Inc.,
17
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22
#include "lm-data-set.h"
28
* Make sure that the resulting area width is divisible by the x
29
* subdivisions, and that the resulting area height is divisible by
32
#define LM_LINE_GRAPH_WIDTH 494
33
#define LM_LINE_GRAPH_HEIGHT 202
35
#define LM_LINE_GRAPH_AREA_WIDTH (LM_LINE_GRAPH_WIDTH - LM_BOX_BORDER_WIDTH * 2)
36
#define LM_LINE_GRAPH_AREA_HEIGHT (LM_LINE_GRAPH_HEIGHT - LM_BOX_BORDER_WIDTH * 2)
41
* Have integral subdivisions (100 ms vertical and 10 seconds
42
* horizontal) at the default scale and span.
45
#define X_MAJOR_SUBDIVISIONS 6
46
#define X_MINOR_SUBDIVISIONS 12
47
#define X_LABELS (X_MAJOR_SUBDIVISIONS + 1)
49
#define Y_MAJOR_SUBDIVISIONS 5
50
#define Y_MINOR_SUBDIVISIONS 10
51
#define Y_LABELS (Y_MAJOR_SUBDIVISIONS + 1)
63
#include <glib/gi18n.h>
65
#include "lm-applet.h"
66
#include "lm-host-frontend.h"
68
#define LINE_WIDTH 1.0
73
/* spacing between graph and labels */
74
#define GRAPH_MARGIN 3
77
class LM:Line:Graph from Gtk:Widget
79
private LMApplet *applet;
80
property POINTER applet (link, flags = CONSTRUCT_ONLY);
82
private unsigned int update_timeout_id;
83
private unsigned int update_time_timeout_id;
84
private unsigned int update_not_done_timeout_id;
86
private LabelInfo x_labels[X_LABELS];
87
private LabelInfo y_labels[Y_LABELS];
89
/* total widget size */
93
/* relative offset of graph within widget */
97
private GdkGC *subdivision_gc unrefwith g_object_unref;
101
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(class);
103
gtk_widget_class_install_style_property(widget_class,
104
g_param_spec_boxed("minor-subdivision-color",
109
gtk_widget_class_install_style_property(widget_class,
110
g_param_spec_boxed("major-subdivision-color",
121
GTK_WIDGET_SET_FLAGS(self, GTK_NO_WINDOW);
123
g_object_connect(self,
124
"signal::style-set", self_clear_labels_and_queue_resize, self,
125
"signal::direction-changed", self_clear_labels_and_queue_resize, self,
128
lm_g_object_connect(self, selfp->applet,
129
"swapped-signal::notify::scale", self_clear_labels_and_queue_resize, self,
130
"swapped-signal::notify::tooltip-graph-span", self_tooltip_graph_span_changed, self,
133
LM_ARRAY_FOREACH(i, selfp->applet->hosts)
135
LMHostFrontend *host = g_ptr_array_index(selfp->applet->hosts, i);
137
lm_g_object_connect(self, host,
138
"swapped-signal::notify::color", gtk_widget_queue_draw, self,
145
lm_source_clear(&selfp->update_timeout_id);
146
lm_source_clear(&selfp->update_time_timeout_id);
147
lm_source_clear(&selfp->update_not_done_timeout_id);
149
self_clear_labels(self);
153
tooltip_graph_span_changed (self)
156
* We must clear the update timeout since a new one is needed
157
* (expose_event() will install it).
159
lm_source_clear(&selfp->update_timeout_id);
161
self_clear_labels_and_queue_resize(self);
164
override (Gtk:Widget) void
165
size_request (GtkWidget *widget, GtkRequisition *requisition)
167
Self *self = SELF(widget);
169
self_ensure_labels(self);
171
requisition->width = selfp->width;
172
requisition->height = selfp->height;
178
if (! selfp->x_labels[0].layout)
181
self_clear_labels_real(self, selfp->x_labels, X_LABELS);
182
self_clear_labels_real(self, selfp->y_labels, Y_LABELS);
186
clear_labels_real (self, LabelInfo *infos, int num_infos)
190
for (i = 0; i < num_infos; i++)
192
LabelInfo *info = &infos[i];
194
g_object_unref(info->layout);
200
clear_labels_and_queue_resize (self)
202
self_clear_labels(self);
203
gtk_widget_queue_resize(GTK_WIDGET(self));
212
const char *time_format;
215
int x_label_width = 0;
216
int x_label_height = 0;
217
int y_label_width = 0;
218
int y_label_height = 0;
224
if (selfp->x_labels[0].layout)
227
/* create horizontal labels */
229
g_get_current_time(&now);
231
step = (double) selfp->applet->tooltip_graph_span / X_MAJOR_SUBDIVISIONS;
233
if (selfp->applet->tooltip_graph_span < 3600)
235
else if (selfp->applet->tooltip_graph_span < 86400)
238
time_format = "%a %R";
240
for (i = 0; i < X_LABELS; i++)
242
LabelInfo *info = &selfp->x_labels[i];
247
label_time = now.tv_sec - step * (X_MAJOR_SUBDIVISIONS - i);
248
tm = localtime(&label_time);
250
str = lm_strftime(time_format, tm);
251
info->layout = self_create_pango_layout(self, str, &x_label_width, &x_label_height);
255
/* create vertical labels */
257
step = (double) selfp->applet->scale / Y_MAJOR_SUBDIVISIONS;
259
/* display a suitable number of decimals */
260
step_usec = selfp->applet->scale * 1000 / Y_MAJOR_SUBDIVISIONS;
261
if (step_usec % 1000 == 0)
263
else if (step_usec % 100 == 0)
268
for (i = 0; i < Y_LABELS; i++)
270
LabelInfo *info = &selfp->y_labels[i];
273
str = g_strdup_printf(_("%.*f ms"), y_precision, step * (Y_MAJOR_SUBDIVISIONS - i));
274
info->layout = self_create_pango_layout(self, str, &y_label_width, &y_label_height);
278
/* compute margins, graph size and position */
280
pango_layout_get_pixel_extents(selfp->x_labels[0].layout, NULL, &rect);
282
x_left_margin = rect.width / 2;
283
y_left_margin = y_label_width + GRAPH_MARGIN;
285
selfp->graph_x = MAX(x_left_margin, y_left_margin);
286
selfp->graph_y = y_label_height / 2;
288
pango_layout_get_pixel_extents(selfp->x_labels[X_LABELS - 1].layout, NULL, &rect);
290
selfp->width = selfp->graph_x + LM_LINE_GRAPH_WIDTH + rect.width / 2;
291
selfp->height = selfp->graph_y + LM_LINE_GRAPH_HEIGHT + GRAPH_MARGIN + x_label_height;
293
/* compute horizontal labels positions */
295
step_pixels = LM_LINE_GRAPH_WIDTH / X_MAJOR_SUBDIVISIONS;
297
for (i = 0; i < X_LABELS; i++)
299
LabelInfo *info = &selfp->x_labels[i];
301
pango_layout_get_pixel_extents(info->layout, NULL, &rect);
303
info->x = selfp->graph_x + step_pixels * i - rect.width / 2;
304
info->y = selfp->graph_y + LM_LINE_GRAPH_HEIGHT + GRAPH_MARGIN + y_label_height - rect.height;
307
/* compute vertical labels positions */
309
step_pixels = LM_LINE_GRAPH_HEIGHT / Y_MAJOR_SUBDIVISIONS;
311
for (i = 0; i < Y_LABELS; i++)
313
LabelInfo *info = &selfp->y_labels[i];
315
pango_layout_get_pixel_extents(info->layout, NULL, &rect);
317
info->x = selfp->graph_x - GRAPH_MARGIN - rect.width;
318
info->y = selfp->graph_y + step_pixels * i - rect.height / 2;
321
/* update the time labels when the second will change */
323
if (selfp->update_time_timeout_id)
324
g_source_remove(selfp->update_time_timeout_id);
326
selfp->update_time_timeout_id = gdk_threads_add_timeout((1000 - now.tv_usec / 1000) + 1, self_update_time_cb, self);
329
private PangoLayout *
330
create_pango_layout (self,
331
const char *text (check null),
332
int *max_width (check null),
333
int *max_height (check null))
339
layout = gtk_widget_create_pango_layout(GTK_WIDGET(self), NULL);
341
markup = g_strdup_printf("<span size=\"smaller\">%s</span>", text);
342
pango_layout_set_markup(layout, markup, -1);
345
pango_layout_get_pixel_extents(layout, NULL, &rect);
347
if (rect.width > *max_width)
348
*max_width = rect.width;
349
if (rect.height > *max_height)
350
*max_height = rect.height;
355
override (Gtk:Widget) gboolean
356
expose_event (GtkWidget *widget, GdkEventExpose *event)
358
Self *self = SELF(widget);
366
if (! GTK_WIDGET_DRAWABLE(widget))
369
lm_source_clear(&selfp->update_not_done_timeout_id);
371
self_ensure_labels(self);
373
lm_widget_get_origin(widget, XALIGN, YALIGN, &x, &y);
375
self_draw_labels(self, event, x, y);
377
graph_x = x + selfp->graph_x;
378
graph_y = y + selfp->graph_y;
380
lm_paint_box(widget->window,
381
GTK_WIDGET_STATE(widget),
385
widget->style->base_gc[GTK_WIDGET_STATE(widget)],
389
LM_LINE_GRAPH_HEIGHT);
391
graph_area_x = graph_x + LM_BOX_BORDER_WIDTH;
392
graph_area_y = graph_y + LM_BOX_BORDER_WIDTH;
394
self_draw_subdivisions(self, graph_area_x, graph_area_y);
396
if (selfp->applet->hosts->len != 0)
404
cr = gdk_cairo_create(widget->window);
407
* Setup a Cairo clip rectangle of size width,height at
408
* (1,1). We need this rectangle because the line that
409
* connects the first data point (which lies outside of the
410
* graph) and the second (which lies inside) crosses the graph
413
* Another purpose is to make sure that Cairo will not draw on
414
* the widget border (because of line width).
416
cairo_rectangle(cr, graph_area_x, graph_area_y, LM_LINE_GRAPH_AREA_WIDTH, LM_LINE_GRAPH_AREA_HEIGHT);
420
* Add an offset so that 0,0 is located at the upper-left
421
* corner of our clip rectangle, not at the upper-left corner
424
cairo_translate(cr, graph_area_x, graph_area_y);
426
cairo_set_line_width(cr, LINE_WIDTH);
427
cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
428
cairo_set_line_join(cr, CAIRO_LINE_JOIN_MITER);
430
now = lm_get_ticks();
431
scale = (LMTimeSpan) selfp->applet->scale * 1000;
432
span = (LMTimeSpan) selfp->applet->tooltip_graph_span * 1000000;
434
/* iterate in reverse order, to plot hosts listed first above */
435
LM_ARRAY_REVERSE_FOREACH(i, selfp->applet->hosts)
437
LMHostFrontend *host = g_ptr_array_index(selfp->applet->hosts, i);
439
self_plot_host(self, host, cr, now, scale, span);
444
if (! selfp->update_timeout_id)
449
* Compute the update interval required for scrolling
450
* pixel by pixel. Note that if we updated more often than
451
* that, lines would wobble, since rasterisation could
452
* cause the number of horizontal pixels between two data
453
* points to vary from one frame to the next.
455
timeout = selfp->applet->tooltip_graph_span * 1000 / LM_LINE_GRAPH_AREA_WIDTH;
457
selfp->update_timeout_id = gdk_threads_add_timeout(timeout, self_update_cb, self);
461
lm_source_clear(&selfp->update_timeout_id);
467
draw_labels (self, GdkEventExpose *event, int x, int y)
469
self_draw_labels_real(self, selfp->x_labels, X_LABELS, event, x, y);
470
self_draw_labels_real(self, selfp->y_labels, Y_LABELS, event, x, y);
474
draw_labels_real (self,
475
const LabelInfo *infos,
477
GdkEventExpose *event (check null),
481
GtkWidget *widget = GTK_WIDGET(self);
484
for (i = 0; i < num_infos; i++)
486
const LabelInfo *info = &infos[i];
488
gtk_paint_layout(widget->style,
490
GTK_WIDGET_STATE(widget),
502
draw_subdivisions (self, int graph_area_x, int graph_area_y)
504
GtkWidget *widget = GTK_WIDGET(self);
505
GdkColor minor_color = { 0, 0xe000, 0xe000, 0xe000 };
506
GdkColor major_color = { 0, 0xff00, 0xcc00, 0xcc00 };
507
GdkColor *custom_minor_color;
508
GdkColor *custom_major_color;
509
int minor_subdivision;
510
int major_subdivision;
515
if (! selfp->subdivision_gc)
516
selfp->subdivision_gc = gdk_gc_new(widget->window);
518
gtk_widget_style_get(widget,
519
"minor-subdivision-color", &custom_minor_color,
520
"major-subdivision-color", &custom_major_color,
523
if (custom_minor_color)
525
minor_color = *custom_minor_color;
526
gdk_color_free(custom_minor_color);
528
if (custom_major_color)
530
major_color = *custom_major_color;
531
gdk_color_free(custom_major_color);
536
minor_subdivision = LM_LINE_GRAPH_AREA_WIDTH / X_MINOR_SUBDIVISIONS;
537
major_subdivision = LM_LINE_GRAPH_AREA_WIDTH / X_MAJOR_SUBDIVISIONS;
539
line_end = graph_area_y + LM_LINE_GRAPH_AREA_HEIGHT - 1;
540
graph_end = graph_area_x + LM_LINE_GRAPH_AREA_WIDTH;
542
gdk_gc_set_rgb_fg_color(selfp->subdivision_gc, &minor_color);
544
for (line = minor_subdivision; line < LM_LINE_GRAPH_AREA_WIDTH; line += minor_subdivision)
545
if (line % major_subdivision != 0)
547
int x = graph_area_x + line;
548
gdk_draw_line(widget->window, selfp->subdivision_gc, x, graph_area_y, x, line_end);
551
gdk_gc_set_rgb_fg_color(selfp->subdivision_gc, &major_color);
553
for (line = graph_area_x + major_subdivision; line < graph_end; line += major_subdivision)
554
gdk_draw_line(widget->window, selfp->subdivision_gc, line, graph_area_y, line, line_end);
558
minor_subdivision = LM_LINE_GRAPH_AREA_HEIGHT / Y_MINOR_SUBDIVISIONS;
559
major_subdivision = LM_LINE_GRAPH_AREA_HEIGHT / Y_MAJOR_SUBDIVISIONS;
561
line_end = graph_area_x + LM_LINE_GRAPH_AREA_WIDTH - 1;
562
graph_end = graph_area_y + LM_LINE_GRAPH_AREA_HEIGHT;
564
gdk_gc_set_rgb_fg_color(selfp->subdivision_gc, &minor_color);
566
for (line = minor_subdivision; line < LM_LINE_GRAPH_AREA_HEIGHT; line += minor_subdivision)
567
if (line % major_subdivision != 0)
569
int y = graph_area_y + line;
570
gdk_draw_line(widget->window, selfp->subdivision_gc, graph_area_x, y, line_end, y);
573
gdk_gc_set_rgb_fg_color(selfp->subdivision_gc, &major_color);
575
for (line = graph_area_y + major_subdivision; line < graph_end; line += major_subdivision)
576
gdk_draw_line(widget->window, selfp->subdivision_gc, graph_area_x, line, line_end, line);
581
LM:Host:Frontend *host (check null type),
582
cairo_t *cr (check null),
587
GArray *display_data;
588
gboolean line_ended = TRUE;
592
gdk_cairo_set_source_color(cr, &host->color);
594
display_data = lm_data_set_get_display_data(host->data_set, now);
596
for (i = 0; i < display_data->len; i++)
598
const LMDataPoint *dp = &g_array_index(display_data, LMDataPoint, i);
603
if (dp->roundtrip_time < 0)
613
age = now - dp->timestamp;
615
x = (double) (span - age) / span * LM_LINE_GRAPH_AREA_WIDTH;
616
y = (double) LM_LINE_GRAPH_AREA_HEIGHT - ((double) dp->roundtrip_time / scale * LM_LINE_GRAPH_AREA_HEIGHT);
619
* Constrain high pings inside the graph, since no plot means
620
* no reply. Note that we use 1 and not 0 because of the line
621
* width: if we used 0, the point would be centered on the
622
* edge of the clip rectangle, and half of it would be
630
cairo_move_to(cr, x, y);
634
cairo_line_to(cr, x, y);
642
update_cb (gpointer data)
646
gtk_widget_queue_draw(GTK_WIDGET(self));
649
* If the update did not occur in a couple of seconds, it normally
650
* means that the graph is no longer visible (for instance because
651
* its parent tooltip was hidden). We can then save some
654
if (! selfp->update_not_done_timeout_id)
655
selfp->update_not_done_timeout_id = g_timeout_add_seconds(10, self_update_not_done_cb, self);
657
return TRUE; /* continue */
661
update_time_cb (gpointer data)
665
self_clear_labels_and_queue_resize(self);
667
selfp->update_time_timeout_id = 0;
668
return FALSE; /* remove */
672
update_not_done_cb (gpointer data)
677
lm_source_clear(&selfp->update_timeout_id);
678
lm_source_clear(&selfp->update_time_timeout_id);
680
LM_ARRAY_FOREACH(i, selfp->applet->hosts)
682
LMHostFrontend *host = g_ptr_array_index(selfp->applet->hosts, i);
684
lm_data_set_clear_display_data(host->data_set);
687
selfp->update_not_done_timeout_id = 0;
692
new (LM:Applet *applet (check null type))
694
return GTK_WIDGET(GET_NEW_VARG(LM_LINE_GRAPH_PROP_APPLET(applet), NULL));