~ubuntu-branches/ubuntu/wily/lxpanel/wily-proposed

« back to all changes in this revision

Viewing changes to plugins/dclock.c

  • Committer: Package Import Robot
  • Author(s): Julien Lavergne
  • Date: 2015-01-31 15:30:45 UTC
  • mfrom: (1.3.3)
  • mto: This revision was merged to the branch mainline in revision 45.
  • Revision ID: package-import@ubuntu.com-20150131153045-1r9i4602vrplnx3i
ImportĀ upstreamĀ versionĀ 0.7.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
 
21
#include <libfm/fm-gtk.h>
 
22
 
 
23
#include <time.h>
 
24
#include <sys/time.h>
 
25
#include <sys/types.h>
 
26
#include <stdio.h>
 
27
#include <stdlib.h>
 
28
#include <string.h>
 
29
#include <glib/gi18n.h>
 
30
 
 
31
#include "dbg.h"
 
32
 
 
33
#define DEFAULT_TIP_FORMAT    "%A %x"
 
34
#define DEFAULT_CLOCK_FORMAT  "%R"
 
35
 
 
36
/* Private context for digital clock plugin. */
 
37
typedef struct {
 
38
    GtkWidget * plugin;                         /* Back pointer to plugin */
 
39
    LXPanel * panel;
 
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) */
 
49
    gboolean center_text;
 
50
    guint timer;                                /* Timer for periodic update */
 
51
    enum {
 
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 */
 
60
} DClockPlugin;
 
61
 
 
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);
 
65
 
 
66
/* Handler for "map" signal on popup window. */
 
67
static void dclock_popup_map(GtkWidget * widget, DClockPlugin * dc)
 
68
{
 
69
    lxpanel_plugin_adjust_popup_position(widget, dc->plugin);
 
70
}
 
71
 
 
72
/* Display a window containing the standard calendar widget. */
 
73
static GtkWidget * dclock_create_calendar(DClockPlugin * dc)
 
74
{
 
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));
 
85
 
 
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));
 
89
 
 
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);
 
96
 
 
97
    /* Connect signals. */
 
98
    g_signal_connect(G_OBJECT(win), "map", G_CALLBACK(dclock_popup_map), dc);
 
99
 
 
100
    /* Return the widget. */
 
101
    return win;
 
102
}
 
103
 
 
104
/* Handler for "button-press-event" event from main widget. */
 
105
static gboolean dclock_button_press_event(GtkWidget * widget, GdkEventButton * evt, LXPanel * panel)
 
106
{
 
107
    DClockPlugin * dc = lxpanel_plugin_get_data(widget);
 
108
 
 
109
    /* If an action is set, execute it. */
 
110
    if (dc->action != NULL)
 
111
        fm_launch_command_simple(NULL, NULL, 0, dc->action, NULL);
 
112
 
 
113
    /* If no action is set, toggle the presentation of the calendar. */
 
114
    else
 
115
    {
 
116
        if (dc->calendar_window == NULL)
 
117
        {
 
118
            dc->calendar_window = dclock_create_calendar(dc);
 
119
            gtk_widget_show_all(dc->calendar_window);
 
120
        }
 
121
        else
 
122
        {
 
123
            gtk_widget_destroy(dc->calendar_window);
 
124
            dc->calendar_window = NULL;
 
125
        }
 
126
    }
 
127
    return TRUE;
 
128
}
 
129
 
 
130
/* Set the timer. */
 
131
static void dclock_timer_set(DClockPlugin * dc, struct timeval *current_time)
 
132
{
 
133
    int milliseconds = 1000;
 
134
 
 
135
    /* Get current time to millisecond resolution. */
 
136
    if (gettimeofday(current_time, NULL) >= 0)
 
137
    {
 
138
        /* Compute number of milliseconds until next second boundary. */
 
139
        milliseconds = 1000 - (current_time->tv_usec / 1000);
 
140
 
 
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)
 
144
        {
 
145
            time_t seconds = 60 - (current_time->tv_sec - (current_time->tv_sec / 60) * 60);
 
146
            milliseconds += seconds * 1000;
 
147
        }
 
148
    }
 
