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