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