149
 
 
150
    /* Be defensive, and set the timer. */
 
151
    if (milliseconds <= 0)
 
152
        milliseconds = 1000;
 
153
    dc->timer = g_timeout_add(milliseconds, (GSourceFunc) dclock_update_display, (gpointer) dc);
 
154
}
 
155
 
 
156
/* Periodic timer callback.
 
157
 * Also used during initialization and configuration change to do a redraw. */
 
158
static gboolean dclock_update_display(DClockPlugin * dc)
 
159
{
 
160
    /* Determine the current time. */
 
161
    struct timeval now;
 
162
    struct tm * current_time;
 
163
 
 
164
    if (g_source_is_destroyed(g_main_current_source()))
 
165
        return FALSE;
 
166
 
 
167
    dclock_timer_set(dc, &now);
 
168
    current_time = localtime(&now.tv_sec);
 
169
 
 
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);
 
179
 
 
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)))
 
185
    {
 
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)
 
189
        {
 
190
            newlines_converted = g_strdup(clock_value); /* Just to get enough space for the converted result */
 
191
            char * p;
 
192
            char * q;
 
193
            for (p = clock_value, q = newlines_converted; *p != '\0'; p += 1)
 
194
            {
 
195
                if ((p[0] == '\\') && (p[1] == 'n'))
 
196
                {
 
197
                    *q++ = '\n';
 
198
                    p += 1;
 
199
                }
 
200
                else
 
201
                    *q++ = *p;
 
202
            }
 
203
            *q = '\0';
 
204
        }
 
205
 
 
206
        gchar * utf8 = g_locale_to_utf8(((newlines_converted != NULL) ? newlines_converted : clock_value), -1, NULL, NULL, NULL);
 
207
        if (utf8 != NULL)
 
208
        {
 
209
            lxpanel_draw_label_text(dc->panel, dc->clock_label, utf8, dc->bold, 1, TRUE);
 
210
            g_free(utf8);
 
211
        }
 
212
        g_free(newlines_converted);
 
213
    }
 
214
 
 
215
    /* Determine the content of the tooltip. */
 
216
    gchar * utf8 = g_locale_to_utf8(tooltip_value, -1, NULL, NULL, NULL);
 
217
    if (utf8 != NULL)
 
218
    {
 
219
        gtk_widget_set_tooltip_text(dc->plugin, utf8);
 
220
        g_free(utf8);
 
221
    }
 
222
 
 
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)
 
227
    {
 
228
        if (dc->prev_clock_value == NULL)
 
229
        {
 
230
            /* Initiate the experiment. */
 
231
            dc->prev_clock_value = g_strdup(clock_value);
 
232
            dc->prev_tooltip_value = g_strdup(tooltip_value);
 
233
        }
 
234
        else
 
235
        {
 
236
            if (((dc->icon_only) || (strcmp(dc->prev_clock_value, clock_value) == 0))
 
237
            && (strcmp(dc->prev_tooltip_value, tooltip_value) == 0))
 
238
            {
 
239
                dc->experiment_count += 1;
 
240
                if (dc->experiment_count > 3)
 
241
                {
 
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;
 
248
                }
 
249
            }
 
250
            else if (dc->expiration_interval == AWAITING_FIRST_CHANGE)
 
251
            {
 
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);
 
260
            }
 
261
            else
 
262
            {
 
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;
 
269
            }
 
270
        }
 
271
    }
 
272
 
 
273
    /* Reset the timer and return. */
 
274
    return FALSE;
 
275
}
 
276
 
 
277
/* Plugin constructor. */
 
278
static GtkWidget *dclock_constructor(LXPanel *panel, config_setting_t *settings)
 
