2
* Copyright (c) 2006-2014 LxDE Developers, see the file AUTHORS for details.
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.
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.
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.
21
#include <libfm/fm-gtk.h>
25
#include <sys/types.h>
29
#include <glib/gi18n.h>
33
#define DEFAULT_TIP_FORMAT "%A %x"
34
#define DEFAULT_CLOCK_FORMAT "%R"
36
/* Private context for digital clock plugin. */
38
GtkWidget * plugin; /* Back pointer to plugin */
40
config_setting_t *settings;
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) */
50
guint timer; /* Timer for periodic update */
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 */
56
} expiration_interval;
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 */
62
static gboolean dclock_update_display(DClockPlugin * dc);
63
static void dclock_destructor(gpointer user_data);
64
static gboolean dclock_apply_configuration(gpointer user_data);
66
/* Handler for "map" signal on popup window. */
67
static void dclock_popup_map(GtkWidget * widget, DClockPlugin * dc)
69
lxpanel_plugin_adjust_popup_position(widget, dc->plugin);
72
/* Display a window containing the standard calendar widget. */
73
static GtkWidget * dclock_create_calendar(DClockPlugin * dc)
75
/* Create a new window. */
76
GtkWidget * win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
77
gtk_window_set_default_size(GTK_WINDOW(win), 180, 180);
78
gtk_window_set_decorated(GTK_WINDOW(win), FALSE);
79
gtk_window_set_resizable(GTK_WINDOW(win), FALSE);
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);
83
gtk_window_set_type_hint(GTK_WINDOW(win), GDK_WINDOW_TYPE_HINT_UTILITY);
84
gtk_window_stick(GTK_WINDOW(win));
86
/* Create a vertical box as a child of the window. */
87
GtkWidget * box = gtk_vbox_new(FALSE, 0);
88
gtk_container_add(GTK_CONTAINER(win), GTK_WIDGET(box));
90
/* Create a standard calendar widget as a child of the vertical box. */
91
GtkWidget * calendar = gtk_calendar_new();
92
gtk_calendar_set_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(GTK_BOX(box), calendar, TRUE, TRUE, 0);
97
/* Connect signals. */
98
g_signal_connect(G_OBJECT(win), "map", G_CALLBACK(dclock_popup_map), dc);
100
/* Return the widget. */
104
/* Handler for "button-press-event" event from main widget. */
105
static gboolean dclock_button_press_event(GtkWidget * widget, GdkEventButton * evt, LXPanel * panel)
107
DClockPlugin * dc = lxpanel_plugin_get_data(widget);
109
/* If an action is set, execute it. */
110
if (dc->action != NULL)
111
fm_launch_command_simple(NULL, NULL, 0, dc->action, NULL);
113
/* If no action is set, toggle the presentation of the calendar. */
116
if (dc->calendar_window == NULL)
118
dc->calendar_window = dclock_create_calendar(dc);
119
gtk_widget_show_all(dc->calendar_window);
123
gtk_widget_destroy(dc->calendar_window);
124
dc->calendar_window = NULL;
131
static void dclock_timer_set(DClockPlugin * dc, struct timeval *current_time)
133
int milliseconds = 1000;
135
/* Get current time to millisecond resolution. */
136
if (gettimeofday(current_time, NULL) >= 0)
138
/* Compute number of milliseconds until next second boundary. */
139
milliseconds = 1000 - (current_time->tv_usec / 1000);
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)
145
time_t seconds = 60 - (current_time->tv_sec - (current_time->tv_sec / 60) * 60);
146
milliseconds += seconds * 1000;
150
/* Be defensive, and set the timer. */
151
if (milliseconds <= 0)
153
dc->timer = g_timeout_add(milliseconds, (GSourceFunc) dclock_update_display, (gpointer) dc);
156
/* Periodic timer callback.
157
* Also used during initialization and configuration change to do a redraw. */
158
static gboolean dclock_update_display(DClockPlugin * dc)
160
/* Determine the current time. */
162
struct tm * current_time;
164
if (g_source_is_destroyed(g_main_current_source()))
167
dclock_timer_set(dc, &now);
168
current_time = localtime(&now.tv_sec);
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);
180
/* When we write the clock value, it causes the panel to do a full relayout.
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. */
183
if (( ! dc->icon_only)
184
&& ((dc->prev_clock_value == NULL) || (strcmp(dc->prev_clock_value, clock_value) != 0)))
186
/* Convert "\n" escapes in the user's format string to newline characters. */
187
char * newlines_converted = NULL;
188
if (strstr(clock_value, "\\n") != NULL)
190
newlines_converted = g_strdup(clock_value); /* Just to get enough space for the converted result */
193
for (p = clock_value, q = newlines_converted; *p != '\0'; p += 1)
195
if ((p[0] == '\\') && (p[1] == 'n'))
206
gchar * utf8 = g_locale_to_utf8(((newlines_converted != NULL) ? newlines_converted : clock_value), -1, NULL, NULL, NULL);
209
lxpanel_draw_label_text(dc->panel, dc->clock_label, utf8, dc->bold, 1, TRUE);
212
g_free(newlines_converted);
215
/* Determine the content of the tooltip. */
216
gchar * utf8 = g_locale_to_utf8(tooltip_value, -1, NULL, NULL, NULL);
219
gtk_widget_set_tooltip_text(dc->plugin, utf8);
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)
228
if (dc->prev_clock_value == NULL)
230
/* Initiate the experiment. */
231
dc->prev_clock_value = g_strdup(clock_value);
232
dc->prev_tooltip_value = g_strdup(tooltip_value);
236
if (((dc->icon_only) || (strcmp(dc->prev_clock_value, clock_value) == 0))
237
&& (strcmp(dc->prev_tooltip_value, tooltip_value) == 0))
239
dc->experiment_count += 1;
240
if (dc->experiment_count > 3)
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;
250
else if (dc->expiration_interval == AWAITING_FIRST_CHANGE)
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);
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;
273
/* Reset the timer and return. */
277
/* Plugin constructor. */
278
static GtkWidget *dclock_constructor(LXPanel *panel, config_setting_t *settings)
280
/* Allocate and initialize plugin context and set into Plugin private data pointer. */
281
DClockPlugin * dc = g_new0(DClockPlugin, 1);
286
/* Load parameters from the configuration file. */
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;
300
/* Save construction pointers */
302
dc->settings = settings;
304
/* Allocate top level widget and set into Plugin widget pointer. */
305
dc->plugin = p = gtk_event_box_new();
306
lxpanel_plugin_set_data(p, dc, dclock_destructor);
308
/* Allocate a horizontal box as the child of the top level. */
309
GtkWidget * hbox = gtk_hbox_new(TRUE, 0);
310
gtk_container_add(GTK_CONTAINER(p), hbox);
311
gtk_widget_show(hbox);
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);
322
/* Initialize the clock display. */
323
if (dc->clock_format == NULL)
324
dc->clock_format = g_strdup(_(DEFAULT_CLOCK_FORMAT));
325
if (dc->tooltip_format == NULL)
326
dc->tooltip_format = g_strdup(_(DEFAULT_TIP_FORMAT));
327
dclock_apply_configuration(p);
329
/* Show the widget and return. */
333
/* Plugin destructor. */
334
static void dclock_destructor(gpointer user_data)
336
DClockPlugin * dc = user_data;
338
/* Remove the timer. */
340
g_source_remove(dc->timer);
342
/* Ensure that the calendar is dismissed. */
343
if (dc->calendar_window != NULL)
344
gtk_widget_destroy(dc->calendar_window);
346
/* Deallocate all memory. */
347
g_free(dc->clock_format);
348
g_free(dc->tooltip_format);
350
g_free(dc->prev_clock_value);
351
g_free(dc->prev_tooltip_value);
355
/* Callback when the configuration dialog has recorded a configuration change. */
356
static gboolean dclock_apply_configuration(gpointer user_data)
358
GtkWidget * p = user_data;
359
DClockPlugin * dc = lxpanel_plugin_get_data(p);
362
/* stop the updater now */
364
g_source_remove(dc->timer);
366
/* Set up the icon or the label as the displayable widget. */
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");
372
gtk_widget_show(dc->clock_icon);
373
gtk_widget_hide(dc->clock_label);
377
gtk_widget_show(dc->clock_label);
378
gtk_widget_hide(dc->clock_icon);
383
gtk_label_set_justify(GTK_LABEL(dc->clock_label), GTK_JUSTIFY_CENTER);
387
gtk_label_set_justify(GTK_LABEL(dc->clock_label), GTK_JUSTIFY_LEFT);
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;
397
dclock_timer_set(dc, &now);
399
/* Hide the calendar. */
400
if (dc->calendar_window != NULL)
402
gtk_widget_destroy(dc->calendar_window);
403
dc->calendar_window = NULL;
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);
416
/* Callback when the configuration dialog is to be shown. */
417
static GtkWidget *dclock_configure(LXPanel *panel, GtkWidget *p)
419
DClockPlugin * dc = lxpanel_plugin_get_data(p);
420
return lxpanel_generic_config_dlg(_("Digital Clock"), panel,
421
dclock_apply_configuration, p,
422
_("Clock Format"), &dc->clock_format, CONF_TYPE_STR,
423
_("Tooltip Format"), &dc->tooltip_format, CONF_TYPE_STR,
424
_("Format codes: man 3 strftime; %n for line break"), NULL, CONF_TYPE_TRIM,
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,
428
_("Center text"), &dc->center_text, CONF_TYPE_BOOL,
432
/* Callback when panel configuration changes. */
433
static void dclock_reconfigure(LXPanel *panel, GtkWidget *p)
435
dclock_apply_configuration(p);
438
/* Plugin descriptor. */
439
LXPanelPluginInit lxpanel_static_plugin_dclock = {
440
.name = N_("Digital Clock"),
441
.description = N_("Display digital clock and tooltip"),
443
.new_instance = dclock_constructor,
444
.config = dclock_configure,
445
.reconfigure = dclock_reconfigure,
446
.button_press_event = dclock_button_press_event