Merge tag 'upstream/1.1.5'
[psensor-pkg-debian.git] / src / graph.c
1 /*
2  * Copyright (C) 2010-2014 jeanfi@gmail.com
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301 USA
18  */
19 #include <stdlib.h>
20 #include <string.h>
21
22 #include <sys/time.h>
23
24 #include <glib/gi18n.h>
25 #include <gtk/gtk.h>
26
27 #include <math.h>
28
29 #include <cfg.h>
30 #include <plog.h>
31 #include <psensor.h>
32
33 /* horizontal padding */
34 const int GRAPH_H_PADDING = 4;
35 /* vertical padding */
36 const int GRAPH_V_PADDING = 4;
37
38 bool is_smooth_curves_enabled;
39
40 struct graph_info {
41         /* Horizontal position of the central area (curves) */
42         int g_xoff;
43         /* Vertical position of the central area (curves) */
44         int g_yoff;
45
46         /* Width of the central area (curves) */
47         int g_width;
48         /* Height of the central area (curves) */
49         int g_height;
50
51         /* Height of the drawing canvas */
52         int height;
53         /* Width of the drawing canvas */
54         int width;
55 };
56
57 static GtkStyleContext *style;
58 /* Foreground color of the current desktop theme */
59 static GdkRGBA theme_fg_color;
60 /* Background color of the current desktop theme */
61 static GdkRGBA theme_bg_color;
62
63 static void update_theme(GtkWidget *w)
64 {
65         style = gtk_widget_get_style_context(w);
66
67         gtk_style_context_get_background_color(style,
68                                                GTK_STATE_FLAG_NORMAL,
69                                                &theme_bg_color);
70         gtk_style_context_get_color(style,
71                                     GTK_STATE_FLAG_NORMAL,
72                                     &theme_fg_color);
73 }
74
75 /* Return the end time of the graph i.e. the more recent measure.  If
76  * no measure are available, return 0.
77  * If Bezier curves are used return the measure n-3 to avoid to
78  * display a part of the curve outside the graph area.
79  */
80 static time_t get_graph_end_time_s(struct psensor **sensors)
81 {
82         time_t ret, t;
83         struct psensor *s;
84         struct measure *measures;
85         int i, n;
86
87         ret = 0;
88         while (*sensors) {
89                 s = *sensors;
90                 measures = s->measures;
91
92                 if (is_smooth_curves_enabled)
93                         n = 2;
94                 else
95                         n = 0;
96
97                 for (i = s->values_max_length - 1; i >= 0; i--) {
98                         if (measures[i].value != UNKNOWN_DBL_VALUE) {
99                                 if (!n) {
100                                         t = measures[i].time.tv_sec;
101
102                                         if (t > ret) {
103                                                 ret = t;
104                                                 break;
105                                         }
106                                 } else {
107                                         n--;
108                                 }
109                         }
110                         i--;
111                 }
112
113                 sensors++;
114         }
115
116         return ret;
117 }
118
119 static time_t get_graph_begin_time_s(struct config *cfg, time_t etime)
120 {
121         if (!etime)
122                 return 0;
123
124         return etime - cfg->graph_monitoring_duration * 60;
125 }
126
127 static double
128 compute_y(double value, double min, double max, int height, int off)
129 {
130         double t = value - min;
131
132         return height - ((double)height * (t / (max - min))) + off;
133 }
134
135 static char *time_to_str(time_t s)
136 {
137         char *str;
138         /* note: localtime returns a static field, no free required */
139         struct tm *tm = localtime(&s);
140
141         if (!tm)
142                 return NULL;
143
144         str = malloc(6);
145         strftime(str, 6, "%H:%M", tm);
146
147         return str;
148 }
149
150 static void draw_left_region(cairo_t *cr, struct graph_info *info)
151 {
152         cairo_set_source_rgb(cr,
153                              theme_bg_color.red,
154                              theme_bg_color.green,
155                              theme_bg_color.blue);
156
157         cairo_rectangle(cr, 0, 0, info->g_xoff, info->height);
158         cairo_fill(cr);
159 }
160
161 static void draw_right_region(cairo_t *cr, struct graph_info *info)
162 {
163         cairo_set_source_rgb(cr,
164                              theme_bg_color.red,
165                              theme_bg_color.green,
166                              theme_bg_color.blue);
167
168
169         cairo_rectangle(cr,
170                         info->g_xoff + info->g_width,
171                         0,
172                         info->g_xoff + info->g_width + GRAPH_H_PADDING,
173                         info->height);
174         cairo_fill(cr);
175 }
176
177 static void
178 draw_graph_background(cairo_t *cr,
179                       struct config *config,
180                       struct graph_info *info)
181 {
182         struct color *bgcolor;
183
184         bgcolor = config->graph_bgcolor;
185
186         if (config->alpha_channel_enabled)
187                 cairo_set_source_rgba(cr,
188                                       theme_bg_color.red,
189                                       theme_bg_color.green,
190                                       theme_bg_color.blue,
191                                       config->graph_bg_alpha);
192         else
193                 cairo_set_source_rgb(cr,
194                                      theme_bg_color.red,
195                                      theme_bg_color.green,
196                                      theme_bg_color.blue);
197
198         cairo_rectangle(cr, info->g_xoff, 0, info->g_width, info->height);
199         cairo_fill(cr);
200
201         if (config->alpha_channel_enabled)
202                 cairo_set_source_rgba(cr,
203                                       bgcolor->red,
204                                       bgcolor->green,
205                                       bgcolor->blue,
206                                       config->graph_bg_alpha);
207         else
208                 cairo_set_source_rgb(cr,
209                                      bgcolor->red,
210                                      bgcolor->green,
211                                      bgcolor->blue);
212
213         cairo_rectangle(cr,
214                         info->g_xoff,
215                         info->g_yoff,
216                         info->g_width,
217                         info->g_height);
218         cairo_fill(cr);
219 }
220
221 /* setup dash style */
222 static double dashes[] = {
223         1.0,            /* ink */
224         2.0,            /* skip */
225 };
226 static int ndash = sizeof(dashes) / sizeof(dashes[0]);
227
228 static void draw_background_lines(cairo_t *cr,
229                                   int min, int max,
230                                   struct config *config,
231                                   struct graph_info *info)
232 {
233         int i;
234         double x, y;
235         struct color *color;
236
237         color = config->graph_fgcolor;
238
239         /* draw background lines */
240         cairo_set_line_width(cr, 1);
241         cairo_set_dash(cr, dashes, ndash, 0);
242         cairo_set_source_rgb(cr, color->red, color->green, color->blue);
243
244         /* vertical lines representing time steps */
245         for (i = 0; i <= 5; i++) {
246                 x = i * ((double)info->g_width / 5) + info->g_xoff;
247                 cairo_move_to(cr, x, info->g_yoff);
248                 cairo_line_to(cr, x, info->g_yoff + info->g_height);
249         }
250
251         /* horizontal lines draws a line for each 10C step */
252         for (i = min; i < max; i++) {
253                 if (i % 10 == 0) {
254                         y = compute_y(i,
255                                       min,
256                                       max,
257                                       info->g_height,
258                                       info->g_yoff);
259
260                         cairo_move_to(cr, info->g_xoff, y);
261                         cairo_line_to(cr, info->g_xoff + info->g_width, y);
262                 }
263         }
264
265         cairo_stroke(cr);
266
267         /* back to normal line style */
268         cairo_set_dash(cr, 0, 0, 0);
269 }
270
271 /* Keys: sensor identifier.
272  *
273  * Values: array of time_t. Each time_t is corresponding to a sensor
274  * measure which has been used as the start point of a Bezier curve.
275  */
276 static GHashTable *times;
277
278 static void draw_sensor_smooth_curve(struct psensor *s,
279                                      cairo_t *cr,
280                                      double min,
281                                      double max,
282                                      int bt,
283                                      int et,
284                                      struct graph_info *info)
285 {
286         int i, dt, vdt, j, k, found;
287         double x[4], y[4], v;
288         time_t t, t0, *stimes;
289
290         if (!times)
291                 times = g_hash_table_new_full(g_str_hash,
292                                               g_str_equal,
293                                               free,
294                                               free);
295
296         stimes = g_hash_table_lookup(times, s->id);
297
298         cairo_set_source_rgb(cr,
299                              s->color->red,
300                              s->color->green,
301                              s->color->blue);
302
303         /* search the index of the first measure used as a start point
304          * of a Bezier curve. The start and end points of the Bezier
305          * curves must be preserved to ensure the same overall shape
306          * of the graph. */
307         i = 0;
308         if (stimes) {
309                 while (i < s->values_max_length) {
310                         t = s->measures[i].time.tv_sec;
311                         v = s->measures[i].value;
312
313                         found = 0;
314                         if (v != UNKNOWN_DBL_VALUE && t) {
315                                 k = 0;
316                                 while (stimes[k]) {
317                                         if (t == stimes[k]) {
318                                                 found = 1;
319                                                 break;
320                                         }
321                                         k++;
322                                 }
323                         }
324
325                         if (found)
326                                 break;
327
328                         i++;
329                 }
330         }
331
332         stimes = malloc((s->values_max_length + 1) * sizeof(time_t));
333         memset(stimes, 0, (s->values_max_length + 1) * sizeof(time_t));
334         g_hash_table_insert(times, strdup(s->id), stimes);
335
336         if (i == s->values_max_length)
337                 i = 0;
338
339         k = 0;
340         dt = et - bt;
341         while (i < s->values_max_length) {
342                 j = 0;
343                 t = 0;
344                 while (i < s->values_max_length && j < 4) {
345                         t = s->measures[i].time.tv_sec;
346                         v = s->measures[i].value;
347
348                         if (v == UNKNOWN_DBL_VALUE || !t) {
349                                 i++;
350                                 continue;
351                         }
352
353                         vdt = t - bt;
354
355                         x[0 + j] = ((double)vdt * info->g_width)
356                                 / dt + info->g_xoff;
357                         y[0 + j] = compute_y(v,
358                                              min,
359                                              max,
360                                              info->g_height,
361                                              info->g_yoff);
362
363                         if (j == 0)
364                                 t0 = t;
365
366                         i++;
367                         j++;
368                 }
369
370                 if (j == 4) {
371                         cairo_move_to(cr, x[0], y[0]);
372                         cairo_curve_to(cr, x[1], y[1], x[2], y[3], x[3], y[3]);
373                         stimes[k++] = t0;
374                         i--;
375                 }
376         }
377
378         cairo_stroke(cr);
379 }
380
381 static void draw_sensor_curve(struct psensor *s,
382                               cairo_t *cr,
383                               double min,
384                               double max,
385                               int bt,
386                               int et,
387                               struct graph_info *info)
388 {
389         int first, i, t, dt, vdt;
390         double v, x, y;
391
392         cairo_set_source_rgb(cr,
393                              s->color->red,
394                              s->color->green,
395                              s->color->blue);
396
397         dt = et - bt;
398         first = 1;
399         for (i = 0; i < s->values_max_length; i++) {
400                 t = s->measures[i].time.tv_sec;
401                 v = s->measures[i].value;
402
403                 if (v == UNKNOWN_DBL_VALUE || !t)
404                         continue;
405
406                 vdt = t - bt;
407
408                 x = ((double)vdt * info->g_width) / dt + info->g_xoff;
409
410                 y = compute_y(v, min, max, info->g_height, info->g_yoff);
411
412                 if (first) {
413                         cairo_move_to(cr, x, y);
414                         first = 0;
415                 } else {
416                         cairo_line_to(cr, x, y);
417                 }
418
419         }
420         cairo_stroke(cr);
421 }
422
423 static void display_no_graphs_warning(cairo_t *cr, int x, int y)
424 {
425         char *msg;
426
427         msg = strdup(_("No graphs enabled"));
428
429         cairo_select_font_face(cr,
430                                "sans-serif",
431                                CAIRO_FONT_SLANT_NORMAL,
432                                CAIRO_FONT_WEIGHT_NORMAL);
433         cairo_set_font_size(cr, 18.0);
434
435         cairo_move_to(cr, x, y);
436         cairo_show_text(cr, msg);
437
438         free(msg);
439 }
440
441 void
442 graph_update(struct psensor **sensors,
443              GtkWidget *w_graph,
444              struct config *config,
445              GtkWidget *window)
446 {
447         int et, bt, width, height, g_width, g_height;
448         double min_rpm, max_rpm, mint, maxt, min, max;
449         char *strmin, *strmax;
450         /* horizontal and vertical offset of the graph */
451         int g_xoff, g_yoff, no_graphs;
452         cairo_surface_t *cst;
453         cairo_t *cr, *cr_pixmap;
454         char *str_btime, *str_etime;
455         cairo_text_extents_t te_btime, te_etime, te_max, te_min;
456         struct psensor **sensor_cur, **enabled_sensors;
457         GtkAllocation galloc;
458         GtkStyleContext *style_ctx;
459         struct graph_info info;
460
461         if (!gtk_widget_is_drawable(w_graph))
462                 return;
463
464         if (!style)
465                 update_theme(window);
466
467         enabled_sensors = psensor_list_filter_graph_enabled(sensors);
468
469         min_rpm = get_min_rpm(enabled_sensors);
470         max_rpm = get_max_rpm(enabled_sensors);
471
472         mint = get_min_temp(enabled_sensors);
473         strmin = psensor_value_to_str(SENSOR_TYPE_TEMP,
474                                       mint,
475                                       config->temperature_unit == CELSIUS);
476
477         maxt = get_max_temp(enabled_sensors);
478         strmax = psensor_value_to_str(SENSOR_TYPE_TEMP,
479                                       maxt,
480                                       config->temperature_unit == CELSIUS);
481
482         et = get_graph_end_time_s(enabled_sensors);
483         bt = get_graph_begin_time_s(config, et);
484
485         str_btime = time_to_str(bt);
486         str_etime = time_to_str(et);
487
488         gtk_widget_get_allocation(w_graph, &galloc);
489         width = galloc.width;
490         info.width = galloc.width;
491         height = galloc.height;
492         info.height = height;
493
494
495         cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
496         cr = cairo_create(cst);
497
498         cairo_select_font_face(cr,
499                                "sans-serif",
500                                CAIRO_FONT_SLANT_NORMAL,
501                                CAIRO_FONT_WEIGHT_NORMAL);
502         cairo_set_font_size(cr, 10.0);
503
504         cairo_text_extents(cr, str_etime, &te_etime);
505         cairo_text_extents(cr, str_btime, &te_btime);
506         cairo_text_extents(cr, strmax, &te_max);
507         cairo_text_extents(cr, strmin, &te_min);
508
509         g_yoff = GRAPH_V_PADDING;
510         info.g_yoff = g_yoff;
511
512         g_height = height - GRAPH_V_PADDING;
513         if (te_etime.height > te_btime.height)
514                 g_height -= GRAPH_V_PADDING + te_etime.height + GRAPH_V_PADDING;
515         else
516                 g_height -= GRAPH_V_PADDING + te_btime.height + GRAPH_V_PADDING;
517
518         info.g_height = g_height;
519
520         if (te_min.width > te_max.width)
521                 g_xoff = (2 * GRAPH_H_PADDING) + te_max.width;
522         else
523                 g_xoff = (2 * GRAPH_H_PADDING) + te_min.width;
524
525         info.g_xoff = g_xoff;
526
527         g_width = width - g_xoff - GRAPH_H_PADDING;
528         info.g_width = g_width;
529
530         draw_graph_background(cr, config, &info);
531
532         /* Set the color for text drawing */
533         cairo_set_source_rgb(cr,
534                              theme_fg_color.red,
535                              theme_fg_color.green,
536                              theme_fg_color.blue);
537
538         /* draw graph begin time */
539         cairo_move_to(cr, g_xoff, height - GRAPH_V_PADDING);
540         cairo_show_text(cr, str_btime);
541         free(str_btime);
542
543         /* draw graph end time */
544         cairo_move_to(cr,
545                       width - te_etime.width - GRAPH_H_PADDING,
546                       height - GRAPH_V_PADDING);
547         cairo_show_text(cr, str_etime);
548         free(str_etime);
549
550         draw_background_lines(cr, mint, maxt, config, &info);
551
552         /* .. and finaly draws the temperature graphs */
553         if (bt && et) {
554                 sensor_cur = enabled_sensors;
555
556                 cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
557                 cairo_set_line_width(cr, 1);
558                 no_graphs = 1;
559                 while (*sensor_cur) {
560                         struct psensor *s = *sensor_cur;
561
562                         no_graphs = 0;
563                         if (s->type & SENSOR_TYPE_RPM) {
564                                 min = min_rpm;
565                                 max = max_rpm;
566                         } else if (s->type & SENSOR_TYPE_PERCENT) {
567                                 min = 0;
568                                 max = get_max_value(enabled_sensors,
569                                                     SENSOR_TYPE_PERCENT);
570                         } else {
571                                 min = mint;
572                                 max = maxt;
573                         }
574
575                         if (is_smooth_curves_enabled)
576                                 draw_sensor_smooth_curve(s, cr,
577                                                          min, max,
578                                                          bt, et,
579                                                          &info);
580                         else
581                                 draw_sensor_curve(s, cr,
582                                                   min, max,
583                                                   bt, et,
584                                                   &info);
585
586                         sensor_cur++;
587                 }
588
589                 if (no_graphs)
590                         display_no_graphs_warning(cr,
591                                                   g_xoff + 12,
592                                                   g_height / 2);
593         }
594
595         draw_left_region(cr, &info);
596         draw_right_region(cr, &info);
597
598         /* draw min and max temp */
599         cairo_set_source_rgb(cr,
600                              theme_fg_color.red,
601                              theme_fg_color.green,
602                              theme_fg_color.blue);
603
604         cairo_move_to(cr, GRAPH_H_PADDING, te_max.height + GRAPH_V_PADDING);
605         cairo_show_text(cr, strmax);
606         free(strmax);
607
608         cairo_move_to(cr,
609                       GRAPH_H_PADDING, height - (te_min.height / 2) - g_yoff);
610         cairo_show_text(cr, strmin);
611         free(strmin);
612
613         cr_pixmap = gdk_cairo_create(gtk_widget_get_window(w_graph));
614
615         if (cr_pixmap) {
616                 if (config->alpha_channel_enabled)
617                         cairo_set_operator(cr_pixmap, CAIRO_OPERATOR_SOURCE);
618
619                 cairo_set_source_surface(cr_pixmap, cst, 0, 0);
620                 cairo_paint(cr_pixmap);
621         }
622
623         free(enabled_sensors);
624
625         cairo_destroy(cr_pixmap);
626         cairo_surface_destroy(cst);
627         cairo_destroy(cr);
628 }
629
630 int compute_values_max_length(struct config *c)
631 {
632         int n, duration, interval;
633
634         duration = c->graph_monitoring_duration * 60;
635         interval = c->sensor_update_interval;
636
637         n = 3 + ceil((((double)duration) / interval) + 0.5) + 3;
638
639         return n;
640 }