279
{
 
280
    /* Allocate and initialize plugin context and set into Plugin private data pointer. */
 
281
    DClockPlugin * dc = g_new0(DClockPlugin, 1);
 
282
    GtkWidget * p;
 
283
    const char *str;
 
284
    int tmp_int;
 
285
 
 
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;
 
299
 
 
300
    /* Save construction pointers */
 
301
    dc->panel = panel;
 
302
    dc->settings = settings;
 
303
 
 
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);
 
307
 
 
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);
 
312
 
 
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);
 
321
 
 
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);
 
328
 
 
329
    /* Show the widget and return. */
 
330
    return p;
 
331
}
 
332
 
 
333
/* Plugin destructor. */
 
334
static void dclock_destructor(gpointer user_data)
 
335
{
 
336
    DClockPlugin * dc = user_data;
 
337
 
 
338
    /* Remove the timer. */
 
339
    if (dc->timer != 0)
 
340
        g_source_remove(dc->timer);
 
341
 
 
342
    /* Ensure that the calendar is dismissed. */
 
343
    if (dc->calendar_window != NULL)
 
344
        gtk_widget_destroy(dc->calendar_window);
 
345
 
 
346
    /* Deallocate all memory. */
 
347
    g_free(dc->clock_format);
 
348
    g_free(dc->tooltip_format);
 
349
    g_free(dc->action);
 
350
    g_free(dc->prev_clock_value);
 
351
    g_free(dc->prev_tooltip_value);
 
352
    g_free(dc);
 
353
}
 
354
 
 
355
/* Callback when the configuration dialog has recorded a configuration change. */
 
356
static gboolean dclock_apply_configuration(gpointer user_data)
 
357
{
 
358
    GtkWidget * p = user_data;
 
359
    DClockPlugin * dc = lxpanel_plugin_get_data(p);
 
360
    struct timeval now;
 
361
 
 
362
    /* stop the updater now */
 
363
    if (dc->timer)
 
364
        g_source_remove(dc->timer);
 
365
 
 
366
    /* Set up the icon or the label as the displayable widget. */
 
367
    if (dc->icon_only)
 
368
    {
 
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");
 
371
        }
 
372
        gtk_widget_show(dc->clock_icon);
 
373
        gtk_widget_hide(dc->clock_label);
 
374
    }
 
375
    else
 
376
    {
 
377
        gtk_widget_show(dc->clock_label);
 
378
        gtk_widget_hide(dc->clock_icon);
 
379
    }
 
380
 
 
381
    if (dc->center_text)
 
382
    {
 
383
        gtk_label_set_justify(GTK_LABEL(dc->clock_label), GTK_JUSTIFY_CENTER);
 
384
    }
 
385
    else
 
386
    {
 
387
        gtk_label_set_justify(GTK_LABEL(dc->clock_label), GTK_JUSTIFY_LEFT);
 
388
    }
 
389
 
 
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);
 
398
 
 
399
    /* Hide the calendar. */
 
400
    if (dc->calendar_window != NULL)
 
401
    {
 
402
        gtk_widget_destroy(dc->calendar_window);
 
403
        dc->calendar_window = NULL;
 
404
    }
 
405
 
 
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);
 
413
    return FALSE;
 
414
}
 
415
 
 
416
/* Callback when the configuration dialog is to be shown. */
 
417
static GtkWidget *dclock_configure(LXPanel *panel, GtkWidget *p)
 
418
{
 
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,
 
429
        NULL);
 
430
}
 
431
 
 
432
/* Callback when panel configuration changes. */
 
433
static void dclock_reconfigure(LXPanel *panel, GtkWidget *p)
 
434
{
 
435
    dclock_apply_configuration(p);
 
436
}
 
437
 
 
438
/* Plugin descriptor. */
 
439
LXPanelPluginInit lxpanel_static_plugin_dclock = {
 
440
    .name = N_("Digital Clock"),
 
441
    .description = N_("Display digital clock and tooltip"),
 
442
 
 
443
    .new_instance = dclock_constructor,
 
444
    .config = dclock_configure,
 
445
    .reconfigure = dclock_reconfigure,
 
446
    .button_press_event = dclock_button_press_event
 
447
};