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