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