Adding upstream version 0.9.0.
[debian/lxpanel.git] / plugins / dclock.c
CommitLineData
0688b017
AG
1/*
2 * Copyright (C) 2006-2008 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
3 * 2006 Jim Huang <jserv.tw@gmail.com>
4 * 2008 Fred Chien <fred@lxde.org>
5 * 2009 Ying-Chun Liu (PaulLiu) <grandpaul@gmail.com>
6 * 2009-2010 Marty Jack <martyj19@comcast.net>
7 * 2010 Julien Lavergne <julien.lavergne@gmail.com>
8 * 2012 Michael Rawson <michaelrawson76@gmail.com>
9 * 2012-2014 Henry Gebhardt <hsggebhardt@gmail.com>
10 * 2012 Piotr Sipika <Piotr.Sipika@gmail.com>
7a1c5048 11 * 2014-2016 Andriy Grytsenko <andrej@rep.kiev.ua>
0688b017
AG
12 *
13 * This file is a part of LXPanel project.
6cc5e1a6
DB
14 *
15 * This program is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 2 of the License, or
18 * (at your option) any later version.
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, write to the Free Software Foundation,
27 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
28 */
29
6b775dbb 30#include "plugin.h"
f7ecd6ce 31#include "misc.h"
6b775dbb
AG
32
33#include <libfm/fm-gtk.h>
34
6cc5e1a6
DB
35#include <time.h>
36#include <sys/time.h>
37#include <sys/types.h>
38#include <stdio.h>
39#include <stdlib.h>
40#include <string.h>
41#include <glib/gi18n.h>
6cc5e1a6 42
6cc5e1a6
DB
43#include "dbg.h"
44
45#define DEFAULT_TIP_FORMAT "%A %x"
46#define DEFAULT_CLOCK_FORMAT "%R"
47
2ba86315 48/* Private context for digital clock plugin. */
6cc5e1a6 49typedef struct {
6b775dbb
AG
50 GtkWidget * plugin; /* Back pointer to plugin */
51 LXPanel * panel;
52 config_setting_t *settings;
2ba86315
DB
53 GtkWidget * clock_label; /* Label containing clock value */
54 GtkWidget * clock_icon; /* Icon when icon_only */
55 GtkWidget * calendar_window; /* Calendar window, if it is being displayed */
56 char * clock_format; /* Format string for clock value */
57 char * tooltip_format; /* Format string for tooltip value */
58 char * action; /* Command to execute on a click */
59 gboolean bold; /* True if bold font */
60 gboolean icon_only; /* True if icon only (no clock value) */
514580cf 61 gboolean center_text;
2ba86315 62 guint timer; /* Timer for periodic update */
4652f59b
DB
63 enum {
64 AWAITING_FIRST_CHANGE, /* Experimenting to determine interval, waiting for first change */
65 AWAITING_SECOND_CHANGE, /* Experimenting to determine interval, waiting for second change */
66 ONE_SECOND_INTERVAL, /* Determined that one second interval is necessary */
67 ONE_MINUTE_INTERVAL /* Determined that one minute interval is sufficient */
6b775dbb 68 } expiration_interval;
4652f59b
DB
69 int experiment_count; /* Count of experiments that have been done to determine interval */
70 char * prev_clock_value; /* Previous value of clock */
71 char * prev_tooltip_value; /* Previous value of tooltip */
2ba86315
DB
72} DClockPlugin;
73
2ba86315 74static gboolean dclock_update_display(DClockPlugin * dc);
6b775dbb
AG
75static void dclock_destructor(gpointer user_data);
76static gboolean dclock_apply_configuration(gpointer user_data);
2ba86315
DB
77
78/* Display a window containing the standard calendar widget. */
32a67dc7 79static GtkWidget * dclock_create_calendar(DClockPlugin * dc)
6cc5e1a6 80{
2ba86315
DB
81 /* Create a new window. */
82 GtkWidget * win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
f7ecd6ce
AG
83 gint x, y;
84
6cc5e1a6
DB
85 gtk_window_set_default_size(GTK_WINDOW(win), 180, 180);
86 gtk_window_set_decorated(GTK_WINDOW(win), FALSE);
2ba86315 87 gtk_window_set_resizable(GTK_WINDOW(win), FALSE);
6cc5e1a6
DB
88 gtk_container_set_border_width(GTK_CONTAINER(win), 5);
89 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(win), TRUE);
90 gtk_window_set_skip_pager_hint(GTK_WINDOW(win), TRUE);
7a1c5048 91 gtk_window_set_type_hint(GTK_WINDOW(win), GDK_WINDOW_TYPE_HINT_DIALOG);
2ba86315
DB
92 gtk_window_stick(GTK_WINDOW(win));
93
94 /* Create a vertical box as a child of the window. */
95 GtkWidget * box = gtk_vbox_new(FALSE, 0);
7486d297 96 gtk_container_add(GTK_CONTAINER(win), GTK_WIDGET(box));
6cc5e1a6 97
2ba86315
DB
98 /* Create a standard calendar widget as a child of the vertical box. */
99 GtkWidget * calendar = gtk_calendar_new();
6b775dbb 100 gtk_calendar_set_display_options(
2ba86315
DB
101 GTK_CALENDAR(calendar),
102 GTK_CALENDAR_SHOW_WEEK_NUMBERS | GTK_CALENDAR_SHOW_DAY_NAMES | GTK_CALENDAR_SHOW_HEADING);
6b775dbb 103 gtk_box_pack_start(GTK_BOX(box), calendar, TRUE, TRUE, 0);
f7ecd6ce 104 gtk_widget_show_all(box);
6cc5e1a6 105
f7ecd6ce
AG
106 /* Preset the widget position right now to not move it across the screen */
107 lxpanel_plugin_popup_set_position_helper(dc->panel, dc->plugin, win, &x, &y);
108 gtk_window_move(GTK_WINDOW(win), x, y);
32a67dc7 109
2ba86315
DB
110 /* Return the widget. */
111 return win;
6cc5e1a6
DB
112}
113
2ba86315 114/* Handler for "button-press-event" event from main widget. */
6b775dbb 115static gboolean dclock_button_press_event(GtkWidget * widget, GdkEventButton * evt, LXPanel * panel)
6cc5e1a6 116{
7a1c5048
AG
117 DClockPlugin * dc;
118
119 if (evt->button != 1)
120 return FALSE;
6cc5e1a6 121
2ba86315 122 /* If an action is set, execute it. */
7a1c5048 123 dc = lxpanel_plugin_get_data(widget);
2ba86315 124 if (dc->action != NULL)
6b775dbb 125 fm_launch_command_simple(NULL, NULL, 0, dc->action, NULL);
2ba86315
DB
126
127 /* If no action is set, toggle the presentation of the calendar. */
128 else
129 {
130 if (dc->calendar_window == NULL)
131 {
32a67dc7 132 dc->calendar_window = dclock_create_calendar(dc);
2ba86315
DB
133 gtk_widget_show_all(dc->calendar_window);
134 }
135 else
136 {
137 gtk_widget_destroy(dc->calendar_window);
138 dc->calendar_window = NULL;
6cc5e1a6
DB
139 }
140 }
2ba86315 141 return TRUE;
6cc5e1a6
DB
142}
143
4652f59b 144/* Set the timer. */
6b775dbb 145static void dclock_timer_set(DClockPlugin * dc, struct timeval *current_time)
4652f59b
DB
146{
147 int milliseconds = 1000;
148
149 /* Get current time to millisecond resolution. */
6b775dbb 150 if (gettimeofday(current_time, NULL) >= 0)
4652f59b
DB
151 {
152 /* Compute number of milliseconds until next second boundary. */
6b775dbb 153 milliseconds = 1000 - (current_time->tv_usec / 1000);
4652f59b
DB
154
155 /* If the expiration interval is the minute boundary,
156 * add number of milliseconds after that until next minute boundary. */
157 if (dc->expiration_interval == ONE_MINUTE_INTERVAL)
158 {
6b775dbb 159 time_t seconds = 60 - (current_time->tv_sec - (current_time->tv_sec / 60) * 60);
4652f59b
DB
160 milliseconds += seconds * 1000;
161 }
162 }
163
164 /* Be defensive, and set the timer. */
165 if (milliseconds <= 0)
166 milliseconds = 1000;
167 dc->timer = g_timeout_add(milliseconds, (GSourceFunc) dclock_update_display, (gpointer) dc);
168}
169
2ba86315
DB
170/* Periodic timer callback.
171 * Also used during initialization and configuration change to do a redraw. */
172static gboolean dclock_update_display(DClockPlugin * dc)
6cc5e1a6 173{
2ba86315 174 /* Determine the current time. */
6b775dbb
AG
175 struct timeval now;
176 struct tm * current_time;
177
178 if (g_source_is_destroyed(g_main_current_source()))
179 return FALSE;
180
181 dclock_timer_set(dc, &now);
182 current_time = localtime(&now.tv_sec);
4652f59b
DB
183
184 /* Determine the content of the clock label and tooltip. */
185 char clock_value[64];
186 char tooltip_value[64];
187 clock_value[0] = '\0';
188 if (dc->clock_format != NULL)
189 strftime(clock_value, sizeof(clock_value), dc->clock_format, current_time);
190 tooltip_value[0] = '\0';
191 if (dc->tooltip_format != NULL)
192 strftime(tooltip_value, sizeof(tooltip_value), dc->tooltip_format, current_time);
6cc5e1a6 193
2ba86315 194 /* When we write the clock value, it causes the panel to do a full relayout.
4652f59b
DB
195 * Since this function may be called too often while the timing experiment is underway,
196 * we take the trouble to check if the string actually changed first. */
2ba86315 197 if (( ! dc->icon_only)
4652f59b 198 && ((dc->prev_clock_value == NULL) || (strcmp(dc->prev_clock_value, clock_value) != 0)))
2ba86315 199 {
2ba86315
DB
200 /* Convert "\n" escapes in the user's format string to newline characters. */
201 char * newlines_converted = NULL;
4652f59b 202 if (strstr(clock_value, "\\n") != NULL)
2ba86315 203 {
4652f59b 204 newlines_converted = g_strdup(clock_value); /* Just to get enough space for the converted result */
2ba86315
DB
205 char * p;
206 char * q;
4652f59b 207 for (p = clock_value, q = newlines_converted; *p != '\0'; p += 1)
2ba86315
DB
208 {
209 if ((p[0] == '\\') && (p[1] == 'n'))
210 {
211 *q++ = '\n';
212 p += 1;
213 }
214 else
215 *q++ = *p;
6cc5e1a6 216 }
2ba86315
DB
217 *q = '\0';
218 }
219
4652f59b 220 gchar * utf8 = g_locale_to_utf8(((newlines_converted != NULL) ? newlines_converted : clock_value), -1, NULL, NULL, NULL);
2ba86315
DB
221 if (utf8 != NULL)
222 {
6b775dbb 223 lxpanel_draw_label_text(dc->panel, dc->clock_label, utf8, dc->bold, 1, TRUE);
2ba86315
DB
224 g_free(utf8);
225 }
226 g_free(newlines_converted);
6cc5e1a6
DB
227 }
228
2ba86315 229 /* Determine the content of the tooltip. */
4652f59b 230 gchar * utf8 = g_locale_to_utf8(tooltip_value, -1, NULL, NULL, NULL);
2ba86315
DB
231 if (utf8 != NULL)
232 {
6b775dbb 233 gtk_widget_set_tooltip_text(dc->plugin, utf8);
2ba86315
DB
234 g_free(utf8);
235 }
4652f59b
DB
236
237 /* Conduct an experiment to see how often the value changes.
238 * Use this to decide whether we update the value every second or every minute.
239 * We need to account for the possibility that the experiment is being run when we cross a minute boundary. */
240 if (dc->expiration_interval < ONE_SECOND_INTERVAL)
241 {
242 if (dc->prev_clock_value == NULL)
243 {
244 /* Initiate the experiment. */
245 dc->prev_clock_value = g_strdup(clock_value);
246 dc->prev_tooltip_value = g_strdup(tooltip_value);
247 }
248 else
249 {
250 if (((dc->icon_only) || (strcmp(dc->prev_clock_value, clock_value) == 0))
251 && (strcmp(dc->prev_tooltip_value, tooltip_value) == 0))
252 {
253 dc->experiment_count += 1;
254 if (dc->experiment_count > 3)
255 {
256 /* No change within 3 seconds. Assume change no more often than once per minute. */
257 dc->expiration_interval = ONE_MINUTE_INTERVAL;
258 g_free(dc->prev_clock_value);
259 g_free(dc->prev_tooltip_value);
260 dc->prev_clock_value = NULL;
261 dc->prev_tooltip_value = NULL;
262 }
263 }
264 else if (dc->expiration_interval == AWAITING_FIRST_CHANGE)
265 {
266 /* We have a change at the beginning of the experiment, but we do not know when the next change might occur.
267 * Continue the experiment for 3 more seconds. */
268 dc->expiration_interval = AWAITING_SECOND_CHANGE;
269 dc->experiment_count = 0;
270 g_free(dc->prev_clock_value);
271 g_free(dc->prev_tooltip_value);
272 dc->prev_clock_value = g_strdup(clock_value);
273 dc->prev_tooltip_value = g_strdup(tooltip_value);
274 }
275 else
276 {
277 /* We have a second change. End the experiment. */
278 dc->expiration_interval = ((dc->experiment_count > 3) ? ONE_MINUTE_INTERVAL : ONE_SECOND_INTERVAL);
279 g_free(dc->prev_clock_value);
280 g_free(dc->prev_tooltip_value);
281 dc->prev_clock_value = NULL;
282 dc->prev_tooltip_value = NULL;
283 }
284 }
285 }
286
287 /* Reset the timer and return. */
4652f59b 288 return FALSE;
6cc5e1a6
DB
289}
290
2ba86315 291/* Plugin constructor. */
6b775dbb 292static GtkWidget *dclock_constructor(LXPanel *panel, config_setting_t *settings)
6cc5e1a6 293{
2ba86315
DB
294 /* Allocate and initialize plugin context and set into Plugin private data pointer. */
295 DClockPlugin * dc = g_new0(DClockPlugin, 1);
6b775dbb
AG
296 GtkWidget * p;
297 const char *str;
298 int tmp_int;
6cc5e1a6 299
2ba86315 300 /* Load parameters from the configuration file. */
6b775dbb
AG
301 if (config_setting_lookup_string(settings, "ClockFmt", &str))
302 dc->clock_format = g_strdup(str);
303 if (config_setting_lookup_string(settings, "TooltipFmt", &str))
304 dc->tooltip_format = g_strdup(str);
305 if (config_setting_lookup_string(settings, "Action", &str))
306 dc->action = g_strdup(str);
307 if (config_setting_lookup_int(settings, "BoldFont", &tmp_int))
308 dc->bold = tmp_int != 0;
309 if (config_setting_lookup_int(settings, "IconOnly", &tmp_int))
310 dc->icon_only = tmp_int != 0;
311 if (config_setting_lookup_int(settings, "CenterText", &tmp_int))
312 dc->center_text = tmp_int != 0;
313
314 /* Save construction pointers */
315 dc->panel = panel;
316 dc->settings = settings;
6cc5e1a6 317
2ba86315 318 /* Allocate top level widget and set into Plugin widget pointer. */
6b775dbb
AG
319 dc->plugin = p = gtk_event_box_new();
320 lxpanel_plugin_set_data(p, dc, dclock_destructor);
6cc5e1a6 321
2ba86315 322 /* Allocate a horizontal box as the child of the top level. */
514580cf 323 GtkWidget * hbox = gtk_hbox_new(TRUE, 0);
6b775dbb 324 gtk_container_add(GTK_CONTAINER(p), hbox);
2ba86315 325 gtk_widget_show(hbox);
6cc5e1a6 326
2ba86315
DB
327 /* Create a label and an image as children of the horizontal box.
328 * Only one of these is visible at a time, controlled by user preference. */
329 dc->clock_label = gtk_label_new(NULL);
330 gtk_misc_set_alignment(GTK_MISC(dc->clock_label), 0.5, 0.5);
331 gtk_misc_set_padding(GTK_MISC(dc->clock_label), 4, 0);
332 gtk_container_add(GTK_CONTAINER(hbox), dc->clock_label);
f7ecd6ce 333 dc->clock_icon = lxpanel_image_new_for_icon(panel, "clock", -1, NULL);
2ba86315 334 gtk_container_add(GTK_CONTAINER(hbox), dc->clock_icon);
6cc5e1a6 335
4652f59b
DB
336 /* Initialize the clock display. */
337 if (dc->clock_format == NULL)
aaccad27 338 dc->clock_format = g_strdup(_(DEFAULT_CLOCK_FORMAT));
4652f59b 339 if (dc->tooltip_format == NULL)
aaccad27 340 dc->tooltip_format = g_strdup(_(DEFAULT_TIP_FORMAT));
2ba86315 341 dclock_apply_configuration(p);
6cc5e1a6 342
2ba86315 343 /* Show the widget and return. */
8713e384 344 dc->timer = g_idle_add((GSourceFunc)dclock_update_display, dc);
6b775dbb 345 return p;
2ba86315 346}
6cc5e1a6 347
2ba86315 348/* Plugin destructor. */
6b775dbb 349static void dclock_destructor(gpointer user_data)
6cc5e1a6 350{
6b775dbb 351 DClockPlugin * dc = user_data;
6cc5e1a6 352
2ba86315
DB
353 /* Remove the timer. */
354 if (dc->timer != 0)
6cc5e1a6
DB
355 g_source_remove(dc->timer);
356
2ba86315
DB
357 /* Ensure that the calendar is dismissed. */
358 if (dc->calendar_window != NULL)
359 gtk_widget_destroy(dc->calendar_window);
7486d297 360
2ba86315
DB
361 /* Deallocate all memory. */
362 g_free(dc->clock_format);
363 g_free(dc->tooltip_format);
6cc5e1a6 364 g_free(dc->action);
4652f59b
DB
365 g_free(dc->prev_clock_value);
366 g_free(dc->prev_tooltip_value);
2ba86315 367 g_free(dc);
6cc5e1a6
DB
368}
369
2ba86315 370/* Callback when the configuration dialog has recorded a configuration change. */
6b775dbb 371static gboolean dclock_apply_configuration(gpointer user_data)
6cc5e1a6 372{
6b775dbb
AG
373 GtkWidget * p = user_data;
374 DClockPlugin * dc = lxpanel_plugin_get_data(p);
6b775dbb
AG
375
376 /* stop the updater now */
377 if (dc->timer)
378 g_source_remove(dc->timer);
2ba86315
DB
379
380 /* Set up the icon or the label as the displayable widget. */
381 if (dc->icon_only)
382 {
2ba86315
DB
383 gtk_widget_show(dc->clock_icon);
384 gtk_widget_hide(dc->clock_label);
385 }
386 else
387 {
388 gtk_widget_show(dc->clock_label);
389 gtk_widget_hide(dc->clock_icon);
390 }
6b775dbb 391
514580cf
DB
392 if (dc->center_text)
393 {
6b775dbb 394 gtk_label_set_justify(GTK_LABEL(dc->clock_label), GTK_JUSTIFY_CENTER);
514580cf
DB
395 }
396 else
397 {
6b775dbb 398 gtk_label_set_justify(GTK_LABEL(dc->clock_label), GTK_JUSTIFY_LEFT);
514580cf 399 }
2ba86315 400
4652f59b
DB
401 /* Rerun the experiment to determine update interval and update the display. */
402 g_free(dc->prev_clock_value);
403 g_free(dc->prev_tooltip_value);
404 dc->expiration_interval = AWAITING_FIRST_CHANGE;
405 dc->experiment_count = 0;
406 dc->prev_clock_value = NULL;
407 dc->prev_tooltip_value = NULL;
8713e384 408 dc->timer = g_idle_add((GSourceFunc)dclock_update_display, dc);
32a67dc7
DB
409
410 /* Hide the calendar. */
411 if (dc->calendar_window != NULL)
412 {
413 gtk_widget_destroy(dc->calendar_window);
414 dc->calendar_window = NULL;
415 }
6b775dbb
AG
416
417 /* Save configuration */
418 config_group_set_string(dc->settings, "ClockFmt", dc->clock_format);
419 config_group_set_string(dc->settings, "TooltipFmt", dc->tooltip_format);
420 config_group_set_string(dc->settings, "Action", dc->action);
421 config_group_set_int(dc->settings, "BoldFont", dc->bold);
422 config_group_set_int(dc->settings, "IconOnly", dc->icon_only);
423 config_group_set_int(dc->settings, "CenterText", dc->center_text);
424 return FALSE;
6cc5e1a6
DB
425}
426
2ba86315 427/* Callback when the configuration dialog is to be shown. */
6b775dbb 428static GtkWidget *dclock_configure(LXPanel *panel, GtkWidget *p)
6cc5e1a6 429{
6b775dbb
AG
430 DClockPlugin * dc = lxpanel_plugin_get_data(p);
431 return lxpanel_generic_config_dlg(_("Digital Clock"), panel,
432 dclock_apply_configuration, p,
2ba86315
DB
433 _("Clock Format"), &dc->clock_format, CONF_TYPE_STR,
434 _("Tooltip Format"), &dc->tooltip_format, CONF_TYPE_STR,
6b775dbb 435 _("Format codes: man 3 strftime; %n for line break"), NULL, CONF_TYPE_TRIM,
2ba86315
DB
436 _("Action when clicked (default: display calendar)"), &dc->action, CONF_TYPE_STR,
437 _("Bold font"), &dc->bold, CONF_TYPE_BOOL,
438 _("Tooltip only"), &dc->icon_only, CONF_TYPE_BOOL,
514580cf 439 _("Center text"), &dc->center_text, CONF_TYPE_BOOL,
2ba86315 440 NULL);
6cc5e1a6
DB
441}
442
2ba86315 443/* Plugin descriptor. */
6b775dbb
AG
444LXPanelPluginInit lxpanel_static_plugin_dclock = {
445 .name = N_("Digital Clock"),
446 .description = N_("Display digital clock and tooltip"),
447
448 .new_instance = dclock_constructor,
449 .config = dclock_configure,
6b775dbb 450 .button_press_event = dclock_button_press_event
6cc5e1a6 451};