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