diff options
Diffstat (limited to 'applets/clock')
-rw-r--r-- | applets/clock/Makefile.am | 27 | ||||
-rw-r--r-- | applets/clock/calendar-client.c | 2095 | ||||
-rw-r--r-- | applets/clock/calendar-client.h | 151 | ||||
-rw-r--r-- | applets/clock/calendar-debug.h | 50 | ||||
-rw-r--r-- | applets/clock/calendar-sources.c | 503 | ||||
-rw-r--r-- | applets/clock/calendar-sources.h | 64 | ||||
-rw-r--r-- | applets/clock/calendar-window.c | 1148 | ||||
-rw-r--r-- | applets/clock/calendar-window.h | 3 | ||||
-rw-r--r-- | applets/clock/clock-face.c | 17 | ||||
-rw-r--r-- | applets/clock/clock-face.h | 1 | ||||
-rw-r--r-- | applets/clock/clock-location-tile.c | 34 | ||||
-rw-r--r-- | applets/clock/clock-location.c | 16 | ||||
-rw-r--r-- | applets/clock/clock-map.c | 27 | ||||
-rw-r--r-- | applets/clock/clock-map.h | 1 | ||||
-rw-r--r-- | applets/clock/clock-sunpos.c | 1 | ||||
-rw-r--r-- | applets/clock/clock.c | 640 | ||||
-rw-r--r-- | applets/clock/clock.ui | 736 | ||||
-rw-r--r-- | applets/clock/org.mate.panel.applet.clock.gschema.xml.in | 10 | ||||
-rw-r--r-- | applets/clock/system-timezone.c | 15 | ||||
-rw-r--r-- | applets/clock/system-timezone.h | 1 |
20 files changed, 4940 insertions, 600 deletions
diff --git a/applets/clock/Makefile.am b/applets/clock/Makefile.am index 55f94e93..5a932422 100644 --- a/applets/clock/Makefile.am +++ b/applets/clock/Makefile.am @@ -35,6 +35,15 @@ CLOCK_SOURCES = \ set-timezone.h \ $(BUILT_SOURCES) +if HAVE_EDS +CLOCK_SOURCES += \ + calendar-client.c \ + calendar-client.h \ + calendar-sources.c \ + calendar-sources.h \ + calendar-debug.h +endif + CLOCK_CPPFLAGS = \ $(AM_CPPFLAGS) \ $(LIBMATE_PANEL_APPLET_CFLAGS) \ @@ -44,6 +53,10 @@ CLOCK_CPPFLAGS = \ -DMATELOCALEDIR=\""$(datadir)/locale"\" \ -DMATEWEATHER_I_KNOW_THIS_IS_UNSTABLE +if HAVE_EDS +CLOCK_CPPFLAGS += $(EDS_CFLAGS) +endif + CLOCK_LDADD = \ ../../libmate-panel-applet/libmate-panel-applet-4.la \ $(CLOCK_LIBS) \ @@ -51,6 +64,10 @@ CLOCK_LDADD = \ libsystem-timezone.la \ -lm +if HAVE_EDS +CLOCK_LDADD += $(EDS_LIBS) +endif + test_system_timezone_SOURCES = \ test-system-timezone.c test_system_timezone_LDADD = libsystem-timezone.la @@ -67,6 +84,14 @@ libclock_applet_la_LIBADD = $(CLOCK_LDADD) libclock_applet_la_LDFLAGS = -module -avoid-version libclock_applet_la_CFLAGS = $(AM_CFLAGS) $(libclock_applet_la_OBJECTS): $(BUILT_SOURCES) + +if ENABLE_WAYLAND +libclock_applet_la_LIBADD += \ + $(WAYLAND_LIBS) + +AM_CPPFLAGS += \ + $(WAYLAND_CFLAGS) +endif else APPLET_IN_PROCESS = false APPLET_LOCATION = $(libexecdir)/clock-applet @@ -138,9 +163,9 @@ $(applet_in_files): $(applet_in_files).in Makefile $(applet_DATA): $(applet_in_files) $(AM_V_GEN) $(MSGFMT) --desktop --keyword= --keyword=Name --keyword=Description --template $< -d $(top_srcdir)/po -o $@ +service_in_files = org.mate.panel.applet.ClockAppletFactory.service.in if !CLOCK_INPROCESS servicedir = $(datadir)/dbus-1/services -service_in_files = org.mate.panel.applet.ClockAppletFactory.service.in service_DATA = $(service_in_files:.service.in=.service) org.mate.panel.applet.ClockAppletFactory.service: $(service_in_files) diff --git a/applets/clock/calendar-client.c b/applets/clock/calendar-client.c new file mode 100644 index 00000000..8bd9a2f3 --- /dev/null +++ b/applets/clock/calendar-client.c @@ -0,0 +1,2095 @@ +/* + * Copyright (C) 2004 Free Software Foundation, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Mark McLoughlin <[email protected]> + * William Jon McCann <[email protected]> + * Martin Grimme <[email protected]> + * Christian Kellner <[email protected]> + */ + +#include <config.h> + +#include "calendar-client.h" + +#include <libintl.h> +#include <string.h> +#define HANDLE_LIBICAL_MEMORY + +#ifdef HAVE_EDS + +#include <libecal/libecal.h> +#include "calendar-sources.h" +#include "system-timezone.h" +#endif + +#undef CALENDAR_ENABLE_DEBUG +#include "calendar-debug.h" + +#ifndef _ +#define _(x) gettext(x) +#endif + +#ifndef N_ +#define N_(x) x +#endif + +#ifdef HAVE_EDS + +typedef struct _CalendarClientQuery CalendarClientQuery; +typedef struct _CalendarClientSource CalendarClientSource; + +struct _CalendarClientQuery +{ + ECalClientView *view; + GHashTable *events; +}; + +struct _CalendarClientSource +{ + CalendarClient *client; + ECalClient *source; + + CalendarClientQuery completed_query; + CalendarClientQuery in_progress_query; + + guint changed_signal_id; + + guint query_completed : 1; + guint query_in_progress : 1; +}; + +struct _CalendarClientPrivate +{ + CalendarSources *calendar_sources; + + GSList *appointment_sources; + GSList *task_sources; + + ICalTimezone *zone; + + guint zone_listener; + GSettings *calendar_settings; + + guint day; + guint month; + guint year; +}; + +static void calendar_client_finalize (GObject *object); +static void calendar_client_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void calendar_client_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +static GSList *calendar_client_update_sources_list (CalendarClient *client, + GSList *sources, + GList *esources, + guint changed_signal_id); +static void calendar_client_appointment_sources_changed (CalendarClient *client); +static void calendar_client_task_sources_changed (CalendarClient *client); + +static void calendar_client_stop_query (CalendarClient *client, + CalendarClientSource *source, + CalendarClientQuery *query); +static void calendar_client_start_query (CalendarClient *client, + CalendarClientSource *source, + const char *query); + +static void calendar_client_source_finalize (CalendarClientSource *source); +static void calendar_client_query_finalize (CalendarClientQuery *query); + +static void +calendar_client_update_appointments (CalendarClient *client); +static void +calendar_client_update_tasks (CalendarClient *client); + +enum +{ + PROP_O, + PROP_DAY, + PROP_MONTH, + PROP_YEAR +}; + +enum +{ + APPOINTMENTS_CHANGED, + TASKS_CHANGED, + LAST_SIGNAL +}; + +static guint signals [LAST_SIGNAL] = { 0, }; + +G_DEFINE_TYPE_WITH_PRIVATE (CalendarClient, calendar_client, G_TYPE_OBJECT) + +static void +calendar_client_class_init (CalendarClientClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->finalize = calendar_client_finalize; + gobject_class->set_property = calendar_client_set_property; + gobject_class->get_property = calendar_client_get_property; + + g_object_class_install_property (gobject_class, + PROP_DAY, + g_param_spec_uint ("day", + "Day", + "The currently monitored day between 1 and 31 (0 denotes unset)", + 0, G_MAXUINT, 0, + G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, + PROP_MONTH, + g_param_spec_uint ("month", + "Month", + "The currently monitored month between 0 and 11", + 0, G_MAXUINT, 0, + G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, + PROP_YEAR, + g_param_spec_uint ("year", + "Year", + "The currently monitored year", + 0, G_MAXUINT, 0, + G_PARAM_READWRITE)); + + signals [APPOINTMENTS_CHANGED] = + g_signal_new ("appointments-changed", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (CalendarClientClass, tasks_changed), + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); + + signals [TASKS_CHANGED] = + g_signal_new ("tasks-changed", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (CalendarClientClass, tasks_changed), + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); +} + +/* Timezone code adapted from evolution/calendar/gui/calendar-config.c */ +/* The current timezone, e.g. "Europe/London". It may be NULL, in which case + you should assume UTC. */ +static gchar * +calendar_client_config_get_timezone (GSettings *calendar_settings) +{ + gchar *timezone = NULL; + + if (calendar_settings == NULL) + return NULL; + + /* Check if we can list the keys to see if timezone exists */ + gchar **keys = g_settings_list_keys (calendar_settings); + gboolean has_timezone = FALSE; + + for (gint i = 0; keys[i] != NULL; i++) { + if (g_strcmp0 (keys[i], "timezone") == 0) { + has_timezone = TRUE; + break; + } + } + g_strfreev (keys); + + if (has_timezone) { + timezone = g_settings_get_string (calendar_settings, "timezone"); + } + + return timezone; +} + +static ICalTimezone * +calendar_client_config_get_icaltimezone (CalendarClient *client) +{ + gchar *location = NULL; + ICalTimezone *zone = NULL; + + if (client->priv->calendar_settings != NULL) + location = calendar_client_config_get_timezone (client->priv->calendar_settings); + + if (!location) { + /* MATE panel doesn't store timezone in GSettings + * Since libical timezone lookup often fails, just use UTC + * The display code will handle local time conversion */ + return i_cal_timezone_get_utc_timezone (); + } + + zone = i_cal_timezone_get_builtin_timezone (location); + g_free (location); + + return zone; +} + +static void +calendar_client_set_timezone (CalendarClient *client) +{ + GList *list, *link; + + client->priv->zone = calendar_client_config_get_icaltimezone (client); + + list = calendar_sources_get_appointment_clients (client->priv->calendar_sources); + for (link = list; link != NULL; link = g_list_next (link)) + { + ECalClient *cal = E_CAL_CLIENT (link->data); + + e_cal_client_set_default_timezone (cal, client->priv->zone); + } + g_list_free (list); +} + +static void +calendar_client_timezone_changed_cb (GSettings *calendar_settings, + const gchar *key, + CalendarClient *client) +{ + calendar_client_set_timezone (client); +} + +static void +load_calendars (CalendarClient *client, + CalendarEventType type) +{ + GSList *l, *clients; + + switch (type) + { + case CALENDAR_EVENT_APPOINTMENT: + clients = client->priv->appointment_sources; + break; + case CALENDAR_EVENT_TASK: + clients = client->priv->task_sources; + break; + case CALENDAR_EVENT_ALL: + default: + g_assert_not_reached (); + } + + for (l = clients; l != NULL; l = l->next) + { + if (type == CALENDAR_EVENT_APPOINTMENT) + calendar_client_update_appointments (client); + else if (type == CALENDAR_EVENT_TASK) + calendar_client_update_tasks (client); + } +} + +static void +calendar_client_init (CalendarClient *client) +{ + GList *list; + + client->priv = calendar_client_get_instance_private (client); + + client->priv->calendar_sources = calendar_sources_get (); + + list = calendar_sources_get_appointment_clients (client->priv->calendar_sources); + client->priv->appointment_sources = + calendar_client_update_sources_list (client, NULL, list, signals [APPOINTMENTS_CHANGED]); + g_list_free (list); + + list = calendar_sources_get_task_clients (client->priv->calendar_sources); + client->priv->task_sources = calendar_client_update_sources_list (client, NULL, list, signals [TASKS_CHANGED]); + g_list_free (list); + + /* set the timezone before loading the clients */ + calendar_client_set_timezone (client); + load_calendars (client, CALENDAR_EVENT_APPOINTMENT); + load_calendars (client, CALENDAR_EVENT_TASK); + + g_signal_connect_swapped (client->priv->calendar_sources, + "appointment-sources-changed", + G_CALLBACK (calendar_client_appointment_sources_changed), + client); + g_signal_connect_swapped (client->priv->calendar_sources, + "task-sources-changed", + G_CALLBACK (calendar_client_task_sources_changed), + client); + + if (client->priv->calendar_settings != NULL) + client->priv->zone_listener = g_signal_connect (client->priv->calendar_settings, + "changed::timezone", + G_CALLBACK (calendar_client_timezone_changed_cb), + client); + + client->priv->day = G_MAXUINT; + client->priv->month = G_MAXUINT; + client->priv->year = G_MAXUINT; +} + +static void +calendar_client_finalize (GObject *object) +{ + CalendarClient *client = CALENDAR_CLIENT (object); + GSList *l; + + if (client->priv->zone_listener) + { + g_signal_handler_disconnect (client->priv->calendar_settings, + client->priv->zone_listener); + client->priv->zone_listener = 0; + } + + if (client->priv->calendar_settings) + g_object_unref (client->priv->calendar_settings); + client->priv->calendar_settings = NULL; + + for (l = client->priv->appointment_sources; l; l = l->next) + { + calendar_client_source_finalize (l->data); + g_free (l->data); + } + g_slist_free (client->priv->appointment_sources); + client->priv->appointment_sources = NULL; + + for (l = client->priv->task_sources; l; l = l->next) + { + calendar_client_source_finalize (l->data); + g_free (l->data); + } + g_slist_free (client->priv->task_sources); + client->priv->task_sources = NULL; + + if (client->priv->calendar_sources) + g_object_unref (client->priv->calendar_sources); + client->priv->calendar_sources = NULL; + + G_OBJECT_CLASS (calendar_client_parent_class)->finalize (object); +} + +static void +calendar_client_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CalendarClient *client = CALENDAR_CLIENT (object); + + switch (prop_id) + { + case PROP_DAY: + calendar_client_select_day (client, g_value_get_uint (value)); + break; + case PROP_MONTH: + calendar_client_select_month (client, + g_value_get_uint (value), + client->priv->year); + break; + case PROP_YEAR: + calendar_client_select_month (client, + client->priv->month, + g_value_get_uint (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +calendar_client_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CalendarClient *client = CALENDAR_CLIENT (object); + + switch (prop_id) + { + case PROP_DAY: + g_value_set_uint (value, client->priv->day); + break; + case PROP_MONTH: + g_value_set_uint (value, client->priv->month); + break; + case PROP_YEAR: + g_value_set_uint (value, client->priv->year); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +CalendarClient * +calendar_client_new (GSettings *settings) +{ + CalendarClient *client = g_object_new (CALENDAR_TYPE_CLIENT, NULL); + + /* Use the provided MATE panel settings instead of Evolution settings */ + if (settings) { + client->priv->calendar_settings = g_object_ref (settings); + } else { + /* Fallback to Evolution calendar settings if available */ + GSettingsSchemaSource *schema_source = g_settings_schema_source_get_default(); + const gchar *evolution_calendar_schema = "org.gnome.evolution.calendar"; + GSettingsSchema *schema = g_settings_schema_source_lookup (schema_source, evolution_calendar_schema, FALSE); + if (schema) { + client->priv->calendar_settings = g_settings_new (evolution_calendar_schema); + g_settings_schema_unref (schema); + } else { + /* No Evolution settings available, calendar_settings will remain NULL */ + client->priv->calendar_settings = NULL; + } + } + + return client; +} + +/* @day and @month can happily be out of range as + * mktime() will normalize them correctly. From mktime(3): + * + * "If structure members are outside their legal interval, + * they will be normalized (so that, e.g., 40 October is + * changed into 9 November)." + * + * "What?", you say, "Something useful in libc?" + */ +static inline time_t +make_time_for_day_begin (int day, + int month, + int year) +{ + struct tm localtime_tm = { 0, }; + + localtime_tm.tm_mday = day; + localtime_tm.tm_mon = month; + localtime_tm.tm_year = year - 1900; + localtime_tm.tm_isdst = -1; + + return mktime (&localtime_tm); +} + +static inline char * +make_isodate_for_day_begin (int day, + int month, + int year) +{ + time_t utctime; + + utctime = make_time_for_day_begin (day, month, year); + + return utctime != -1 ? isodate_from_time_t (utctime) : NULL; +} + +static time_t +get_time_from_property (ICalComponent *icomp, + ICalPropertyKind prop_kind, + ICalTime * (* get_prop_func) (ICalProperty *prop), + ICalTimezone *default_zone) +{ + ICalProperty *prop; + ICalTime *ical_time; + ICalParameter *param; + ICalTimezone *timezone; + time_t retval; + + prop = i_cal_component_get_first_property (icomp, prop_kind); + if (!prop) + return 0; + + param = i_cal_property_get_first_parameter (prop, I_CAL_TZID_PARAMETER); + ical_time = get_prop_func (prop); + g_object_unref (prop); + + if (param) + { + const char *tzid; + + tzid = i_cal_parameter_get_tzid (param); + timezone = i_cal_timezone_get_builtin_timezone_from_tzid (tzid); + g_object_unref (param); + + /* If timezone lookup failed, fall back to default zone */ + if (!timezone) { + timezone = default_zone; + } + } + else if (i_cal_time_is_utc (ical_time)) + { + timezone = i_cal_timezone_get_utc_timezone (); + } + else + { + timezone = default_zone; + } + + retval = i_cal_time_as_timet_with_zone (ical_time, timezone); + + g_object_unref (ical_time); + + return retval; +} + +static char * +get_component_uid (ICalComponent *component) +{ + return g_strdup (i_cal_component_get_uid (component)); +} + +static char * +get_component_rid (ICalComponent *component) +{ + ICalProperty *prop; + ICalTime *time; + char *rid; + + prop = i_cal_component_get_first_property (component, I_CAL_RECURRENCEID_PROPERTY); + if (!prop) + return NULL; + + time = i_cal_property_get_recurrenceid (prop); + g_object_unref (prop); + + if (!i_cal_time_is_valid_time (time) || i_cal_time_is_null_time (time)) + { + g_object_unref (time); + return NULL; + } + + rid = g_strdup (i_cal_time_as_ical_string (time)); + g_object_unref (time); + + return rid; +} + +static char * +get_component_summary (ICalComponent *component) +{ + ICalProperty *prop; + char *summary; + + prop = i_cal_component_get_first_property (component, I_CAL_SUMMARY_PROPERTY); + if (!prop) + return NULL; + + summary = g_strdup (i_cal_property_get_summary (prop)); + g_object_unref (prop); + + return summary; +} + +static char * +get_component_description (ICalComponent *component) +{ + ICalProperty *prop; + char *description; + + prop = i_cal_component_get_first_property (component, I_CAL_DESCRIPTION_PROPERTY); + if (!prop) + return NULL; + + description = g_strdup (i_cal_property_get_description (prop)); + g_object_unref (prop); + + return description; +} + +static inline time_t +get_component_start_time (ICalComponent *component, + ICalTimezone *default_zone) +{ + return get_time_from_property (component, + I_CAL_DTSTART_PROPERTY, + i_cal_property_get_dtstart, + default_zone); +} + +static inline time_t +get_component_end_time (ICalComponent *component, + ICalTimezone *default_zone) +{ + return get_time_from_property (component, + I_CAL_DTEND_PROPERTY, + i_cal_property_get_dtend, + default_zone); +} + +static gboolean +get_component_is_all_day (ICalComponent *component, + time_t start_time, + ICalTimezone *default_zone) +{ + ICalTime *dtstart; + struct tm *start_tm; + time_t end_time; + ICalProperty *prop; + ICalDuration *duration; + gboolean is_all_day; + + dtstart = i_cal_component_get_dtstart (component); + + if (dtstart && i_cal_time_is_date (dtstart)) + { + g_object_unref (dtstart); + return TRUE; + } + + g_object_unref (dtstart); + + start_tm = gmtime (&start_time); + if (start_tm->tm_sec != 0 || + start_tm->tm_min != 0 || + start_tm->tm_hour != 0) + return FALSE; + + if ((end_time = get_component_end_time (component, default_zone))) + return (end_time - start_time) % 86400 == 0; + + prop = i_cal_component_get_first_property (component, I_CAL_DURATION_PROPERTY); + if (!prop) + return FALSE; + + duration = i_cal_property_get_duration (prop); + g_object_unref (prop); + + is_all_day = i_cal_duration_as_int (duration) % 86400 == 0; + g_object_unref (duration); + + return is_all_day; +} + +static inline time_t +get_component_due_time (ICalComponent *component, + ICalTimezone *default_zone) +{ + return get_time_from_property (component, + I_CAL_DUE_PROPERTY, + i_cal_property_get_due, + default_zone); +} + +static guint +get_component_percent_complete (ICalComponent *component) +{ + ICalPropertyStatus status; + ICalProperty *prop; + int percent_complete; + + status = i_cal_component_get_status (component); + if (status == I_CAL_STATUS_COMPLETED) + return 100; + + prop = i_cal_component_get_first_property (component, I_CAL_COMPLETED_PROPERTY); + + if (prop) + { + g_object_unref (prop); + return 100; + } + + prop = i_cal_component_get_first_property (component, I_CAL_PERCENTCOMPLETE_PROPERTY); + if (!prop) + return 0; + + percent_complete = i_cal_property_get_percentcomplete (prop); + g_object_unref (prop); + + return CLAMP (percent_complete, 0, 100); +} + +static inline time_t +get_component_completed_time (ICalComponent *component, + ICalTimezone *default_zone) +{ + return get_time_from_property (component, + I_CAL_COMPLETED_PROPERTY, + i_cal_property_get_completed, + default_zone); +} + +static int +get_component_priority (ICalComponent *component) +{ + ICalProperty *prop; + int priority; + + prop = i_cal_component_get_first_property (component, I_CAL_PRIORITY_PROPERTY); + if (!prop) + return -1; + + priority = i_cal_property_get_priority (prop); + g_object_unref (prop); + + return priority; +} + +static char * +get_source_color (ECalClient *esource) +{ + ESource *source; + ECalClientSourceType source_type; + ESourceSelectable *extension; + const gchar *extension_name; + + g_return_val_if_fail (E_IS_CAL_CLIENT (esource), NULL); + + source = e_client_get_source (E_CLIENT (esource)); + source_type = e_cal_client_get_source_type (esource); + + switch (source_type) + { + case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: + extension_name = E_SOURCE_EXTENSION_CALENDAR; + break; + case E_CAL_CLIENT_SOURCE_TYPE_TASKS: + extension_name = E_SOURCE_EXTENSION_TASK_LIST; + break; + case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: + case E_CAL_CLIENT_SOURCE_TYPE_LAST: + default: + g_return_val_if_reached (NULL); + } + + extension = e_source_get_extension (source, extension_name); + + return e_source_selectable_dup_color (extension); +} + +static gchar * +get_source_backend_name (ECalClient *esource) +{ + ESource *source; + ECalClientSourceType source_type; + ESourceBackend *extension; + const gchar *extension_name; + + g_return_val_if_fail (E_IS_CAL_CLIENT (esource), NULL); + + source = e_client_get_source (E_CLIENT (esource)); + source_type = e_cal_client_get_source_type (esource); + + switch (source_type) + { + case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: + extension_name = E_SOURCE_EXTENSION_CALENDAR; + break; + case E_CAL_CLIENT_SOURCE_TYPE_TASKS: + extension_name = E_SOURCE_EXTENSION_TASK_LIST; + break; + case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: + case E_CAL_CLIENT_SOURCE_TYPE_LAST: + default: + g_return_val_if_reached (NULL); + } + + extension = e_source_get_extension (source, extension_name); + + return e_source_backend_dup_backend_name (extension); +} + +static inline gboolean +calendar_appointment_equal (CalendarAppointment *a, + CalendarAppointment *b) +{ + GSList *la, *lb; + + if (g_slist_length (a->occurrences) != g_slist_length (b->occurrences)) + return FALSE; + + for (la = a->occurrences, lb = b->occurrences; la && lb; la = la->next, lb = lb->next) + { + CalendarOccurrence *oa = la->data; + CalendarOccurrence *ob = lb->data; + + if (oa->start_time != ob->start_time || + oa->end_time != ob->end_time) + return FALSE; + } + + return + g_strcmp0 (a->uid, b->uid) == 0 && + g_strcmp0 (a->backend_name, b->backend_name) == 0 && + g_strcmp0 (a->summary, b->summary) == 0 && + g_strcmp0 (a->description, b->description) == 0 && + g_strcmp0 (a->color_string, b->color_string) == 0 && + a->start_time == b->start_time && + a->end_time == b->end_time && + a->is_all_day == b->is_all_day; +} + +static void +calendar_appointment_copy (CalendarAppointment *appointment, + CalendarAppointment *appointment_copy) +{ + GSList *l; + + g_assert (appointment != NULL); + g_assert (appointment_copy != NULL); + + appointment_copy->occurrences = g_slist_copy (appointment->occurrences); + for (l = appointment_copy->occurrences; l; l = l->next) + { + CalendarOccurrence *occurrence = l->data; + CalendarOccurrence *occurrence_copy; + + occurrence_copy = g_new0 (CalendarOccurrence, 1); + occurrence_copy->start_time = occurrence->start_time; + occurrence_copy->end_time = occurrence->end_time; + + l->data = occurrence_copy; + } + + appointment_copy->uid = g_strdup (appointment->uid); + appointment_copy->backend_name = g_strdup (appointment->backend_name); + appointment_copy->summary = g_strdup (appointment->summary); + appointment_copy->description = g_strdup (appointment->description); + appointment_copy->color_string = g_strdup (appointment->color_string); + appointment_copy->start_time = appointment->start_time; + appointment_copy->end_time = appointment->end_time; + appointment_copy->is_all_day = appointment->is_all_day; +} + +static void +calendar_appointment_finalize (CalendarAppointment *appointment) +{ + GSList *l; + + for (l = appointment->occurrences; l; l = l->next) + g_free (l->data); + g_slist_free (appointment->occurrences); + appointment->occurrences = NULL; + + g_free (appointment->uid); + appointment->uid = NULL; + + g_free (appointment->rid); + appointment->rid = NULL; + + g_free (appointment->backend_name); + appointment->backend_name = NULL; + + g_free (appointment->summary); + appointment->summary = NULL; + + g_free (appointment->description); + appointment->description = NULL; + + g_free (appointment->color_string); + appointment->color_string = NULL; + + appointment->start_time = 0; + appointment->is_all_day = FALSE; +} + +static void +calendar_appointment_init (CalendarAppointment *appointment, + ICalComponent *component, + CalendarClientSource *source, + ICalTimezone *default_zone) +{ + appointment->uid = get_component_uid (component); + appointment->rid = get_component_rid (component); + appointment->backend_name = get_source_backend_name (source->source); + appointment->summary = get_component_summary (component); + appointment->description = get_component_description (component); + appointment->color_string = get_source_color (source->source); + appointment->start_time = get_component_start_time (component, default_zone); + appointment->end_time = get_component_end_time (component, default_zone); + appointment->is_all_day = get_component_is_all_day (component, + appointment->start_time, + default_zone); +} + +static inline gboolean +calendar_task_equal (CalendarTask *a, + CalendarTask *b) +{ + return + g_strcmp0 (a->uid, b->uid) == 0 && + g_strcmp0 (a->summary, b->summary) == 0 && + g_strcmp0 (a->description, b->description) == 0 && + g_strcmp0 (a->color_string, b->color_string) == 0 && + a->start_time == b->start_time && + a->due_time == b->due_time && + a->percent_complete == b->percent_complete && + a->completed_time == b->completed_time && + a->priority == b->priority; +} + +static void +calendar_task_copy (CalendarTask *task, + CalendarTask *task_copy) +{ + g_assert (task != NULL); + g_assert (task_copy != NULL); + + task_copy->uid = g_strdup (task->uid); + task_copy->summary = g_strdup (task->summary); + task_copy->description = g_strdup (task->description); + task_copy->color_string = g_strdup (task->color_string); + task_copy->start_time = task->start_time; + task_copy->due_time = task->due_time; + task_copy->percent_complete = task->percent_complete; + task_copy->completed_time = task->completed_time; + task_copy->priority = task->priority; +} + +static void +calendar_task_finalize (CalendarTask *task) +{ + g_free (task->uid); + task->uid = NULL; + + g_free (task->summary); + task->summary = NULL; + + g_free (task->description); + task->description = NULL; + + g_free (task->color_string); + task->color_string = NULL; + + task->percent_complete = 0; +} + +static void +calendar_task_init (CalendarTask *task, + ICalComponent *component, + CalendarClientSource *source, + ICalTimezone *default_zone) +{ + task->uid = get_component_uid (component); + task->summary = get_component_summary (component); + task->description = get_component_description (component); + task->color_string = get_source_color (source->source); + task->start_time = get_component_start_time (component, default_zone); + task->due_time = get_component_due_time (component, default_zone); + task->percent_complete = get_component_percent_complete (component); + task->completed_time = get_component_completed_time (component, default_zone); + task->priority = get_component_priority (component); +} + +void +calendar_event_free (CalendarEvent *event) +{ + switch (event->type) + { + case CALENDAR_EVENT_APPOINTMENT: + calendar_appointment_finalize (CALENDAR_APPOINTMENT (event)); + break; + case CALENDAR_EVENT_TASK: + calendar_task_finalize (CALENDAR_TASK (event)); + break; + case CALENDAR_EVENT_ALL: + default: + g_assert_not_reached (); + break; + } + + g_free (event); +} + +static CalendarEvent * +calendar_event_new (ICalComponent *component, + CalendarClientSource *source, + ICalTimezone *default_zone) +{ + CalendarEvent *event; + ICalComponentKind component_kind; + + event = g_new0 (CalendarEvent, 1); + component_kind = i_cal_component_isa (component); + + if (component_kind == I_CAL_VEVENT_COMPONENT) + { + event->type = CALENDAR_EVENT_APPOINTMENT; + calendar_appointment_init (CALENDAR_APPOINTMENT (event), + component, source, default_zone); + } + else if (component_kind == I_CAL_VTODO_COMPONENT) + { + event->type = CALENDAR_EVENT_TASK; + calendar_task_init (CALENDAR_TASK (event), + component, source, default_zone); + } + else + { + g_warning ("Unknown calendar component type: %d\n", component_kind); + g_free (event); + + return NULL; + } + + return event; +} + +static CalendarEvent * +calendar_event_copy (CalendarEvent *event) +{ + CalendarEvent *retval; + + if (!event) + return NULL; + + retval = g_new0 (CalendarEvent, 1); + + retval->type = event->type; + + switch (event->type) + { + case CALENDAR_EVENT_APPOINTMENT: + calendar_appointment_copy (CALENDAR_APPOINTMENT (event), + CALENDAR_APPOINTMENT (retval)); + break; + case CALENDAR_EVENT_TASK: + calendar_task_copy (CALENDAR_TASK (event), + CALENDAR_TASK (retval)); + break; + case CALENDAR_EVENT_ALL: + default: + g_assert_not_reached (); + break; + } + + return retval; +} + +static char * +calendar_event_get_uid (CalendarEvent *event) +{ + switch (event->type) + { + case CALENDAR_EVENT_APPOINTMENT: + return g_strdup_printf ("%s%s", CALENDAR_APPOINTMENT (event)->uid, CALENDAR_APPOINTMENT (event)->rid ? CALENDAR_APPOINTMENT (event)->rid : ""); + break; + case CALENDAR_EVENT_TASK: + return g_strdup (CALENDAR_TASK (event)->uid); + break; + case CALENDAR_EVENT_ALL: + default: + g_assert_not_reached (); + break; + } + + return NULL; +} + +static gboolean +calendar_event_equal (CalendarEvent *a, + CalendarEvent *b) +{ + if (!a && !b) + return TRUE; + + if ((a && !b) || (!a && b)) + return FALSE; + + if (a->type != b->type) + return FALSE; + + switch (a->type) + { + case CALENDAR_EVENT_APPOINTMENT: + return calendar_appointment_equal (CALENDAR_APPOINTMENT (a), + CALENDAR_APPOINTMENT (b)); + case CALENDAR_EVENT_TASK: + return calendar_task_equal (CALENDAR_TASK (a), + CALENDAR_TASK (b)); + case CALENDAR_EVENT_ALL: + default: + break; + } + + g_assert_not_reached (); + + return FALSE; +} + +static inline void +calendar_event_debug_dump (CalendarEvent *event) +{ +#ifdef CALENDAR_ENABLE_DEBUG + switch (event->type) + { + case CALENDAR_EVENT_APPOINTMENT: + { + char *start_str; + char *end_str; + GSList *l; + + start_str = CALENDAR_APPOINTMENT (event)->start_time ? + isodate_from_time_t (CALENDAR_APPOINTMENT (event)->start_time) : + g_strdup ("(undefined)"); + end_str = CALENDAR_APPOINTMENT (event)->end_time ? + isodate_from_time_t (CALENDAR_APPOINTMENT (event)->end_time) : + g_strdup ("(undefined)"); + + g_free (start_str); + g_free (end_str); + + for (l = CALENDAR_APPOINTMENT (event)->occurrences; l; l = l->next) + { + CalendarOccurrence *occurrence = l->data; + + start_str = occurrence->start_time ? + isodate_from_time_t (occurrence->start_time) : + g_strdup ("(undefined)"); + + end_str = occurrence->end_time ? + isodate_from_time_t (occurrence->end_time) : + g_strdup ("(undefined)"); + + g_free (start_str); + g_free (end_str); + } + } + break; + case CALENDAR_EVENT_TASK: + { + char *start_str; + char *due_str; + char *completed_str; + + start_str = CALENDAR_TASK (event)->start_time ? + isodate_from_time_t (CALENDAR_TASK (event)->start_time) : + g_strdup ("(undefined)"); + due_str = CALENDAR_TASK (event)->due_time ? + isodate_from_time_t (CALENDAR_TASK (event)->due_time) : + g_strdup ("(undefined)"); + completed_str = CALENDAR_TASK (event)->completed_time ? + isodate_from_time_t (CALENDAR_TASK (event)->completed_time) : + g_strdup ("(undefined)"); + + g_free (completed_str); + } + break; + default: + g_assert_not_reached (); + break; + } +#endif +} + +static inline CalendarClientQuery * +goddamn_this_is_crack (CalendarClientSource *source, + ECalClientView *view, + gboolean *emit_signal) +{ + g_assert (view != NULL); + + if (source->completed_query.view == view) + { + if (emit_signal) + *emit_signal = TRUE; + return &source->completed_query; + } + else if (source->in_progress_query.view == view) + { + if (emit_signal) + *emit_signal = FALSE; + return &source->in_progress_query; + } + + g_assert_not_reached (); + + return NULL; +} + +static void +calendar_client_query_finalize (CalendarClientQuery *query) +{ + if (query->view) + g_object_unref (query->view); + query->view = NULL; + + if (query->events) + g_hash_table_destroy (query->events); + query->events = NULL; +} + +static void +calendar_client_stop_query (CalendarClient *client, + CalendarClientSource *source, + CalendarClientQuery *query) +{ + if (query == &source->in_progress_query) + { + g_assert (source->query_in_progress != FALSE); + + source->query_in_progress = FALSE; + } + else if (query == &source->completed_query) + { + g_assert (source->query_completed != FALSE); + + source->query_completed = FALSE; + } + else + g_assert_not_reached (); + + calendar_client_query_finalize (query); +} + +typedef struct { + CalendarClient *client; + CalendarClientSource *source; + time_t start_time; + time_t end_time; + gboolean events_changed; + ICalTimezone *system_timezone; +} InstanceGenerationData; + +static gboolean +calendar_client_instance_cb (ICalComponent *icomp, + ICalTime *instance_start, + ICalTime *instance_end, + gpointer user_data, + GCancellable *cancellable, + GError **error) +{ + InstanceGenerationData *data = user_data; + CalendarEvent *event; + CalendarEvent *old_event; + char *uid; + time_t start_time_t, end_time_t; + + /* Convert instance times from their timezone to local time */ + ICalTimezone *event_tz = i_cal_time_get_timezone(instance_start); + + ICalTime *local_start = i_cal_time_clone(instance_start); + ICalTime *local_end = i_cal_time_clone(instance_end); + + /* Convert to local timezone */ + if (event_tz && data->system_timezone && event_tz != data->system_timezone) { + i_cal_time_convert_timezone(local_start, event_tz, data->system_timezone); + i_cal_time_convert_timezone(local_end, event_tz, data->system_timezone); + } + + start_time_t = i_cal_time_as_timet (local_start); + end_time_t = i_cal_time_as_timet (local_end); + + g_object_unref(local_start); + g_object_unref(local_end); + + /* Create event from the component */ + event = calendar_event_new (icomp, data->source, data->client->priv->zone); + if (!event) + return TRUE; + + /* Override the times with the instance times (already converted to local timezone) */ + if (event->type == CALENDAR_EVENT_APPOINTMENT) { + CALENDAR_APPOINTMENT (event)->start_time = start_time_t; + CALENDAR_APPOINTMENT (event)->end_time = end_time_t; + + /* Create a single occurrence for this instance */ + CalendarOccurrence *occurrence = g_new0 (CalendarOccurrence, 1); + occurrence->start_time = start_time_t; + occurrence->end_time = end_time_t; + CALENDAR_APPOINTMENT (event)->occurrences = g_slist_prepend (NULL, occurrence); + } + + uid = calendar_event_get_uid (event); + old_event = g_hash_table_lookup (data->source->in_progress_query.events, uid); + + if (!calendar_event_equal (event, old_event)) { + calendar_event_debug_dump (event); + g_hash_table_replace (data->source->in_progress_query.events, uid, event); + data->events_changed = TRUE; + } else { + calendar_event_free (event); + g_free (uid); + } + + return TRUE; +} + +static void +calendar_client_start_query (CalendarClient *client, + CalendarClientSource *source, + const char *query) +{ + time_t month_begin, month_end; + GSList *objects = NULL; + GError *error = NULL; + InstanceGenerationData instance_data; + + /* Validate that client is properly initialized */ + if (client->priv->month == G_MAXUINT || client->priv->year == G_MAXUINT) { + return; + } + + /* Calculate time range */ + month_begin = make_time_for_day_begin (1, client->priv->month, client->priv->year); + + /* Handle year rollover when month is December (11) */ + if (client->priv->month == 11) { /* December */ + month_end = make_time_for_day_begin (1, 0, client->priv->year + 1); /* January next year */ + } else { + month_end = make_time_for_day_begin (1, client->priv->month + 1, client->priv->year); + } + + /* Validate time range */ + if (month_begin == -1 || month_end == -1) { + g_warning ("Invalid time range: month_begin=%ld, month_end=%ld", (long)month_begin, (long)month_end); + calendar_client_stop_query (client, source, &source->in_progress_query); + return; + } + + if (source->query_in_progress) + calendar_client_stop_query (client, source, &source->in_progress_query); + + source->query_in_progress = TRUE; + source->in_progress_query.view = NULL; /* No view needed for instance generation */ + source->in_progress_query.events = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) calendar_event_free); + + /* Get all objects for the month using query */ + if (!e_cal_client_get_object_list_sync (source->source, query, &objects, NULL, &error)) { + g_warning ("Error getting calendar objects: %s", error->message); + g_error_free (error); + calendar_client_stop_query (client, source, &source->in_progress_query); + return; + } + + /* Get system timezone once for all instances */ + SystemTimezone *systz = system_timezone_new(); + const char *system_tz_name = system_timezone_get(systz); + ICalTimezone *system_timezone = i_cal_timezone_get_builtin_timezone(system_tz_name); + g_object_unref(systz); + + /* Set up instance generation data */ + instance_data.client = client; + instance_data.source = source; + instance_data.start_time = month_begin; + instance_data.end_time = month_end; + instance_data.events_changed = FALSE; + instance_data.system_timezone = system_timezone; + + /* Generate instances for each object with automatic timezone conversion */ + for (GSList *l = objects; l; l = l->next) { + ICalComponent *component = l->data; + + /* Some instances of recurring events may yield negative months - I think these are safe to skip */ + if (month_begin < 0 || month_end < 0) { + continue; + } + + e_cal_client_generate_instances_for_object_sync (source->source, + component, + month_begin, + month_end, + NULL, /* cancellable */ + calendar_client_instance_cb, + &instance_data); + } + + g_slist_free_full (objects, g_object_unref); + + /* Query is now completed */ + calendar_client_query_finalize (&source->completed_query); + source->completed_query = source->in_progress_query; + source->query_completed = TRUE; + + source->query_in_progress = FALSE; + source->in_progress_query.view = NULL; + source->in_progress_query.events = NULL; + + /* Emit signal to capture changed events */ + if (instance_data.events_changed) { + g_signal_emit (source->client, source->changed_signal_id, 0); + } +} + +static void +calendar_client_update_appointments (CalendarClient *client) +{ + GSList *l; + char *query; + char *month_begin; + char *month_end; + + if (client->priv->month == G_MAXUINT || client->priv->year == G_MAXUINT) + return; + + month_begin = make_isodate_for_day_begin (1, + client->priv->month, + client->priv->year); + + month_end = make_isodate_for_day_begin (1, + client->priv->month + 1, + client->priv->year); + + query = g_strdup_printf ("occur-in-time-range? (make-time \"%s\") " + "(make-time \"%s\")", + month_begin, month_end); + + for (l = client->priv->appointment_sources; l; l = l->next) + { + CalendarClientSource *cs = l->data; + + calendar_client_start_query (client, cs, query); + } + + g_free (month_begin); + g_free (month_end); + g_free (query); +} + +/* FIXME: + * perhaps we should use evo's "hide_completed_tasks" pref? + */ +static void +calendar_client_update_tasks (CalendarClient *client) +{ + GSList *l; + char *query; + +#ifdef FIX_BROKEN_TASKS_QUERY + /* FIXME: this doesn't work for tasks without a start or + * due date + * Look at filter_task() to see the behaviour we + * want. + */ + + char *day_begin; + char *day_end; + + if (client->priv->day == G_MAXUINT || + client->priv->month == G_MAXUINT || + client->priv->year == G_MAXUINT) + return; + + day_begin = make_isodate_for_day_begin (client->priv->day, + client->priv->month, + client->priv->year); + + day_end = make_isodate_for_day_begin (client->priv->day + 1, + client->priv->month, + client->priv->year); + if (!day_begin || !day_end) + { + g_warning ("Cannot run query with invalid date: %dd %dy %dm\n", + client->priv->day, + client->priv->month, + client->priv->year); + g_free (day_begin); + g_free (day_end); + return; + } + + query = g_strdup_printf ("(and (occur-in-time-range? (make-time \"%s\") " + "(make-time \"%s\")) " + "(or (not is-completed?) " + "(and (is-completed?) " + "(not (completed-before? (make-time \"%s\"))))))", + day_begin, day_end, day_begin); +#else + query = g_strdup ("#t"); +#endif /* FIX_BROKEN_TASKS_QUERY */ + + for (l = client->priv->task_sources; l; l = l->next) + { + CalendarClientSource *cs = l->data; + + calendar_client_start_query (client, cs, query); + } + +#ifdef FIX_BROKEN_TASKS_QUERY + g_free (day_begin); + g_free (day_end); +#endif + g_free (query); +} + +static void +calendar_client_source_finalize (CalendarClientSource *source) +{ + source->client = NULL; + + if (source->source) { + g_object_unref (source->source); + } + source->source = NULL; + + calendar_client_query_finalize (&source->completed_query); + calendar_client_query_finalize (&source->in_progress_query); + + source->query_completed = FALSE; + source->query_in_progress = FALSE; +} + +static int +compare_calendar_sources (CalendarClientSource *s1, + CalendarClientSource *s2) +{ + return (s1->source == s2->source) ? 0 : 1; +} + +static GSList * +calendar_client_update_sources_list (CalendarClient *client, + GSList *sources, + GList *esources, + guint changed_signal_id) +{ + GList *link; + GSList *retval, *l; + + retval = NULL; + + for (link = esources; link != NULL; link = g_list_next (link)) + { + CalendarClientSource dummy_source; + CalendarClientSource *new_source; + GSList *s; + ECalClient *esource = link->data; + + dummy_source.source = esource; + + if ((s = g_slist_find_custom (sources, + &dummy_source, + (GCompareFunc) compare_calendar_sources))) + { + new_source = s->data; + sources = g_slist_delete_link (sources, s); + } + else + { + new_source = g_new0 (CalendarClientSource, 1); + new_source->client = client; + new_source->source = g_object_ref (esource); + new_source->changed_signal_id = changed_signal_id; + } + + retval = g_slist_prepend (retval, new_source); + } + + for (l = sources; l; l = l->next) + { + CalendarClientSource *source = l->data; + + calendar_client_source_finalize (source); + g_free (source); + } + g_slist_free (sources); + + return retval; +} + +static void +calendar_client_appointment_sources_changed (CalendarClient *client) +{ + GList *list; + + list = calendar_sources_get_appointment_clients (client->priv->calendar_sources); + + client->priv->appointment_sources = calendar_client_update_sources_list (client, + client->priv->appointment_sources, + list, + signals [APPOINTMENTS_CHANGED]); + + load_calendars (client, CALENDAR_EVENT_APPOINTMENT); + calendar_client_update_appointments (client); + + g_list_free (list); +} + +static void +calendar_client_task_sources_changed (CalendarClient *client) +{ + GList *list; + + list = calendar_sources_get_task_clients (client->priv->calendar_sources); + + client->priv->task_sources = calendar_client_update_sources_list (client, + client->priv->task_sources, + list, + signals [TASKS_CHANGED]); + + load_calendars (client, CALENDAR_EVENT_TASK); + calendar_client_update_tasks (client); + + g_list_free (list); +} + +void +calendar_client_get_date (CalendarClient *client, + guint *year, + guint *month, + guint *day) +{ + g_return_if_fail (CALENDAR_IS_CLIENT (client)); + + if (year) + *year = client->priv->year; + + if (month) + *month = client->priv->month; + + if (day) + *day = client->priv->day; +} + +void +calendar_client_select_month (CalendarClient *client, + guint month, + guint year) +{ + g_return_if_fail (CALENDAR_IS_CLIENT (client)); + g_return_if_fail (month <= 11); + + if (client->priv->year != year || client->priv->month != month) + { + client->priv->month = month; + client->priv->year = year; + + calendar_client_update_appointments (client); + calendar_client_update_tasks (client); + + g_object_freeze_notify (G_OBJECT (client)); + g_object_notify (G_OBJECT (client), "month"); + g_object_notify (G_OBJECT (client), "year"); + g_object_thaw_notify (G_OBJECT (client)); + } +} + +void +calendar_client_select_day (CalendarClient *client, + guint day) +{ + g_return_if_fail (CALENDAR_IS_CLIENT (client)); + g_return_if_fail (day <= 31); + + if (client->priv->day != day) + { + client->priv->day = day; + + /* don't need to update appointments unless + * the selected month changes + */ +#ifdef FIX_BROKEN_TASKS_QUERY + calendar_client_update_tasks (client); +#endif + + g_object_notify (G_OBJECT (client), "day"); + } +} + +typedef struct +{ + CalendarClient *client; + GSList *events; + time_t start_time; + time_t end_time; +} FilterData; + +typedef void (* CalendarEventFilterFunc) (const char *uid, + CalendarEvent *event, + FilterData *filter_data); + +static void +filter_appointment (const char *uid, + CalendarEvent *event, + FilterData *filter_data) +{ + GSList *occurrences, *l; + + if (event->type != CALENDAR_EVENT_APPOINTMENT) + return; + + occurrences = CALENDAR_APPOINTMENT (event)->occurrences; + CALENDAR_APPOINTMENT (event)->occurrences = NULL; + + for (l = occurrences; l; l = l->next) + { + CalendarOccurrence *occurrence = l->data; + time_t start_time = occurrence->start_time; + time_t end_time = occurrence->end_time; + + if ((start_time >= filter_data->start_time && + start_time < filter_data->end_time) || + (start_time <= filter_data->start_time && + (end_time - 1) > filter_data->start_time)) + { + CalendarEvent *new_event; + + new_event = calendar_event_copy (event); + + CALENDAR_APPOINTMENT (new_event)->start_time = occurrence->start_time; + CALENDAR_APPOINTMENT (new_event)->end_time = occurrence->end_time; + + filter_data->events = g_slist_prepend (filter_data->events, new_event); + } + } + + CALENDAR_APPOINTMENT (event)->occurrences = occurrences; +} + +static void +filter_task (const char *uid, + CalendarEvent *event, + FilterData *filter_data) +{ +#ifdef FIX_BROKEN_TASKS_QUERY + CalendarTask *task; +#endif + + if (event->type != CALENDAR_EVENT_TASK) + return; + +#ifdef FIX_BROKEN_TASKS_QUERY + task = CALENDAR_TASK (event); + + if (task->start_time && task->start_time > filter_data->start_time) + return; + + if (task->completed_time && + (task->completed_time < filter_data->start_time || + task->completed_time > filter_data->end_time)) + return; +#endif /* FIX_BROKEN_TASKS_QUERY */ + + filter_data->events = g_slist_prepend (filter_data->events, + calendar_event_copy (event)); +} + +static GSList * +calendar_client_filter_events (CalendarClient *client, + GSList *sources, + CalendarEventFilterFunc filter_func, + time_t start_time, + time_t end_time) +{ + FilterData filter_data; + GSList *l; + GSList *retval; + + if (!sources) + return NULL; + + filter_data.client = client; + filter_data.events = NULL; + filter_data.start_time = start_time; + filter_data.end_time = end_time; + + retval = NULL; + for (l = sources; l; l = l->next) + { + CalendarClientSource *source = l->data; + + if (source->query_completed) + { + filter_data.events = NULL; + g_hash_table_foreach (source->completed_query.events, + (GHFunc) filter_func, + &filter_data); + + filter_data.events = g_slist_reverse (filter_data.events); + + retval = g_slist_concat (retval, filter_data.events); + } + } + + return retval; +} + +GSList * +calendar_client_get_events (CalendarClient *client, + CalendarEventType event_mask) +{ + GSList *appointments; + GSList *tasks; + time_t day_begin; + time_t day_end; + + g_return_val_if_fail (CALENDAR_IS_CLIENT (client), NULL); + g_return_val_if_fail (client->priv->day != G_MAXUINT, NULL); + g_return_val_if_fail (client->priv->month != G_MAXUINT, NULL); + g_return_val_if_fail (client->priv->year != G_MAXUINT, NULL); + + day_begin = make_time_for_day_begin (client->priv->day, + client->priv->month, + client->priv->year); + day_end = make_time_for_day_begin (client->priv->day + 1, + client->priv->month, + client->priv->year); + + appointments = NULL; + if (event_mask & CALENDAR_EVENT_APPOINTMENT) + { + appointments = calendar_client_filter_events (client, + client->priv->appointment_sources, + filter_appointment, + day_begin, + day_end); + } + + tasks = NULL; + if (event_mask & CALENDAR_EVENT_TASK) + { + tasks = calendar_client_filter_events (client, + client->priv->task_sources, + filter_task, + day_begin, + day_end); + } + + return g_slist_concat (appointments, tasks); +} + +static inline int +day_from_time_t (time_t t) +{ + struct tm *tm = localtime (&t); + + g_assert (tm == NULL || (tm->tm_mday >=1 && tm->tm_mday <= 31)); + + return tm ? tm->tm_mday : 0; +} + +void +calendar_client_foreach_appointment_day (CalendarClient *client, + CalendarDayIter iter_func, + gpointer user_data) +{ + GSList *appointments, *l; + gboolean marked_days [32] = { FALSE, }; + time_t month_begin; + time_t month_end; + int i; + + g_return_if_fail (CALENDAR_IS_CLIENT (client)); + g_return_if_fail (iter_func != NULL); + g_return_if_fail (client->priv->month != G_MAXUINT); + g_return_if_fail (client->priv->year != G_MAXUINT); + + month_begin = make_time_for_day_begin (1, + client->priv->month, + client->priv->year); + month_end = make_time_for_day_begin (1, + client->priv->month + 1, + client->priv->year); + + appointments = calendar_client_filter_events (client, + client->priv->appointment_sources, + filter_appointment, + month_begin, + month_end); + for (l = appointments; l; l = l->next) + { + CalendarAppointment *appointment = l->data; + + if (appointment->start_time) + { + time_t day_time = appointment->start_time; + + if (day_time >= month_begin) + marked_days [day_from_time_t (day_time)] = TRUE; + + if (appointment->end_time) + { + int day_offset; + int duration = appointment->end_time - appointment->start_time; + /* mark the days for the appointment, no need to add an extra one when duration is a multiple of 86400 */ + for (day_offset = 1; day_offset <= duration / 86400 && duration != day_offset * 86400; day_offset++) + { + time_t day_tm = appointment->start_time + day_offset * 86400; + + if (day_tm > month_end) + break; + if (day_tm >= month_begin) + marked_days [day_from_time_t (day_tm)] = TRUE; + } + } + } + calendar_event_free (CALENDAR_EVENT (appointment)); + } + + g_slist_free (appointments); + + for (i = 1; i < 32; i++) + { + if (marked_days [i]) + iter_func (client, i, user_data); + } +} + +void +calendar_client_set_task_completed (CalendarClient *client, + char *task_uid, + gboolean task_completed, + guint percent_complete) +{ + GSList *l; + ECalClient *esource; + ICalComponent *component; + ICalProperty *prop; + ICalPropertyStatus status; + + g_return_if_fail (CALENDAR_IS_CLIENT (client)); + g_return_if_fail (task_uid != NULL); + g_return_if_fail (task_completed == FALSE || percent_complete == 100); + + component = NULL; + esource = NULL; + for (l = client->priv->task_sources; l; l = l->next) + { + CalendarClientSource *source = l->data; + + esource = source->source; + e_cal_client_get_object_sync (esource, task_uid, NULL, &component, NULL, NULL); + if (component) + break; + } + + if (!component) + { + g_warning ("Cannot locate task with uid = '%s'\n", task_uid); + return; + } + + g_assert (esource != NULL); + + /* Completed time */ + prop = i_cal_component_get_first_property (component, I_CAL_COMPLETED_PROPERTY); + if (task_completed) + { + ICalTime *completed_time; + + completed_time = i_cal_time_new_current_with_zone (client->priv->zone); + if (!prop) + { + i_cal_component_take_property (component, + i_cal_property_new_completed (completed_time)); + } + else + { + i_cal_property_set_completed (prop, completed_time); + } + } + else if (prop) + { + i_cal_component_remove_property (component, prop); + } + g_clear_object (&prop); + + /* Percent complete */ + prop = i_cal_component_get_first_property (component, I_CAL_PERCENTCOMPLETE_PROPERTY); + if (!prop) + { + i_cal_component_take_property (component, + i_cal_property_new_percentcomplete (percent_complete)); + } + else + { + i_cal_property_set_percentcomplete (prop, percent_complete); + } + g_clear_object (&prop); + + /* Status */ + status = task_completed ? I_CAL_STATUS_COMPLETED : I_CAL_STATUS_NEEDSACTION; + prop = i_cal_component_get_first_property (component, I_CAL_STATUS_PROPERTY); + if (prop) + { + i_cal_property_set_status (prop, status); + } + else + { + i_cal_component_take_property (component, i_cal_property_new_status (status)); + } + g_clear_object (&prop); + + e_cal_client_modify_object_sync (esource, + component, + E_CAL_OBJ_MOD_ALL, + 0, + NULL, + NULL); +} + +gboolean +calendar_client_create_task (CalendarClient *client, + const char *summary) +{ + GSList *l; + ECalClient *task_client = NULL; + ICalComponent *vtodo_component; + gchar *uid; + GError *error = NULL; + gboolean success = FALSE; + + g_return_val_if_fail (CALENDAR_IS_CLIENT (client), FALSE); + g_return_val_if_fail (summary != NULL && *summary != '\0', FALSE); + + /* Use the first available task source (like the existing code does) */ + for (l = client->priv->task_sources; l; l = l->next) + { + CalendarClientSource *source = l->data; + task_client = source->source; + if (task_client) + break; + } + + if (!task_client) + { + g_warning ("No task client available for task creation"); + return FALSE; + } + + /* Create a simple VTODO component */ + vtodo_component = i_cal_component_new (I_CAL_VTODO_COMPONENT); + + /* Generate UID */ + uid = e_util_generate_uid (); + i_cal_component_set_uid (vtodo_component, uid); + g_free (uid); + + /* Set summary */ + i_cal_component_set_summary (vtodo_component, summary); + + /* Set created time */ + ICalTime *now = i_cal_time_new_current_with_zone (client->priv->zone); + i_cal_component_set_dtstamp (vtodo_component, now); + g_object_unref (now); + + /* Create the task */ + success = e_cal_client_create_object_sync (task_client, + vtodo_component, + E_CAL_OPERATION_FLAG_NONE, + NULL, /* out uid */ + NULL, /* cancellable */ + &error); + + if (error) + { + g_warning ("Failed to create task: %s", error->message); + g_error_free (error); + success = FALSE; + } + + /* Cleanup */ + g_object_unref (vtodo_component); + + return success; +} + +#endif /* HAVE_EDS */ diff --git a/applets/clock/calendar-client.h b/applets/clock/calendar-client.h new file mode 100644 index 00000000..7747d23f --- /dev/null +++ b/applets/clock/calendar-client.h @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2004 Free Software Foundation, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Mark McLoughlin <[email protected]> + * William Jon McCann <[email protected]> + * Martin Grimme <[email protected]> + * Christian Kellner <[email protected]> + */ + +#ifndef __CALENDAR_CLIENT_H__ +#define __CALENDAR_CLIENT_H__ + +#include <glib-object.h> +#include <gio/gio.h> + +G_BEGIN_DECLS + +typedef enum +{ + CALENDAR_EVENT_APPOINTMENT = 1 << 0, + CALENDAR_EVENT_TASK = 1 << 1, + CALENDAR_EVENT_ALL = (1 << 2) - 1 +} CalendarEventType; + +#define CALENDAR_TYPE_CLIENT (calendar_client_get_type ()) +#define CALENDAR_CLIENT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CALENDAR_TYPE_CLIENT, CalendarClient)) +#define CALENDAR_CLIENT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), CALENDAR_TYPE_CLIENT, CalendarClientClass)) +#define CALENDAR_IS_CLIENT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CALENDAR_TYPE_CLIENT)) +#define CALENDAR_IS_CLIENT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CALENDAR_TYPE_CLIENT)) +#define CALENDAR_CLIENT_GET_CLASS(o)(G_TYPE_INSTANCE_GET_CLASS ((o), CALENDAR_TYPE_CLIENT, CalendarClientClass)) + +typedef struct _CalendarClient CalendarClient; +typedef struct _CalendarClientClass CalendarClientClass; +typedef struct _CalendarClientPrivate CalendarClientPrivate; + +struct _CalendarClient +{ + GObject parent; + CalendarClientPrivate *priv; +}; + +struct _CalendarClientClass +{ + GObjectClass parent_class; + + void (* appointments_changed) (CalendarClient *client); + void (* tasks_changed) (CalendarClient *client); +}; + + +typedef struct +{ + time_t start_time; + time_t end_time; +} CalendarOccurrence; + +typedef struct +{ + char *uid; + char *rid; + char *backend_name; + char *summary; + char *description; + char *color_string; + time_t start_time; + time_t end_time; + guint is_all_day : 1; + + /* Only used internally */ + GSList *occurrences; +} CalendarAppointment; + +typedef struct +{ + char *uid; + char *summary; + char *description; + char *color_string; + char *url; + time_t start_time; + time_t due_time; + guint percent_complete; + time_t completed_time; + int priority; +} CalendarTask; + +typedef struct +{ + union + { + CalendarAppointment appointment; + CalendarTask task; + } event; + CalendarEventType type; +} CalendarEvent; + +#define CALENDAR_EVENT(e) ((CalendarEvent *)(e)) +#define CALENDAR_APPOINTMENT(e) ((CalendarAppointment *)(e)) +#define CALENDAR_TASK(e) ((CalendarTask *)(e)) + +typedef void (* CalendarDayIter) (CalendarClient *client, + guint day, + gpointer user_data); + + +GType calendar_client_get_type (void) G_GNUC_CONST; +CalendarClient *calendar_client_new (GSettings *settings); + +void calendar_client_get_date (CalendarClient *client, + guint *year, + guint *month, + guint *day); +void calendar_client_select_month (CalendarClient *client, + guint month, + guint year); +void calendar_client_select_day (CalendarClient *client, + guint day); + +GSList *calendar_client_get_events (CalendarClient *client, + CalendarEventType event_mask); +void calendar_client_foreach_appointment_day (CalendarClient *client, + CalendarDayIter iter_func, + gpointer user_data); + +void calendar_client_set_task_completed (CalendarClient *client, + char *task_uid, + gboolean task_completed, + guint percent_complete); + +gboolean calendar_client_create_task (CalendarClient *client, + const char *summary); + +void calendar_event_free (CalendarEvent *event); + +G_END_DECLS + +#endif /* __CALENDAR_CLIENT_H__ */ diff --git a/applets/clock/calendar-debug.h b/applets/clock/calendar-debug.h new file mode 100644 index 00000000..39befd74 --- /dev/null +++ b/applets/clock/calendar-debug.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2004 Free Software Foundation, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Mark McLoughlin <[email protected]> + */ + +#ifndef __CALENDAR_DEBUG_H__ +#define __CALENDAR_DEBUG_H__ + +#include <glib.h> + +G_BEGIN_DECLS + +#ifdef CALENDAR_ENABLE_DEBUG + +#include <stdio.h> + +#ifdef G_HAVE_ISO_VARARGS +# define dprintf(...) fprintf (stderr, __VA_ARGS__); +#elif defined(G_HAVE_GNUC_VARARGS) +# define dprintf(args...) fprintf (stderr, args); +#endif + +#else /* if !defined (CALENDAR_DEBUG) */ + +#ifdef G_HAVE_ISO_VARARGS +# define dprintf(...) +#elif defined(G_HAVE_GNUC_VARARGS) +# define dprintf(args...) +#endif + +#endif /* CALENDAR_ENABLE_DEBUG */ + +G_END_DECLS + +#endif /* __CALENDAR_DEBUG_H__ */ diff --git a/applets/clock/calendar-sources.c b/applets/clock/calendar-sources.c new file mode 100644 index 00000000..54c6dc8c --- /dev/null +++ b/applets/clock/calendar-sources.c @@ -0,0 +1,503 @@ +/* + * Copyright (C) 2004 Free Software Foundation, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Mark McLoughlin <[email protected]> + * William Jon McCann <[email protected]> + * Martin Grimme <[email protected]> + * Christian Kellner <[email protected]> + */ + +#include <config.h> + +#include "calendar-sources.h" + +#include <libintl.h> +#include <string.h> +#define HANDLE_LIBICAL_MEMORY + + +#include <libecal/libecal.h> + +#undef CALENDAR_ENABLE_DEBUG +#include "calendar-debug.h" + +#ifndef _ +#define _(x) gettext(x) +#endif + +#ifndef N_ +#define N_(x) x +#endif + +typedef struct _ClientData ClientData; +typedef struct _CalendarSourceData CalendarSourceData; + +struct _ClientData +{ + ECalClient *client; + gulong backend_died_id; +}; + +struct _CalendarSourceData +{ + ECalClientSourceType source_type; + CalendarSources *sources; + guint changed_signal; + + /* ESource -> EClient */ + GHashTable *clients; + + guint timeout_id; + + guint loaded : 1; +}; + +struct _CalendarSourcesPrivate +{ + ESourceRegistry *registry; + gulong source_added_id; + gulong source_changed_id; + gulong source_removed_id; + + CalendarSourceData appointment_sources; + CalendarSourceData task_sources; +}; + +static void calendar_sources_finalize (GObject *object); + +static void backend_died_cb (EClient *client, CalendarSourceData *source_data); +static void calendar_sources_registry_source_changed_cb (ESourceRegistry *registry, + ESource *source, + CalendarSources *sources); +static void calendar_sources_registry_source_removed_cb (ESourceRegistry *registry, + ESource *source, + CalendarSources *sources); + +enum +{ + APPOINTMENT_SOURCES_CHANGED, + TASK_SOURCES_CHANGED, + LAST_SIGNAL +}; +static guint signals [LAST_SIGNAL] = { 0, }; + +static CalendarSources *calendar_sources_singleton = NULL; + +static void +client_data_free (ClientData *data) +{ + g_signal_handler_disconnect (data->client, data->backend_died_id); + g_object_unref (data->client); + g_free (data); +} + +G_DEFINE_TYPE_WITH_PRIVATE (CalendarSources, calendar_sources, G_TYPE_OBJECT) + +static void +calendar_sources_class_init (CalendarSourcesClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->finalize = calendar_sources_finalize; + + signals [APPOINTMENT_SOURCES_CHANGED] = + g_signal_new ("appointment-sources-changed", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (CalendarSourcesClass, + appointment_sources_changed), + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); + + signals [TASK_SOURCES_CHANGED] = + g_signal_new ("task-sources-changed", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (CalendarSourcesClass, + task_sources_changed), + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); +} + +static void +calendar_sources_init (CalendarSources *sources) +{ + GError *error = NULL; + + sources->priv = calendar_sources_get_instance_private (sources); + + /* XXX Not sure what to do if this fails. + * Should this class implement GInitable or pass the + * registry in as a G_PARAM_CONSTRUCT_ONLY property? */ + sources->priv->registry = e_source_registry_new_sync (NULL, &error); + if (error != NULL) { + g_critical ("%s: %s", G_STRFUNC, error->message); + g_error_free (error); + } + + sources->priv->source_added_id = g_signal_connect (sources->priv->registry, + "source-added", + G_CALLBACK (calendar_sources_registry_source_changed_cb), + sources); + sources->priv->source_changed_id = g_signal_connect (sources->priv->registry, + "source-changed", + G_CALLBACK (calendar_sources_registry_source_changed_cb), + sources); + sources->priv->source_removed_id = g_signal_connect (sources->priv->registry, + "source-removed", + G_CALLBACK (calendar_sources_registry_source_removed_cb), + sources); + + sources->priv->appointment_sources.source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS; + sources->priv->appointment_sources.sources = sources; + sources->priv->appointment_sources.changed_signal = signals [APPOINTMENT_SOURCES_CHANGED]; + sources->priv->appointment_sources.clients = g_hash_table_new_full ((GHashFunc) e_source_hash, + (GEqualFunc) e_source_equal, + (GDestroyNotify) g_object_unref, + (GDestroyNotify) client_data_free); + sources->priv->appointment_sources.timeout_id = 0; + + sources->priv->task_sources.source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS; + sources->priv->task_sources.sources = sources; + sources->priv->task_sources.changed_signal = signals [TASK_SOURCES_CHANGED]; + sources->priv->task_sources.clients = g_hash_table_new_full ((GHashFunc) e_source_hash, + (GEqualFunc) e_source_equal, + (GDestroyNotify) g_object_unref, + (GDestroyNotify) client_data_free); + sources->priv->task_sources.timeout_id = 0; +} + +static void +calendar_sources_finalize_source_data (CalendarSources *sources, + CalendarSourceData *source_data) +{ + if (source_data->loaded) + { + g_hash_table_destroy (source_data->clients); + source_data->clients = NULL; + + if (source_data->timeout_id != 0) + { + g_source_remove (source_data->timeout_id); + source_data->timeout_id = 0; + } + + source_data->loaded = FALSE; + } +} + +static void +calendar_sources_finalize (GObject *object) +{ + CalendarSources *sources = CALENDAR_SOURCES (object); + + if (sources->priv->registry) + { + g_signal_handler_disconnect (sources->priv->registry, + sources->priv->source_added_id); + g_signal_handler_disconnect (sources->priv->registry, + sources->priv->source_changed_id); + g_signal_handler_disconnect (sources->priv->registry, + sources->priv->source_removed_id); + g_object_unref (sources->priv->registry); + } + sources->priv->registry = NULL; + + calendar_sources_finalize_source_data (sources, &sources->priv->appointment_sources); + calendar_sources_finalize_source_data (sources, &sources->priv->task_sources); + + G_OBJECT_CLASS (calendar_sources_parent_class)->finalize (object); +} + +CalendarSources * +calendar_sources_get (void) +{ + gpointer singleton_location = &calendar_sources_singleton; + + if (calendar_sources_singleton) + return g_object_ref (calendar_sources_singleton); + + calendar_sources_singleton = g_object_new (CALENDAR_TYPE_SOURCES, NULL); + g_object_add_weak_pointer (G_OBJECT (calendar_sources_singleton), + singleton_location); + + return calendar_sources_singleton; +} + +/* The clients are just created here but not loaded */ +static void +create_client_for_source (ESource *source, + ECalClientSourceType source_type, + CalendarSourceData *source_data) +{ + ClientData *data; + GError *error; + EClient *client; + + client = g_hash_table_lookup (source_data->clients, source); + g_return_if_fail (client == NULL); + + error = NULL; + client = e_cal_client_connect_sync (source, source_type, -1, NULL, &error); + + if (!client) + { + g_warning ("Could not load source '%s': %s", + e_source_get_uid (source), + error->message); + + g_clear_error (&error); + return; + } + + data = g_new0 (ClientData, 1); + data->client = E_CAL_CLIENT (client); /* takes ownership */ + data->backend_died_id = g_signal_connect (client, + "backend-died", + G_CALLBACK (backend_died_cb), + source_data); + + g_hash_table_insert (source_data->clients, g_object_ref (source), data); +} + +static inline void +debug_dump_ecal_list (GHashTable *clients) +{ +#ifdef CALENDAR_ENABLE_DEBUG + GList *list, *link; + + dprintf ("Loaded clients:\n"); + list = g_hash_table_get_keys (clients); + for (link = list; link != NULL; link = g_list_next (link)) + { + ESource *source = E_SOURCE (link->data); + + dprintf (" %s %s\n", + e_source_get_uid (source), + e_source_get_display_name (source)); + } +#endif +} + +static void +calendar_sources_load_esource_list (ESourceRegistry *registry, + CalendarSourceData *source_data); + +static gboolean +backend_restart (gpointer data) +{ + CalendarSourceData *source_data = data; + ESourceRegistry *registry; + + registry = source_data->sources->priv->registry; + calendar_sources_load_esource_list (registry, source_data); + g_signal_emit (source_data->sources, source_data->changed_signal, 0); + + source_data->timeout_id = 0; + + return FALSE; +} + +static void +backend_died_cb (EClient *client, CalendarSourceData *source_data) +{ + ESource *source; + const char *display_name; + + source = e_client_get_source (client); + display_name = e_source_get_display_name (source); + g_warning ("The calendar backend for '%s' has crashed.", display_name); + g_hash_table_remove (source_data->clients, source); + + if (source_data->timeout_id != 0) + { + g_source_remove (source_data->timeout_id); + source_data->timeout_id = 0; + } + + source_data->timeout_id = g_timeout_add_seconds (2, backend_restart, + source_data); +} + +static void +calendar_sources_load_esource_list (ESourceRegistry *registry, + CalendarSourceData *source_data) +{ + GList *list, *link; + const gchar *extension_name; + + switch (source_data->source_type) + { + case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: + extension_name = E_SOURCE_EXTENSION_CALENDAR; + break; + case E_CAL_CLIENT_SOURCE_TYPE_TASKS: + extension_name = E_SOURCE_EXTENSION_TASK_LIST; + break; + case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: + case E_CAL_CLIENT_SOURCE_TYPE_LAST: + default: + g_return_if_reached (); + } + + list = e_source_registry_list_sources (registry, extension_name); + + for (link = list; link != NULL; link = g_list_next (link)) + { + ESource *source = E_SOURCE (link->data); + ESourceSelectable *extension; + gboolean show_source; + + extension = e_source_get_extension (source, extension_name); + show_source = e_source_get_enabled (source) && e_source_selectable_get_selected (extension); + + if (show_source) + create_client_for_source (source, source_data->source_type, source_data); + } + + debug_dump_ecal_list (source_data->clients); + + g_list_free_full (list, g_object_unref); +} + +static void +calendar_sources_registry_source_changed_cb (ESourceRegistry *registry, + ESource *source, + CalendarSources *sources) +{ + if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR)) + { + CalendarSourceData *source_data; + ESourceSelectable *extension; + gboolean have_client; + gboolean show_source; + + source_data = &sources->priv->appointment_sources; + extension = e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR); + have_client = (g_hash_table_lookup (source_data->clients, source) != NULL); + show_source = e_source_get_enabled (source) && e_source_selectable_get_selected (extension); + + if (!show_source && have_client) + { + g_hash_table_remove (source_data->clients, source); + g_signal_emit (sources, source_data->changed_signal, 0); + } + if (show_source && !have_client) + { + create_client_for_source (source, source_data->source_type, source_data); + g_signal_emit (sources, source_data->changed_signal, 0); + } + } + + if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST)) + { + CalendarSourceData *source_data; + ESourceSelectable *extension; + gboolean have_client; + gboolean show_source; + + source_data = &sources->priv->task_sources; + extension = e_source_get_extension (source, E_SOURCE_EXTENSION_TASK_LIST); + have_client = (g_hash_table_lookup (source_data->clients, source) != NULL); + show_source = e_source_get_enabled (source) && e_source_selectable_get_selected (extension); + + if (!show_source && have_client) + { + g_hash_table_remove (source_data->clients, source); + g_signal_emit (sources, source_data->changed_signal, 0); + } + if (show_source && !have_client) + { + create_client_for_source (source, source_data->source_type, source_data); + g_signal_emit (sources, source_data->changed_signal, 0); + } + } +} + +static void +calendar_sources_registry_source_removed_cb (ESourceRegistry *registry, + ESource *source, + CalendarSources *sources) +{ + if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR)) + { + CalendarSourceData *source_data; + + source_data = &sources->priv->appointment_sources; + g_hash_table_remove (source_data->clients, source); + g_signal_emit (sources, source_data->changed_signal, 0); + } + + if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST)) + { + CalendarSourceData *source_data; + + source_data = &sources->priv->task_sources; + g_hash_table_remove (source_data->clients, source); + g_signal_emit (sources, source_data->changed_signal, 0); + } +} + +GList * +calendar_sources_get_appointment_clients (CalendarSources *sources) +{ + GList *list, *link; + + g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL); + + if (!sources->priv->appointment_sources.loaded) + { + calendar_sources_load_esource_list (sources->priv->registry, + &sources->priv->appointment_sources); + sources->priv->appointment_sources.loaded = TRUE; + } + + list = g_hash_table_get_values (sources->priv->appointment_sources.clients); + + for (link = list; link != NULL; link = g_list_next (link)) + link->data = ((ClientData *) link->data)->client; + + return list; +} + +GList * +calendar_sources_get_task_clients (CalendarSources *sources) +{ + GList *list, *link; + + g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL); + + if (!sources->priv->task_sources.loaded) + { + calendar_sources_load_esource_list (sources->priv->registry, + &sources->priv->task_sources); + sources->priv->task_sources.loaded = TRUE; + } + + list = g_hash_table_get_values (sources->priv->task_sources.clients); + + for (link = list; link != NULL; link = g_list_next (link)) + link->data = ((ClientData *) link->data)->client; + + return list; +} diff --git a/applets/clock/calendar-sources.h b/applets/clock/calendar-sources.h new file mode 100644 index 00000000..1dfc7445 --- /dev/null +++ b/applets/clock/calendar-sources.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2004 Free Software Foundation, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Mark McLoughlin <[email protected]> + * William Jon McCann <[email protected]> + * Martin Grimme <[email protected]> + * Christian Kellner <[email protected]> + */ + +#ifndef __CALENDAR_SOURCES_H__ +#define __CALENDAR_SOURCES_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define CALENDAR_TYPE_SOURCES (calendar_sources_get_type ()) +#define CALENDAR_SOURCES(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CALENDAR_TYPE_SOURCES, CalendarSources)) +#define CALENDAR_SOURCES_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), CALENDAR_TYPE_SOURCES, CalendarSourcesClass)) +#define CALENDAR_IS_SOURCES(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CALENDAR_TYPE_SOURCES)) +#define CALENDAR_IS_SOURCES_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CALENDAR_TYPE_SOURCES)) +#define CALENDAR_SOURCES_GET_CLASS(o)(G_TYPE_INSTANCE_GET_CLASS ((o), CALENDAR_TYPE_SOURCES, CalendarSourcesClass)) + +typedef struct _CalendarSources CalendarSources; +typedef struct _CalendarSourcesClass CalendarSourcesClass; +typedef struct _CalendarSourcesPrivate CalendarSourcesPrivate; + +struct _CalendarSources +{ + GObject parent; + CalendarSourcesPrivate *priv; +}; + +struct _CalendarSourcesClass +{ + GObjectClass parent_class; + + void (* appointment_sources_changed) (CalendarSources *sources); + void (* task_sources_changed) (CalendarSources *sources); +}; + + +GType calendar_sources_get_type (void) G_GNUC_CONST; +CalendarSources *calendar_sources_get (void); +GList *calendar_sources_get_appointment_clients (CalendarSources *sources); +GList *calendar_sources_get_task_clients (CalendarSources *sources); + +G_END_DECLS + +#endif /* __CALENDAR_SOURCES_H__ */ diff --git a/applets/clock/calendar-window.c b/applets/clock/calendar-window.c index 8fe2c77c..ae9f1d7c 100644 --- a/applets/clock/calendar-window.c +++ b/applets/clock/calendar-window.c @@ -38,11 +38,24 @@ #include "clock.h" #include "clock-utils.h" #include "clock-typebuiltins.h" +#ifdef HAVE_EDS +#include "calendar-client.h" +#endif #define KEY_LOCATIONS_EXPANDED "expand-locations" +#ifdef HAVE_EDS +#define KEY_SHOW_CALENDAR_EVENTS "show-calendar-events" +#define KEY_SHOW_TASKS "show-tasks" +#define SCHEMA_CALENDAR_APP "org.mate.desktop.default-applications.office.calendar" +#define SCHEMA_TASKS_APP "org.mate.desktop.default-applications.office.tasks" +#endif + enum { EDIT_LOCATIONS, +#ifdef HAVE_EDS + PERMISSION_READY, +#endif LAST_SIGNAL }; @@ -60,6 +73,33 @@ struct _CalendarWindowPrivate { GtkWidget *locations_list; GSettings *settings; + + /* Signal handler IDs for proper cleanup */ + gulong calendar_month_changed_id; + gulong calendar_day_selected_id; + +#ifdef HAVE_EDS + ClockFormat time_format; + + CalendarClient *client; + + GtkWidget *appointment_list; + + GtkListStore *appointments_model; + GtkListStore *tasks_model; + + GtkTreeSelection *previous_selection; + + GtkTreeModelFilter *appointments_filter; + GtkTreeModelFilter *tasks_filter; + + GtkWidget *task_list; + GtkWidget *task_entry; + + /* EDS-specific signal handler IDs */ + gulong client_appointments_changed_id; + gulong client_tasks_changed_id; +#endif /* HAVE_EDS */ }; G_DEFINE_TYPE_WITH_PRIVATE (CalendarWindow, calendar_window, GTK_TYPE_WINDOW) @@ -84,6 +124,63 @@ static GtkWidget * create_hig_frame (CalendarWindow *calwin, const char *key, GCallback callback); +#ifdef HAVE_EDS +enum { + APPOINTMENT_COLUMN_UID, + APPOINTMENT_COLUMN_TYPE, + APPOINTMENT_COLUMN_SUMMARY, + APPOINTMENT_COLUMN_DESCRIPTION, + APPOINTMENT_COLUMN_START_TIME, + APPOINTMENT_COLUMN_START_TEXT, + APPOINTMENT_COLUMN_END_TIME, + APPOINTMENT_COLUMN_ALL_DAY, + APPOINTMENT_COLUMN_COLOR, + N_APPOINTMENT_COLUMNS +}; + +enum { + TASK_COLUMN_UID, + TASK_COLUMN_TYPE, + TASK_COLUMN_SUMMARY, + TASK_COLUMN_DESCRIPTION, + TASK_COLUMN_START_TIME, + TASK_COLUMN_START_TEXT, + TASK_COLUMN_DUE_TIME, + TASK_COLUMN_DUE_TEXT, + TASK_COLUMN_PERCENT_COMPLETE, + TASK_COLUMN_PERCENT_COMPLETE_TEXT, + TASK_COLUMN_COMPLETED, + TASK_COLUMN_COMPLETED_TIME, + TASK_COLUMN_PRIORITY, + TASK_COLUMN_COLOR, + N_TASK_COLUMNS +}; + +enum { + APPOINTMENT_TYPE_APPOINTMENT, + TASK_TYPE_TASK +}; + +static void calendar_window_pack_pim (CalendarWindow *calwin, GtkWidget *vbox); +static char *format_time (ClockFormat format, time_t t, gint year, gint month, gint day); +static void update_frame_visibility (GtkWidget *frame, GtkTreeModel *model); +static GtkWidget *create_appointment_list (CalendarWindow *calwin, GtkWidget **tree_view, GtkWidget **scrolled_window); +static GtkWidget *create_task_list (CalendarWindow *calwin, GtkWidget **tree_view, GtkWidget **scrolled_window); +static void calendar_window_create_appointments_model (CalendarWindow *calwin); +static void calendar_window_create_tasks_model (CalendarWindow *calwin); +static void handle_appointments_changed (CalendarWindow *calwin); +static void handle_tasks_changed (CalendarWindow *calwin); +static void mark_day_on_calendar (CalendarClient *client, guint day, CalendarWindow *calwin); +static gboolean is_for_filter (GtkTreeModel *model, GtkTreeIter *iter, gpointer data); +static gboolean appointment_tooltip_query_cb (GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data); +static gboolean task_tooltip_query_cb (GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data); +static void appointment_row_activated_cb (GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data); +static void task_row_activated_cb (GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data); +static void task_completion_toggled_cb (GtkCellRendererToggle *cell, gchar *path_str, CalendarWindow *calwin); +static gboolean task_entry_key_press_cb (GtkWidget *widget, GdkEventKey *event, CalendarWindow *calwin); +static void task_entry_activate_cb (GtkEntry *entry, CalendarWindow *calwin); +#endif /* HAVE_EDS */ + static void calendar_mark_today(GtkCalendar *calendar) { time_t now; @@ -93,10 +190,10 @@ static void calendar_mark_today(GtkCalendar *calendar) gtk_calendar_get_date(calendar, &year, &month, &day); time(&now); localtime_r (&now, &tm1); - if ((tm1.tm_mon == month) && (tm1.tm_year + 1900 == year)) { - gtk_calendar_mark_day (GTK_CALENDAR (calendar), tm1.tm_mday); + if ((tm1.tm_mon == (int) month) && (tm1.tm_year + 1900 == (int) year)) { + gtk_calendar_mark_day (GTK_CALENDAR (calendar), (guint) tm1.tm_mday); } else { - gtk_calendar_unmark_day (GTK_CALENDAR (calendar), tm1.tm_mday); + gtk_calendar_unmark_day (GTK_CALENDAR (calendar), (guint) tm1.tm_mday); } } @@ -110,7 +207,22 @@ static gboolean calendar_update(gpointer user_data) static void calendar_month_changed_cb(GtkCalendar *calendar, gpointer user_data) { gtk_calendar_clear_marks(calendar); - g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, calendar_update, user_data, NULL); + g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, calendar_update, calendar, NULL); + +#ifdef HAVE_EDS + /* Update calendar client when date changes */ + CalendarWindow *calwin = CALENDAR_WINDOW (user_data); + if (calwin->priv->client) { + guint year, month, day; + gtk_calendar_get_date (calendar, &year, &month, &day); + calendar_client_select_month (calwin->priv->client, month, year); + calendar_client_select_day (calwin->priv->client, day); + + /* Refresh appointments and tasks for the new date */ + handle_appointments_changed (calwin); + handle_tasks_changed (calwin); + } +#endif } static GtkWidget * @@ -131,12 +243,14 @@ calendar_window_create_calendar (CalendarWindow *calwin) localtime_r (calwin->priv->current_time, &tm1); gtk_calendar_select_month (GTK_CALENDAR (calendar), - tm1.tm_mon, tm1.tm_year + 1900); - gtk_calendar_select_day (GTK_CALENDAR (calendar), tm1.tm_mday); + (guint) tm1.tm_mon, (guint) (tm1.tm_year + 1900)); + gtk_calendar_select_day (GTK_CALENDAR (calendar), (guint) tm1.tm_mday); calendar_mark_today (GTK_CALENDAR(calendar)); - g_signal_connect(calendar, "month-changed", - G_CALLBACK(calendar_month_changed_cb), calendar); + calwin->priv->calendar_month_changed_id = g_signal_connect(calendar, "month-changed", + G_CALLBACK(calendar_month_changed_cb), calwin); + calwin->priv->calendar_day_selected_id = g_signal_connect(calendar, "day-selected", + G_CALLBACK(calendar_month_changed_cb), calwin); return calendar; } @@ -250,6 +364,52 @@ edit_locations (CalendarWindow *calwin) g_signal_emit (calwin, signals[EDIT_LOCATIONS], 0); } +#ifdef HAVE_EDS +static gboolean +hide_task_entry_idle (gpointer user_data) +{ + CalendarWindow *calwin = CALENDAR_WINDOW (user_data); + if (calwin->priv->task_entry) { + gtk_widget_hide (calwin->priv->task_entry); + } + return FALSE; /* Remove the idle source */ +} + +static gboolean +focus_task_entry_idle (gpointer user_data) +{ + CalendarWindow *calwin = CALENDAR_WINDOW (user_data); + if (calwin->priv->task_entry && gtk_widget_get_visible (calwin->priv->task_entry)) { + gtk_widget_grab_focus (calwin->priv->task_entry); + } + return FALSE; /* Remove the idle source */ +} + +static void +add_task (CalendarWindow *calwin) +{ + if (calwin->priv->task_entry) { + gtk_widget_show (calwin->priv->task_entry); + gtk_widget_set_can_focus (calwin->priv->task_entry, TRUE); + gtk_widget_set_sensitive (calwin->priv->task_entry, TRUE); + + /* Make sure parent window is active */ + gtk_window_present (GTK_WINDOW (calwin)); + + /* Ensure widget is realized */ + if (!gtk_widget_get_realized (calwin->priv->task_entry)) { + gtk_widget_realize (calwin->priv->task_entry); + } + + /* Try to grab focus immediately */ + gtk_widget_grab_focus (calwin->priv->task_entry); + + /* Also try to grab focus in idle callback in case immediate focus fails */ + g_idle_add (focus_task_entry_idle, calwin); + } +} +#endif + static void calendar_window_pack_locations (CalendarWindow *calwin, GtkWidget *vbox) { @@ -287,12 +447,22 @@ calendar_window_fill (CalendarWindow *calwin) calwin->priv->calendar = calendar_window_create_calendar (calwin); gtk_widget_show (calwin->priv->calendar); +#ifdef HAVE_EDS + /* Calendar client will be initialized later in calendar_window_pack_pim */ +#endif + if (!calwin->priv->invert_order) { gtk_box_pack_start (GTK_BOX (vbox), calwin->priv->calendar, TRUE, FALSE, 0); +#ifdef HAVE_EDS + calendar_window_pack_pim (calwin, vbox); +#endif calendar_window_pack_locations (calwin, vbox); } else { calendar_window_pack_locations (calwin, vbox); +#ifdef HAVE_EDS + calendar_window_pack_pim (calwin, vbox); +#endif gtk_box_pack_start (GTK_BOX (vbox), calwin->priv->calendar, TRUE, FALSE, 0); } @@ -403,6 +573,35 @@ calendar_window_dispose (GObject *object) calwin = CALENDAR_WINDOW (object); + g_clear_pointer (&calwin->priv->prefs_path, g_free); + + /* Disconnect calendar signals */ + if (calwin->priv->calendar && calwin->priv->calendar_month_changed_id > 0) { + g_signal_handler_disconnect (calwin->priv->calendar, calwin->priv->calendar_month_changed_id); + calwin->priv->calendar_month_changed_id = 0; + } + if (calwin->priv->calendar && calwin->priv->calendar_day_selected_id > 0) { + g_signal_handler_disconnect (calwin->priv->calendar, calwin->priv->calendar_day_selected_id); + calwin->priv->calendar_day_selected_id = 0; + } + +#ifdef HAVE_EDS + /* Disconnect client signals */ + if (calwin->priv->client) { + if (calwin->priv->client_appointments_changed_id > 0) { + g_signal_handler_disconnect (calwin->priv->client, calwin->priv->client_appointments_changed_id); + calwin->priv->client_appointments_changed_id = 0; + } + if (calwin->priv->client_tasks_changed_id > 0) { + g_signal_handler_disconnect (calwin->priv->client, calwin->priv->client_tasks_changed_id); + calwin->priv->client_tasks_changed_id = 0; + } + g_signal_handlers_disconnect_by_data (calwin->priv->client, calwin); + g_object_unref (calwin->priv->client); + calwin->priv->client = NULL; + } +#endif + if (calwin->priv->settings) g_object_unref (calwin->priv->settings); calwin->priv->settings = NULL; @@ -473,6 +672,13 @@ calendar_window_init (CalendarWindow *calwin) calwin->priv = calendar_window_get_instance_private (calwin); +#ifdef HAVE_EDS + /* Initialize signal handler IDs */ + calwin->priv->calendar_month_changed_id = 0; + calwin->priv->calendar_day_selected_id = 0; + calwin->priv->client_appointments_changed_id = 0; +#endif + window = GTK_WINDOW (calwin); gtk_window_set_type_hint (window, GDK_WINDOW_TYPE_HINT_DOCK); gtk_window_set_decorated (window, FALSE); @@ -486,7 +692,8 @@ calendar_window_init (CalendarWindow *calwin) GtkWidget * calendar_window_new (time_t *static_current_time, const char *prefs_path, - gboolean invert_order) + gboolean invert_order, + GSettings *settings) { CalendarWindow *calwin; @@ -497,6 +704,13 @@ calendar_window_new (time_t *static_current_time, "prefs-path", prefs_path, NULL); +#ifdef HAVE_EDS + /* Store settings for calendar client initialization in init */ + if (settings) { + calwin->priv->settings = g_object_ref (settings); + } +#endif + return GTK_WIDGET (calwin); } @@ -504,6 +718,16 @@ void calendar_window_refresh (CalendarWindow *calwin) { g_return_if_fail (CALENDAR_IS_WINDOW (calwin)); + +#ifdef HAVE_EDS + if (calwin->priv->appointments_filter && calwin->priv->appointment_list) + gtk_tree_model_filter_refilter (calwin->priv->appointments_filter); + + /* Update frame visibility based on model content */ + if (calwin->priv->appointment_list && calwin->priv->appointments_filter) + update_frame_visibility (calwin->priv->appointment_list, + GTK_TREE_MODEL (calwin->priv->appointments_filter)); +#endif } gboolean @@ -541,8 +765,6 @@ void calendar_window_set_show_weeks (CalendarWindow *calwin, gboolean show_weeks) { - GtkCalendarDisplayOptions options; - g_return_if_fail (CALENDAR_IS_WINDOW (calwin)); if (show_weeks == calwin->priv->show_weeks) @@ -551,6 +773,8 @@ calendar_window_set_show_weeks (CalendarWindow *calwin, calwin->priv->show_weeks = show_weeks; if (calwin->priv->calendar) { + GtkCalendarDisplayOptions options; + options = gtk_calendar_get_display_options (GTK_CALENDAR (calwin->priv->calendar)); if (show_weeks) @@ -571,7 +795,11 @@ calendar_window_get_time_format (CalendarWindow *calwin) g_return_val_if_fail (CALENDAR_IS_WINDOW (calwin), CLOCK_FORMAT_INVALID); +#ifdef HAVE_EDS + return calwin->priv->time_format; +#else return CLOCK_FORMAT_INVALID; +#endif } static time_t * @@ -617,17 +845,901 @@ calendar_window_set_prefs_path (CalendarWindow *calwin, !strcmp (calwin->priv->prefs_path, prefs_path)) return; - if (calwin->priv->prefs_path) - g_free (calwin->priv->prefs_path); - calwin->priv->prefs_path = NULL; - + g_free (calwin->priv->prefs_path); if (prefs_path && prefs_path [0]) calwin->priv->prefs_path = g_strdup (prefs_path); + else + calwin->priv->prefs_path = NULL; g_object_notify (G_OBJECT (calwin), "prefs-path"); - if (calwin->priv->settings) - g_object_unref (calwin->priv->settings); + /* Only create new settings if we don't already have shared settings */ + if (!calwin->priv->settings) { + calwin->priv->settings = g_settings_new_with_path (CLOCK_SCHEMA, calwin->priv->prefs_path); + } +} + +#ifdef HAVE_EDS + +static char * +format_time (ClockFormat format, + time_t t, + gint year, + gint month, + gint day) +{ + GDateTime *dt; + gchar *time; + + if (!t) + return NULL; + + /* Evolution timestamps are in UTC but represent local appointment times + * Since TZID lookup failed, treat UTC timestamp as local time directly */ + dt = g_date_time_new_from_unix_utc (t); + time = NULL; + + if (!dt) + return NULL; + + /* Always show time since we're filtering by selected date */ + if (format == CLOCK_FORMAT_12) { + /* Translators: This is a strftime format string. + * It is used to display the time in 12-hours format + * (eg, like in the US: 8:10 am). The %p expands to + * am/pm. + */ + time = g_date_time_format (dt, _("%l:%M %p")); + } else { + /* Translators: This is a strftime format string. + * It is used to display the time in 24-hours format + * (eg, like in France: 20:10). + */ + time = g_date_time_format (dt, _("%H:%M")); + } + + g_date_time_unref (dt); + return time; +} + +static void +update_frame_visibility (GtkWidget *frame, + GtkTreeModel *model) +{ + GtkTreeIter iter; + gboolean model_empty; + + if (!frame) + return; + + model_empty = !gtk_tree_model_get_iter_first (model, &iter); + + if (model_empty) + gtk_widget_hide (frame); + else + gtk_widget_show (frame); +} + + +static void +calendar_window_create_appointments_model (CalendarWindow *calwin) +{ + calwin->priv->appointments_model = gtk_list_store_new (N_APPOINTMENT_COLUMNS, + G_TYPE_STRING, /* APPOINTMENT_COLUMN_UID */ + G_TYPE_INT, /* APPOINTMENT_COLUMN_TYPE */ + G_TYPE_STRING, /* APPOINTMENT_COLUMN_SUMMARY */ + G_TYPE_STRING, /* APPOINTMENT_COLUMN_DESCRIPTION */ + G_TYPE_ULONG, /* APPOINTMENT_COLUMN_START_TIME */ + G_TYPE_STRING, /* APPOINTMENT_COLUMN_START_TEXT */ + G_TYPE_ULONG, /* APPOINTMENT_COLUMN_END_TIME */ + G_TYPE_BOOLEAN, /* APPOINTMENT_COLUMN_ALL_DAY */ + G_TYPE_STRING); /* APPOINTMENT_COLUMN_COLOR */ + + calwin->priv->appointments_filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (GTK_TREE_MODEL (calwin->priv->appointments_model), NULL)); + gtk_tree_model_filter_set_visible_func (calwin->priv->appointments_filter, + (GtkTreeModelFilterVisibleFunc) is_for_filter, + GINT_TO_POINTER (APPOINTMENT_TYPE_APPOINTMENT), + NULL); +} + +static void +calendar_window_create_tasks_model (CalendarWindow *calwin) +{ + calwin->priv->tasks_model = gtk_list_store_new (N_TASK_COLUMNS, + G_TYPE_STRING, /* TASK_COLUMN_UID */ + G_TYPE_INT, /* TASK_COLUMN_TYPE */ + G_TYPE_STRING, /* TASK_COLUMN_SUMMARY */ + G_TYPE_STRING, /* TASK_COLUMN_DESCRIPTION */ + G_TYPE_ULONG, /* TASK_COLUMN_START_TIME */ + G_TYPE_STRING, /* TASK_COLUMN_START_TEXT */ + G_TYPE_ULONG, /* TASK_COLUMN_DUE_TIME */ + G_TYPE_STRING, /* TASK_COLUMN_DUE_TEXT */ + G_TYPE_INT, /* TASK_COLUMN_PERCENT_COMPLETE */ + G_TYPE_STRING, /* TASK_COLUMN_PERCENT_COMPLETE_TEXT */ + G_TYPE_BOOLEAN, /* TASK_COLUMN_COMPLETED */ + G_TYPE_ULONG, /* TASK_COLUMN_COMPLETED_TIME */ + G_TYPE_INT, /* TASK_COLUMN_PRIORITY */ + G_TYPE_STRING); /* TASK_COLUMN_COLOR */ + + calwin->priv->tasks_filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (GTK_TREE_MODEL (calwin->priv->tasks_model), NULL)); + gtk_tree_model_filter_set_visible_func (calwin->priv->tasks_filter, + (GtkTreeModelFilterVisibleFunc) is_for_filter, + GINT_TO_POINTER (TASK_TYPE_TASK), + NULL); +} + +static GtkWidget * +create_hig_calendar_frame (CalendarWindow *calwin, + const char *title, + const char *button_label, + const char *key, + GCallback callback) +{ + return create_hig_frame (calwin, title, button_label, key, callback); +} + + +static GtkWidget * +create_appointment_list (CalendarWindow *calwin, + GtkWidget **tree_view, + GtkWidget **scrolled_window) +{ + GtkWidget *frame; + GtkWidget *list; + GtkTreeViewColumn *column; + GtkCellRenderer *cell; + + frame = create_hig_calendar_frame (calwin, _("Appointments"), NULL, + KEY_SHOW_CALENDAR_EVENTS, NULL); + + list = gtk_tree_view_new (); + gtk_tree_view_set_model (GTK_TREE_VIEW (list), + GTK_TREE_MODEL (calwin->priv->appointments_filter)); + + column = gtk_tree_view_column_new (); + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, cell, FALSE); + gtk_tree_view_column_set_attributes (column, cell, + "text", APPOINTMENT_COLUMN_START_TEXT, + NULL); + + cell = gtk_cell_renderer_text_new (); + g_object_set (cell, + "wrap-mode", PANGO_WRAP_WORD_CHAR, + "wrap-width", 200, + "ellipsize", PANGO_ELLIPSIZE_END, + NULL); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_set_attributes (column, cell, + "text", APPOINTMENT_COLUMN_SUMMARY, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (list), column); + + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (list), FALSE); + gtk_widget_set_has_tooltip (list, TRUE); + g_signal_connect (list, "query-tooltip", G_CALLBACK (appointment_tooltip_query_cb), calwin); + g_signal_connect (list, "row-activated", G_CALLBACK (appointment_row_activated_cb), calwin); + + *scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (*scrolled_window), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (*scrolled_window), + GTK_SHADOW_IN); + gtk_widget_set_size_request (*scrolled_window, -1, 150); + gtk_container_add (GTK_CONTAINER (*scrolled_window), list); + + gtk_container_add (GTK_CONTAINER (frame), *scrolled_window); + + /* Ensure the scrolled window and tree view are visible */ + gtk_widget_show (*scrolled_window); + gtk_widget_show (list); + + /* Appointment list widgets created */ + + *tree_view = list; + return frame; +} + +static GtkWidget * +create_task_list (CalendarWindow *calwin, + GtkWidget **tree_view, + GtkWidget **scrolled_window) +{ + GtkWidget *frame; + GtkWidget *list; + GtkTreeViewColumn *column; + GtkCellRenderer *cell; + + frame = create_hig_calendar_frame (calwin, _("Tasks"), _("Add"), + KEY_SHOW_TASKS, G_CALLBACK (add_task)); + + list = gtk_tree_view_new (); + gtk_tree_view_set_model (GTK_TREE_VIEW (list), + GTK_TREE_MODEL (calwin->priv->tasks_filter)); + + column = gtk_tree_view_column_new (); + + /* Completion checkbox */ + cell = gtk_cell_renderer_toggle_new (); + gtk_tree_view_column_pack_start (column, cell, FALSE); + gtk_tree_view_column_set_attributes (column, cell, + "active", TASK_COLUMN_COMPLETED, + NULL); + g_signal_connect (cell, "toggled", G_CALLBACK (task_completion_toggled_cb), calwin); + + + /* Task summary */ + cell = gtk_cell_renderer_text_new (); + g_object_set (cell, + "wrap-mode", PANGO_WRAP_WORD_CHAR, + "wrap-width", 200, + "ellipsize", PANGO_ELLIPSIZE_END, + NULL); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_set_attributes (column, cell, + "text", TASK_COLUMN_SUMMARY, + "strikethrough", TASK_COLUMN_COMPLETED, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (list), column); + + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (list), FALSE); + gtk_widget_set_has_tooltip (list, TRUE); + g_signal_connect (list, "query-tooltip", G_CALLBACK (task_tooltip_query_cb), calwin); + g_signal_connect (list, "row-activated", G_CALLBACK (task_row_activated_cb), calwin); + + *scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (*scrolled_window), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (*scrolled_window), + GTK_SHADOW_IN); + gtk_widget_set_size_request (*scrolled_window, -1, 150); + gtk_container_add (GTK_CONTAINER (*scrolled_window), list); + + gtk_container_add (GTK_CONTAINER (frame), *scrolled_window); + + /* Create task entry field */ + calwin->priv->task_entry = gtk_entry_new (); + gtk_entry_set_placeholder_text (GTK_ENTRY (calwin->priv->task_entry), _("Enter task description...")); + gtk_widget_set_can_focus (calwin->priv->task_entry, TRUE); + gtk_widget_set_sensitive (calwin->priv->task_entry, TRUE); + g_signal_connect (calwin->priv->task_entry, "key-press-event", G_CALLBACK (task_entry_key_press_cb), calwin); + g_signal_connect (calwin->priv->task_entry, "activate", G_CALLBACK (task_entry_activate_cb), calwin); + gtk_container_add (GTK_CONTAINER (frame), calwin->priv->task_entry); + + /* Ensure the scrolled window and tree view are visible */ + gtk_widget_show (*scrolled_window); + gtk_widget_show (list); + + /* Hide task entry after all show operations are complete */ + g_idle_add (hide_task_entry_idle, calwin); + + *tree_view = list; + return frame; +} + +static void +calendar_window_pack_pim (CalendarWindow *calwin, + GtkWidget *vbox) +{ + GtkWidget *list; + GtkWidget *tree_view; + GtkWidget *scrolled_window; + gboolean show_calendar_events; + gboolean show_tasks; + + /* Check if calendar events should be shown */ + show_calendar_events = g_settings_get_boolean (calwin->priv->settings, KEY_SHOW_CALENDAR_EVENTS); + show_tasks = g_settings_get_boolean (calwin->priv->settings, KEY_SHOW_TASKS); + + if (!show_calendar_events && !show_tasks) { + return; + } + + /* Initialize calendar client if not already done */ + if (!calwin->priv->client && calwin->priv->settings) { + calwin->priv->client = calendar_client_new (calwin->priv->settings); + + if (calwin->priv->client) { + if (show_calendar_events) { + calwin->priv->client_appointments_changed_id = g_signal_connect_swapped (calwin->priv->client, + "appointments-changed", + G_CALLBACK (handle_appointments_changed), + calwin); + } + if (show_tasks) { + calwin->priv->client_tasks_changed_id = g_signal_connect_swapped (calwin->priv->client, + "tasks-changed", + G_CALLBACK (handle_tasks_changed), + calwin); + } + } + } + + if (!calwin->priv->client) { + g_warning ("Failed to create calendar client in calendar_window_pack_pim"); + return; + } + + /* Create and pack appointments list if enabled */ + if (show_calendar_events) { + calendar_window_create_appointments_model (calwin); + list = create_appointment_list (calwin, &tree_view, &scrolled_window); + update_frame_visibility (list, + GTK_TREE_MODEL (calwin->priv->appointments_filter)); + calwin->priv->appointment_list = list; + + gtk_box_pack_start (GTK_BOX (vbox), + calwin->priv->appointment_list, + TRUE, TRUE, 0); + } + + /* Create and pack tasks list if enabled */ + if (show_tasks) { + calendar_window_create_tasks_model (calwin); + list = create_task_list (calwin, &tree_view, &scrolled_window); + update_frame_visibility (list, + GTK_TREE_MODEL (calwin->priv->tasks_filter)); + calwin->priv->task_list = list; + + gtk_box_pack_start (GTK_BOX (vbox), + calwin->priv->task_list, + TRUE, TRUE, 0); + } + + /* Initialize calendar client with current date now that client is ready */ + if (calwin->priv->client && calwin->priv->calendar) { + guint year, month, day; + gtk_calendar_get_date (GTK_CALENDAR (calwin->priv->calendar), &year, &month, &day); + /* Set a flag to indicate we're initializing to prevent redundant calls */ + g_object_set_data (G_OBJECT (calwin), "initializing", GINT_TO_POINTER (1)); + + calendar_client_select_month (calwin->priv->client, month, year); + calendar_client_select_day (calwin->priv->client, day); + + /* Clear the initialization flag and trigger initial load */ + g_object_set_data (G_OBJECT (calwin), "initializing", GINT_TO_POINTER (0)); + + /* Now trigger the initial appointments and tasks load */ + if (show_calendar_events) { + handle_appointments_changed (calwin); + } + if (show_tasks) { + handle_tasks_changed (calwin); + } + } +} + +static gboolean +is_for_filter (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gint type; + gint expected_type = GPOINTER_TO_INT (data); + + /* Check if this is a task model or appointment model */ + if (expected_type == TASK_TYPE_TASK) { + gtk_tree_model_get (model, iter, TASK_COLUMN_TYPE, &type, -1); + } else { + gtk_tree_model_get (model, iter, APPOINTMENT_COLUMN_TYPE, &type, -1); + } + return type == expected_type; +} + +static void +mark_day_on_calendar (CalendarClient *client, + guint day, + CalendarWindow *calwin) +{ + gtk_calendar_mark_day (GTK_CALENDAR (calwin->priv->calendar), day); +} + + + +static gint +compare_appointments_by_time (const CalendarAppointment *a, const CalendarAppointment *b) +{ + /* Sort by start time - earlier appointments first */ + if (a->start_time < b->start_time) + return -1; + else if (a->start_time > b->start_time) + return 1; + else + return 0; +} + +static gint +compare_tasks_by_due_time (const CalendarTask *a, const CalendarTask *b) +{ + /* Sort by due time - earlier due dates first, then by priority */ + if (a->due_time && b->due_time) { + if (a->due_time < b->due_time) + return -1; + else if (a->due_time > b->due_time) + return 1; + } else if (a->due_time && !b->due_time) { + return -1; /* Tasks with due dates come first */ + } else if (!a->due_time && b->due_time) { + return 1; + } + + /* If due times are equal or both missing, sort by priority (higher priority first) */ + if (a->priority > b->priority) + return -1; + else if (a->priority < b->priority) + return 1; + else + return 0; +} + +static void +handle_appointments_changed (CalendarWindow *calwin) +{ + GSList *events, *l; + guint year, month, day; + + /* Skip redundant calls during initialization */ + if (g_object_get_data (G_OBJECT (calwin), "initializing")) { + return; + } + + if (calwin->priv->calendar) { + gtk_calendar_clear_marks (GTK_CALENDAR (calwin->priv->calendar)); + + calendar_client_foreach_appointment_day (calwin->priv->client, + (CalendarDayIter) mark_day_on_calendar, + calwin); + } + + gtk_list_store_clear (calwin->priv->appointments_model); + + calendar_client_get_date (calwin->priv->client, &year, &month, &day); + + events = calendar_client_get_events (calwin->priv->client, + CALENDAR_EVENT_APPOINTMENT); + + /* Sort appointments by start time for better display order */ + events = g_slist_sort (events, (GCompareFunc) compare_appointments_by_time); + + /* Found appointments for current date */ + for (l = events; l; l = l->next) { + CalendarAppointment *appointment = l->data; + GtkTreeIter iter; + char *start_text; + + g_assert (CALENDAR_EVENT (appointment)->type == CALENDAR_EVENT_APPOINTMENT); + + if (appointment->is_all_day) + start_text = g_strdup (_("All Day")); + else + start_text = format_time (calendar_window_get_time_format (calwin), + appointment->start_time, + year, month, day); + + gtk_list_store_append (calwin->priv->appointments_model, &iter); + /* Appointment added to model */ + gtk_list_store_set (calwin->priv->appointments_model, &iter, + APPOINTMENT_COLUMN_UID, appointment->uid, + APPOINTMENT_COLUMN_TYPE, APPOINTMENT_TYPE_APPOINTMENT, + APPOINTMENT_COLUMN_SUMMARY, appointment->summary, + APPOINTMENT_COLUMN_DESCRIPTION, appointment->description, + APPOINTMENT_COLUMN_START_TIME, (gint64)appointment->start_time, + APPOINTMENT_COLUMN_START_TEXT, start_text, + APPOINTMENT_COLUMN_END_TIME, (gint64)appointment->end_time, + APPOINTMENT_COLUMN_ALL_DAY, appointment->is_all_day, + APPOINTMENT_COLUMN_COLOR, appointment->color_string, + -1); + + g_free (start_text); + } + + /* Refresh filter before checking visibility */ + if (calwin->priv->appointments_filter) + gtk_tree_model_filter_refilter (calwin->priv->appointments_filter); + + update_frame_visibility (calwin->priv->appointment_list, + GTK_TREE_MODEL (calwin->priv->appointments_filter)); +} + +static void +handle_tasks_changed (CalendarWindow *calwin) +{ + GSList *events, *l; + guint year, month, day; + + /* Skip redundant calls during initialization */ + if (g_object_get_data (G_OBJECT (calwin), "initializing")) { + return; + } + + if (!calwin->priv->tasks_model) { + return; + } + + gtk_list_store_clear (calwin->priv->tasks_model); + + calendar_client_get_date (calwin->priv->client, &year, &month, &day); + + events = calendar_client_get_events (calwin->priv->client, + CALENDAR_EVENT_TASK); + + /* Sort tasks by due time for better display order */ + events = g_slist_sort (events, (GCompareFunc) compare_tasks_by_due_time); + + /* Found tasks for current date */ + for (l = events; l; l = l->next) { + CalendarTask *task = (CalendarTask *) l->data; + GtkTreeIter iter; + char *start_text = NULL; + char *due_text = NULL; + char *percent_complete_text = NULL; + gboolean completed; + + g_assert (CALENDAR_EVENT (task)->type == CALENDAR_EVENT_TASK); + + if (task->start_time) { + start_text = format_time (calendar_window_get_time_format (calwin), + task->start_time, + year, month, day); + } else { + start_text = g_strdup (""); + } + + if (task->due_time) { + due_text = format_time (calendar_window_get_time_format (calwin), + task->due_time, + year, month, day); + } else { + due_text = g_strdup (""); + } + + /* Format percent complete as text */ + if (task->percent_complete > 0) { + percent_complete_text = g_strdup_printf ("%d%%", task->percent_complete); + } else { + percent_complete_text = g_strdup (""); + } + + completed = (task->percent_complete == 100); + + gtk_list_store_append (calwin->priv->tasks_model, &iter); + gtk_list_store_set (calwin->priv->tasks_model, &iter, + TASK_COLUMN_UID, task->uid, + TASK_COLUMN_TYPE, TASK_TYPE_TASK, + TASK_COLUMN_SUMMARY, task->summary, + TASK_COLUMN_DESCRIPTION, task->description, + TASK_COLUMN_START_TIME, (gint64)task->start_time, + TASK_COLUMN_START_TEXT, start_text, + TASK_COLUMN_DUE_TIME, (gint64)task->due_time, + TASK_COLUMN_DUE_TEXT, due_text, + TASK_COLUMN_PERCENT_COMPLETE, task->percent_complete, + TASK_COLUMN_PERCENT_COMPLETE_TEXT, percent_complete_text, + TASK_COLUMN_COMPLETED, completed, + TASK_COLUMN_COMPLETED_TIME, (gint64)task->completed_time, + TASK_COLUMN_PRIORITY, task->priority, + TASK_COLUMN_COLOR, task->color_string, + -1); + + g_free (start_text); + g_free (due_text); + g_free (percent_complete_text); + } + + /* Refresh filter before checking visibility */ + if (calwin->priv->tasks_filter) + gtk_tree_model_filter_refilter (calwin->priv->tasks_filter); + + update_frame_visibility (calwin->priv->task_list, + GTK_TREE_MODEL (calwin->priv->tasks_filter)); +} + +static void +appointment_row_activated_cb (GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + gpointer user_data) +{ + GAppInfo *app_info; + GError *error = NULL; + + /* Launch Evolution calendar */ + app_info = g_app_info_get_default_for_type ("text/calendar", FALSE); + if (!app_info) { + /* Try launching evolution directly if no calendar app is set */ + app_info = g_app_info_create_from_commandline ("evolution -c calendar", + "Evolution Calendar", + G_APP_INFO_CREATE_NONE, + &error); + } + + if (app_info) { + if (!g_app_info_launch (app_info, NULL, NULL, &error)) { + g_warning ("Failed to launch calendar application: %s", error->message); + g_error_free (error); + } + g_object_unref (app_info); + } else { + g_warning ("No calendar application found"); + if (error) { + g_warning ("Error: %s", error->message); + g_error_free (error); + } + } +} - calwin->priv->settings = g_settings_new_with_path (CLOCK_SCHEMA, calwin->priv->prefs_path); +static gboolean +appointment_tooltip_query_cb (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_mode, + GtkTooltip *tooltip, + gpointer user_data) +{ + GtkTreeView *tree_view = GTK_TREE_VIEW (widget); + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + gchar *summary, *description, *start_text; + gchar *tooltip_text, *end_text; + gboolean all_day; + gulong start_time, end_time; + + if (!gtk_tree_view_get_tooltip_context (tree_view, &x, &y, keyboard_mode, + &model, &path, &iter)) { + return FALSE; + } + + gtk_tree_model_get (model, &iter, + APPOINTMENT_COLUMN_SUMMARY, &summary, + APPOINTMENT_COLUMN_DESCRIPTION, &description, + APPOINTMENT_COLUMN_START_TEXT, &start_text, + APPOINTMENT_COLUMN_START_TIME, &start_time, + APPOINTMENT_COLUMN_END_TIME, &end_time, + APPOINTMENT_COLUMN_ALL_DAY, &all_day, + -1); + + if (!summary) { + gtk_tree_path_free (path); + return FALSE; + } + + /* Format end time */ + if (!all_day && end_time > 0) { + GDateTime *end_dt = g_date_time_new_from_unix_utc (end_time); + if (end_dt) { + end_text = g_date_time_format (end_dt, "%H:%M"); + g_date_time_unref (end_dt); + } else { + end_text = NULL; + } + } else { + end_text = NULL; + } + + if (description && strlen (description) > 0) { + if (all_day) { + tooltip_text = g_markup_printf_escaped ("<b>%s</b>\n%s\nAll Day", summary, description); + } else if (end_text) { + tooltip_text = g_markup_printf_escaped ("<b>%s</b>\n%s\n%s - %s", summary, description, start_text ? start_text : "", end_text); + } else { + tooltip_text = g_markup_printf_escaped ("<b>%s</b>\n%s\n%s", summary, description, start_text ? start_text : ""); + } + } else { + if (all_day) { + tooltip_text = g_markup_printf_escaped ("<b>%s</b>\nAll Day", summary); + } else if (end_text) { + tooltip_text = g_markup_printf_escaped ("<b>%s</b>\n%s - %s", summary, start_text ? start_text : "", end_text); + } else { + tooltip_text = g_markup_printf_escaped ("<b>%s</b>\n%s", summary, start_text ? start_text : ""); + } + } + + gtk_tooltip_set_markup (tooltip, tooltip_text); + gtk_tree_view_set_tooltip_row (tree_view, tooltip, path); + + g_free (summary); + g_free (description); + g_free (start_text); + g_free (end_text); + g_free (tooltip_text); + gtk_tree_path_free (path); + + return TRUE; +} + +static gboolean +task_tooltip_query_cb (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_mode, + GtkTooltip *tooltip, + gpointer user_data) +{ + GtkTreeView *tree_view = GTK_TREE_VIEW (widget); + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + gchar *summary, *description, *start_text, *due_text; + gchar *tooltip_text; + gint percent_complete, priority; + + if (!gtk_tree_view_get_tooltip_context (tree_view, &x, &y, keyboard_mode, + &model, &path, &iter)) { + return FALSE; + } + + gtk_tree_model_get (model, &iter, + TASK_COLUMN_SUMMARY, &summary, + TASK_COLUMN_DESCRIPTION, &description, + TASK_COLUMN_START_TEXT, &start_text, + TASK_COLUMN_DUE_TEXT, &due_text, + TASK_COLUMN_PERCENT_COMPLETE, &percent_complete, + TASK_COLUMN_PRIORITY, &priority, + -1); + + if (!summary) { + gtk_tree_path_free (path); + return FALSE; + } + + /* Build tooltip with task information */ + if (description && strlen (description) > 0) { + tooltip_text = g_markup_printf_escaped ("<b>%s</b>\n%s\nProgress: %d%%", + summary, description, percent_complete); + } else { + tooltip_text = g_markup_printf_escaped ("<b>%s</b>\nProgress: %d%%", + summary, percent_complete); + } + + /* Add due date if available */ + if (due_text && strlen (due_text) > 0) { + gchar *temp = tooltip_text; + tooltip_text = g_markup_printf_escaped ("%s\nDue: %s", temp, due_text); + g_free (temp); + } + + gtk_tooltip_set_markup (tooltip, tooltip_text); + gtk_tree_view_set_tooltip_row (tree_view, tooltip, path); + + g_free (summary); + g_free (description); + g_free (start_text); + g_free (due_text); + g_free (tooltip_text); + gtk_tree_path_free (path); + + return TRUE; +} + +static void +task_row_activated_cb (GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + gpointer user_data) +{ + GAppInfo *app_info; + GError *error = NULL; + + /* Launch Evolution tasks */ + app_info = g_app_info_get_default_for_uri_scheme ("task"); + if (!app_info) { + /* Fallback to Evolution tasks directly */ + app_info = g_app_info_create_from_commandline ("evolution --component=tasks", + "Evolution Tasks", + G_APP_INFO_CREATE_NONE, + &error); + } + + if (app_info) { + g_app_info_launch (app_info, NULL, NULL, &error); + g_object_unref (app_info); + } + + if (error) { + g_warning ("Failed to launch Evolution tasks: %s", error->message); + g_error_free (error); + } } + +static void +task_completion_toggled_cb (GtkCellRendererToggle *cell, + gchar *path_str, + CalendarWindow *calwin) +{ + GtkTreePath *path; + GtkTreeIter iter; + gchar *task_uid; + gboolean completed; + gint percent_complete; + + path = gtk_tree_path_new_from_string (path_str); + + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (calwin->priv->tasks_filter), &iter, path)) { + gtk_tree_path_free (path); + return; + } + + gtk_tree_model_get (GTK_TREE_MODEL (calwin->priv->tasks_filter), &iter, + TASK_COLUMN_UID, &task_uid, + TASK_COLUMN_COMPLETED, &completed, + TASK_COLUMN_PERCENT_COMPLETE, &percent_complete, + -1); + + /* Toggle completion state */ + completed = !completed; + percent_complete = completed ? 100 : 0; + + /* Update the Evolution task */ + if (calwin->priv->client && task_uid) { + calendar_client_set_task_completed (calwin->priv->client, + task_uid, + completed, + percent_complete); + } + + g_free (task_uid); + gtk_tree_path_free (path); +} + +static gboolean +task_entry_key_press_cb (GtkWidget *widget, + GdkEventKey *event, + CalendarWindow *calwin) +{ + const gchar *text; + + if (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) { + /* Get the text from the entry */ + text = gtk_entry_get_text (GTK_ENTRY (widget)); + + /* Create task if text is not empty */ + if (text && *text != '\0') { + if (calwin->priv->client) { + gboolean success = calendar_client_create_task (calwin->priv->client, text); + if (success) { + /* Clear the entry and hide it */ + gtk_entry_set_text (GTK_ENTRY (widget), ""); + gtk_widget_hide (widget); + } else { + g_warning ("Failed to create task"); + } + } + } + return TRUE; /* Event handled */ + } else if (event->keyval == GDK_KEY_Escape) { + /* Clear the entry and hide it */ + gtk_entry_set_text (GTK_ENTRY (widget), ""); + gtk_widget_hide (widget); + return TRUE; /* Event handled */ + } + + return FALSE; /* Let other handlers process the event */ +} + +static void +task_entry_activate_cb (GtkEntry *entry, + CalendarWindow *calwin) +{ + const gchar *text; + + /* Get the text from the entry */ + text = gtk_entry_get_text (entry); + + /* Create task if text is not empty */ + if (text && *text != '\0') { + if (calwin->priv->client) { + gboolean success = calendar_client_create_task (calwin->priv->client, text); + if (success) { + /* Clear the entry and hide it */ + gtk_entry_set_text (entry, ""); + gtk_widget_hide (GTK_WIDGET (entry)); + } else { + g_warning ("Failed to create task"); + } + } + } +} + +#endif /* HAVE_EDS */ diff --git a/applets/clock/calendar-window.h b/applets/clock/calendar-window.h index d4ef49dd..e24fdfc3 100644 --- a/applets/clock/calendar-window.h +++ b/applets/clock/calendar-window.h @@ -61,7 +61,8 @@ struct _CalendarWindowClass { GType calendar_window_get_type (void) G_GNUC_CONST; GtkWidget *calendar_window_new (time_t *static_current_time, const char *prefs_dir, - gboolean invert_order); + gboolean invert_order, + GSettings *settings); void calendar_window_refresh (CalendarWindow *calwin); diff --git a/applets/clock/clock-face.c b/applets/clock/clock-face.c index a30afd13..7340c8a8 100644 --- a/applets/clock/clock-face.c +++ b/applets/clock/clock-face.c @@ -369,20 +369,9 @@ clock_face_finalize (GObject *obj) { ClockFacePrivate *priv = clock_face_get_instance_private (CLOCK_FACE(obj)); - if (priv->location) { - g_object_unref (priv->location); - priv->location = NULL; - } - - if (priv->face_pixbuf) { - g_object_unref (priv->face_pixbuf); - priv->face_pixbuf = NULL; - } - - if (priv->size_widget) { - g_object_unref (priv->size_widget); - priv->size_widget = NULL; - } + g_clear_object (&priv->location); + g_clear_object (&priv->face_pixbuf); + g_clear_object (&priv->size_widget); G_OBJECT_CLASS (clock_face_parent_class)->finalize (obj); diff --git a/applets/clock/clock-face.h b/applets/clock/clock-face.h index 3c665c63..00106ca4 100644 --- a/applets/clock/clock-face.h +++ b/applets/clock/clock-face.h @@ -56,7 +56,6 @@ GtkWidget *clock_face_new_with_location (ClockFaceSize size, GtkWidget *size_widget); gboolean clock_face_refresh (ClockFace *this); - #ifdef __cplusplus } #endif diff --git a/applets/clock/clock-location-tile.c b/applets/clock/clock-location-tile.c index 55bc749a..385d0da1 100644 --- a/applets/clock/clock-location-tile.c +++ b/applets/clock/clock-location-tile.c @@ -82,7 +82,7 @@ clock_location_tile_new (ClockLocation *loc, g_signal_connect (priv->weather_icon, "query-tooltip", G_CALLBACK (weather_tooltip), this); - priv->location_weather_updated_id = g_signal_connect (G_OBJECT (loc), "weather-updated", + priv->location_weather_updated_id = g_signal_connect (loc, "weather-updated", G_CALLBACK (update_weather_icon), this); return this; @@ -140,22 +140,22 @@ clock_location_tile_finalize (GObject *g_obj) priv = clock_location_tile_get_instance_private (this); if (priv->location) { - g_signal_handler_disconnect (priv->location, priv->location_weather_updated_id); - priv->location_weather_updated_id = 0; - - g_object_unref (priv->location); - priv->location = NULL; - } +#if GLIB_CHECK_VERSION(2,62,0) + g_clear_signal_handler (&priv->location_weather_updated_id, + priv->location); +#else + if (priv->location_weather_updated_id != 0) { + g_signal_handler_disconnect (priv->location, + priv->location_weather_updated_id); + priv->location_weather_updated_id = 0; + } +#endif - if (priv->button_group) { - g_object_unref (priv->button_group); - priv->button_group = NULL; + g_clear_object (&priv->location); } - if (priv->current_group) { - g_object_unref (priv->current_group); - priv->current_group = NULL; - } + g_clear_object (&priv->button_group); + g_clear_object (&priv->current_group); G_OBJECT_CLASS (clock_location_tile_parent_class)->finalize (g_obj); } @@ -434,7 +434,7 @@ format_time (struct tm *now, * weekday differs from the weekday at the location * (the %A expands to the weekday). The %p expands to * am/pm. */ - format = _("%l:%M <small>%p (%A)</small>"); + format = _("%_I:%M <small>%p (%A)</small>"); } else { /* Translators: This is a strftime format string. @@ -451,7 +451,7 @@ format_time (struct tm *now, * It is used to display the time in 12-hours format * (eg, like in the US: 8:10 am). The %p expands to * am/pm. */ - format = _("%l:%M <small>%p</small>"); + format = _("%_I:%M <small>%p</small>"); } else { /* Translators: This is a strftime format string. @@ -497,7 +497,7 @@ convert_time_to_str (time_t now, ClockFormat clock_format) * It is used to display the time in 12-hours format (eg, like * in the US: 8:10 am). The %p expands to am/pm. */ - format = _("%l:%M %p"); + format = _("%_I:%M %p"); } else { /* Translators: This is a strftime format string. diff --git a/applets/clock/clock-location.c b/applets/clock/clock-location.c index e5b84bf1..637d834d 100644 --- a/applets/clock/clock-location.c +++ b/applets/clock/clock-location.c @@ -210,17 +210,14 @@ clock_location_finalize (GObject *g_obj) G_CALLBACK (network_changed), CLOCK_LOCATION (g_obj)); - g_free (priv->name); - g_free (priv->city); + g_clear_pointer (&priv->name, g_free); + g_clear_pointer (&priv->city, g_free); - if (priv->systz) { - g_object_unref (priv->systz); - priv->systz = NULL; - } + g_clear_object (&priv->systz); - g_free (priv->timezone); - g_free (priv->tzname); - g_free (priv->weather_code); + g_clear_pointer (&priv->timezone, g_free); + g_clear_pointer (&priv->tzname, g_free); + g_clear_pointer (&priv->weather_code, g_free); if (priv->weather_info) { weather_info_free (priv->weather_info); @@ -437,7 +434,6 @@ clock_location_is_current (ClockLocation *loc) return FALSE; } - glong clock_location_get_offset (ClockLocation *loc) { diff --git a/applets/clock/clock-map.c b/applets/clock/clock-map.c index fc096c60..97441c62 100644 --- a/applets/clock/clock-map.c +++ b/applets/clock/clock-map.c @@ -150,32 +150,15 @@ clock_map_finalize (GObject *g_obj) priv->highlight_timeout_id = 0; } - if (priv->stock_map_pixbuf) { - g_object_unref (priv->stock_map_pixbuf); - priv->stock_map_pixbuf = NULL; - } + g_clear_object (&priv->stock_map_pixbuf); for (i = 0; i < MARKER_NB; i++) { - if (priv->location_marker_pixbuf[i]) { - g_object_unref (priv->location_marker_pixbuf[i]); - priv->location_marker_pixbuf[i] = NULL; - } + g_clear_object (&priv->location_marker_pixbuf[i]); } - if (priv->location_map_pixbuf) { - g_object_unref (priv->location_map_pixbuf); - priv->location_map_pixbuf = NULL; - } - - if (priv->shadow_pixbuf) { - g_object_unref (priv->shadow_pixbuf); - priv->shadow_pixbuf = NULL; - } - - if (priv->shadow_map_pixbuf) { - g_object_unref (priv->shadow_map_pixbuf); - priv->shadow_map_pixbuf = NULL; - } + g_clear_object (&priv->location_map_pixbuf); + g_clear_object (&priv->shadow_pixbuf); + g_clear_object (&priv->shadow_map_pixbuf); G_OBJECT_CLASS (clock_map_parent_class)->finalize (g_obj); } diff --git a/applets/clock/clock-map.h b/applets/clock/clock-map.h index 0df4a416..d400fa9b 100644 --- a/applets/clock/clock-map.h +++ b/applets/clock/clock-map.h @@ -36,7 +36,6 @@ void clock_map_refresh (ClockMap *this); void clock_map_update_time (ClockMap *this); void clock_map_blink_location (ClockMap *this, ClockLocation *loc); - #ifdef __cplusplus } #endif diff --git a/applets/clock/clock-sunpos.c b/applets/clock/clock-sunpos.c index 20a7b619..7b3ee86a 100644 --- a/applets/clock/clock-sunpos.c +++ b/applets/clock/clock-sunpos.c @@ -170,7 +170,6 @@ sun_position (time_t unix_time, gdouble *lat, gdouble *lon) *lon = ra; } - #if 0 int main (int argc, char *argv[]) diff --git a/applets/clock/clock.c b/applets/clock/clock.c index 1b3480fe..4b7920c1 100644 --- a/applets/clock/clock.c +++ b/applets/clock/clock.c @@ -51,6 +51,11 @@ #include <gdk/gdkkeysyms.h> #include <gio/gio.h> +#if defined (CLOCK_INPROCESS) && defined (HAVE_WAYLAND) +#include <gdk/gdkwayland.h> +#include <gtk-layer-shell/gtk-layer-shell.h> +#endif + #ifdef HAVE_X11 #include <gdk/gdkx.h> #endif @@ -84,6 +89,20 @@ #define KEY_CITIES "cities" #define KEY_TEMPERATURE_UNIT "temperature-unit" #define KEY_SPEED_UNIT "speed-unit" +#define KEY_SHOW_CALENDAR_EVENTS "show-calendar-events" +#define KEY_SHOW_TASKS "show-tasks" + +/* For watching for when the system resumes from sleep mode (e.g. suspend) + * and updating the clock as soon as that happens. */ +#define LOGIND_RUNNING() (access("/run/systemd/seats/", F_OK) >= 0) +#define SYSTEMD_LOGIND_SERVICE "org.freedesktop.login1" +#define SYSTEMD_LOGIND_PATH "/org/freedesktop/login1" +#define SYSTEMD_LOGIND_INTERFACE "org.freedesktop.login1.Manager" +#define CK_SERVICE "org.freedesktop.ConsoleKit" +#define CK_MANAGER_PATH "/org/freedesktop/ConsoleKit/Manager" +#define CK_MANAGER_INTERFACE "org.freedesktop.ConsoleKit.Manager" + +#define _clock_get_widget(x,y) (GTK_WIDGET (gtk_builder_get_object ((x)->builder, (y)))) enum { COL_CITY_NAME = 0, @@ -110,6 +129,7 @@ struct _ClockData { GtkWidget *props; GtkWidget *calendar_popup; + gboolean calendar_popup_destroying; GtkWidget *clock_vbox; GtkSizeGroup *clock_group; @@ -184,6 +204,8 @@ struct _ClockData { GSettings *settings; const gchar *weather_icon_name; + + GDBusProxy *system_manager_proxy; }; /* Used to count the number of clock instances. It's there to know when we @@ -241,13 +263,6 @@ clock_box_class_init (ClockBoxClass *klass) /* Clock */ -static inline GtkWidget * -_clock_get_widget (ClockData *cd, - const char *name) -{ - return GTK_WIDGET (gtk_builder_get_object (cd->builder, name)); -} - static void unfix_size (ClockData *cd) { @@ -373,7 +388,7 @@ calculate_minimum_height (GtkWidget *widget, { GtkStateFlags state; GtkStyleContext *style_context; - const PangoFontDescription *font_desc; + PangoFontDescription *font_desc; GtkBorder padding; PangoContext *pango_context; PangoFontMetrics *metrics; @@ -394,6 +409,7 @@ calculate_minimum_height (GtkWidget *widget, descent = pango_font_metrics_get_descent (metrics); pango_font_metrics_unref (metrics); + pango_font_description_free (font_desc); gtk_style_context_get_padding (style_context, state, &padding); @@ -445,7 +461,7 @@ get_updated_timeformat (ClockData *cd) /* Translators: This is a strftime format string. * It is used to display the time in 12-hours format (eg, like * in the US: 8:10 am). The %p expands to am/pm. */ - time_format = cd->showseconds ? _("%l:%M:%S %p") : _("%l:%M %p"); + time_format = cd->showseconds ? _("%_I:%M:%S %p") : _("%_I:%M %p"); else /* Translators: This is a strftime format string. * It is used to display the time in 24-hours format (eg, like @@ -751,6 +767,12 @@ free_locations (ClockData *cd) static void destroy_clock (GtkWidget * widget, ClockData *cd) { + if (cd->system_manager_proxy) + { + g_signal_handlers_disconnect_by_data (cd->system_manager_proxy, cd); + g_object_unref (cd->system_manager_proxy); + } + if (cd->settings) g_signal_handlers_disconnect_by_data( cd->settings, cd); @@ -769,9 +791,12 @@ destroy_clock (GtkWidget * widget, ClockData *cd) gtk_widget_destroy (cd->props); cd->props = NULL; - if (cd->calendar_popup) + if (cd->calendar_popup && !cd->calendar_popup_destroying) { + cd->calendar_popup_destroying = TRUE; gtk_widget_destroy (cd->calendar_popup); + } cd->calendar_popup = NULL; + cd->calendar_popup_destroying = FALSE; g_free (cd->timeformat); @@ -838,11 +863,23 @@ create_calendar (ClockData *cd) { GtkWidget *window; char *prefs_path; + char *fallback_path = NULL; prefs_path = mate_panel_applet_get_preferences_path (MATE_PANEL_APPLET (cd->applet)); + + /* Provide a fallback preferences path in case the applet doesn't + * provide one. This happens when running outside the actual panel, + * like in mate-panel-test-applets */ + if (!prefs_path || !prefs_path[0]) { + fallback_path = g_strdup ("/org/mate/panel/applets/clock/"); + g_free (prefs_path); + prefs_path = fallback_path; + } + window = calendar_window_new (&cd->current_time, prefs_path, - cd->orient == MATE_PANEL_APPLET_ORIENT_UP); + cd->orient == MATE_PANEL_APPLET_ORIENT_UP, + cd->settings); g_free (prefs_path); calendar_window_set_show_weeks (CALENDAR_WINDOW (window), @@ -854,9 +891,9 @@ create_calendar (ClockData *cd) g_signal_connect (window, "edit-locations", G_CALLBACK (edit_locations_cb), cd); - g_signal_connect (window, "delete_event", + g_signal_connect (window, "delete-event", G_CALLBACK (delete_event), cd->panel_button); - g_signal_connect (window, "key_press_event", + g_signal_connect (window, "key-press-event", G_CALLBACK (close_on_escape), cd->panel_button); /*Name this window so the default theme can be overridden in panel theme, @@ -874,105 +911,203 @@ create_calendar (ClockData *cd) static void position_calendar_popup (ClockData *cd) { -#ifdef HAVE_X11 - GtkRequisition req; - GtkAllocation allocation; - GdkDisplay *display; - GdkScreen *screen; - GdkRectangle monitor; - GdkGravity gravity = GDK_GRAVITY_NORTH_WEST; - int button_w, button_h; - int x, y; - int w, h; - int i, n; - gboolean found_monitor = FALSE; - - if (!GDK_IS_X11_DISPLAY (gdk_display_get_default ())) - return; +#if defined(HAVE_X11) && defined(GDK_WINDOWING_X11) + if (GDK_IS_X11_DISPLAY (gdk_display_get_default ())) + { + GtkRequisition req; + GtkAllocation allocation; + GdkDisplay *display; + GdkScreen *screen; + GdkRectangle monitor; + GdkGravity gravity = GDK_GRAVITY_NORTH_WEST; + int button_w, button_h; + int x, y; + int w, h; + int i, n; + gboolean found_monitor = FALSE; + + + /* Get root origin of the toggle button, and position above that. */ + gdk_window_get_origin (gtk_widget_get_window (cd->panel_button), + &x, &y); + + gtk_window_get_size (GTK_WINDOW (cd->calendar_popup), &w, &h); + gtk_widget_get_preferred_size (cd->calendar_popup, &req, NULL); + w = req.width; + h = req.height; + + gtk_widget_get_allocation (cd->panel_button, &allocation); + button_w = allocation.width; + button_h = allocation.height; + + display = gtk_widget_get_display (GTK_WIDGET (cd->calendar_popup)); + + n = gdk_display_get_n_monitors (display); + for (i = 0; i < n; i++) { + gdk_monitor_get_geometry (gdk_display_get_monitor (display, i), &monitor); + if (x >= monitor.x && x <= monitor.x + monitor.width && + y >= monitor.y && y <= monitor.y + monitor.height) { + found_monitor = TRUE; + break; + } + } - /* Get root origin of the toggle button, and position above that. */ - gdk_window_get_origin (gtk_widget_get_window (cd->panel_button), - &x, &y); + if (!found_monitor) { + /* eek, we should be on one of those xinerama + monitors */ + monitor.x = 0; + monitor.y = 0; + monitor.width = WidthOfScreen (gdk_x11_screen_get_xscreen (screen)); + monitor.height = HeightOfScreen (gdk_x11_screen_get_xscreen (screen)); + } - gtk_window_get_size (GTK_WINDOW (cd->calendar_popup), &w, &h); - gtk_widget_get_preferred_size (cd->calendar_popup, &req, NULL); - w = req.width; - h = req.height; + /* Based on panel orientation, position the popup. + * Ignore window gravity since the window is undecorated. + * The orientations are all named backward from what + * I expected. + */ + switch (cd->orient) { + case MATE_PANEL_APPLET_ORIENT_RIGHT: + x += button_w; + if ((y + h) > monitor.y + monitor.height) + y -= (y + h) - (monitor.y + monitor.height); + + if ((y + h) > (monitor.height / 2)) + gravity = GDK_GRAVITY_SOUTH_WEST; + else + gravity = GDK_GRAVITY_NORTH_WEST; - gtk_widget_get_allocation (cd->panel_button, &allocation); - button_w = allocation.width; - button_h = allocation.height; - - screen = gtk_window_get_screen (GTK_WINDOW (cd->calendar_popup)); - display = gdk_screen_get_display (screen); - - n = gdk_display_get_n_monitors (display); - for (i = 0; i < n; i++) { - gdk_monitor_get_geometry (gdk_display_get_monitor (display, i), &monitor); - if (x >= monitor.x && x <= monitor.x + monitor.width && - y >= monitor.y && y <= monitor.y + monitor.height) { - found_monitor = TRUE; break; - } - } + case MATE_PANEL_APPLET_ORIENT_LEFT: + x -= w; + if ((y + h) > monitor.y + monitor.height) + y -= (y + h) - (monitor.y + monitor.height); - if (!found_monitor) { - /* eek, we should be on one of those xinerama - monitors */ - monitor.x = 0; - monitor.y = 0; - monitor.width = WidthOfScreen (gdk_x11_screen_get_xscreen (screen)); - monitor.height = HeightOfScreen (gdk_x11_screen_get_xscreen (screen)); - } + if ((y + h) > (monitor.height / 2)) + gravity = GDK_GRAVITY_SOUTH_EAST; + else + gravity = GDK_GRAVITY_NORTH_EAST; - /* Based on panel orientation, position the popup. - * Ignore window gravity since the window is undecorated. - * The orientations are all named backward from what - * I expected. - */ - switch (cd->orient) { - case MATE_PANEL_APPLET_ORIENT_RIGHT: - x += button_w; - if ((y + h) > monitor.y + monitor.height) - y -= (y + h) - (monitor.y + monitor.height); + break; + case MATE_PANEL_APPLET_ORIENT_DOWN: + y += button_h; + if ((x + w) > monitor.x + monitor.width) + x -= (x + w) - (monitor.x + monitor.width); - if ((y + h) > (monitor.height / 2)) - gravity = GDK_GRAVITY_SOUTH_WEST; - else gravity = GDK_GRAVITY_NORTH_WEST; - break; - case MATE_PANEL_APPLET_ORIENT_LEFT: - x -= w; - if ((y + h) > monitor.y + monitor.height) - y -= (y + h) - (monitor.y + monitor.height); + break; + case MATE_PANEL_APPLET_ORIENT_UP: + y -= h; + if ((x + w) > monitor.x + monitor.width) + x -= (x + w) - (monitor.x + monitor.width); - if ((y + h) > (monitor.height / 2)) - gravity = GDK_GRAVITY_SOUTH_EAST; - else - gravity = GDK_GRAVITY_NORTH_EAST; + gravity = GDK_GRAVITY_SOUTH_WEST; - break; - case MATE_PANEL_APPLET_ORIENT_DOWN: - y += button_h; - if ((x + w) > monitor.x + monitor.width) - x -= (x + w) - (monitor.x + monitor.width); + break; + } - gravity = GDK_GRAVITY_NORTH_WEST; + gtk_window_move (GTK_WINDOW (cd->calendar_popup), x, y); + gtk_window_set_gravity (GTK_WINDOW (cd->calendar_popup), gravity); + } +#endif - break; - case MATE_PANEL_APPLET_ORIENT_UP: - y -= h; - if ((x + w) > monitor.x + monitor.width) - x -= (x + w) - (monitor.x + monitor.width); + /*Only build wayland support when building in process*/ +#if defined(CLOCK_INPROCESS) && defined(HAVE_WAYLAND) && defined(GDK_WINDOWING_WAYLAND) + if (GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ())) + { + GtkWindow *window; + GdkWindow *panelwin; + GtkWidget *toplevel; + int x, y, w, h, panel_w, panel_h; + + /*Get the calendar window dimensions*/ + window = (GTK_WINDOW (cd->calendar_popup)); + gtk_window_get_size (window, &w, &h); + /*Find the position of the applet*/ + gdk_window_get_origin (gtk_widget_get_window (cd->panel_button), + &x, &y); + + /*Get the panel dimensions*/ + toplevel = gtk_widget_get_toplevel (cd->applet); + panelwin = gtk_widget_get_window (toplevel); + gdk_window_get_geometry (panelwin, NULL, NULL, &panel_w, &panel_h); + + /*Set up GTK Layer Shell*/ + gtk_layer_init_for_window (window); + gtk_layer_set_layer (window, GTK_LAYER_SHELL_LAYER_TOP); + + switch (cd->orient) { + case MATE_PANEL_APPLET_ORIENT_RIGHT: + gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_LEFT, TRUE); + gtk_layer_set_margin (window, GTK_LAYER_SHELL_EDGE_LEFT, 0); + gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_RIGHT, FALSE); + if (y < (panel_h - h)) + { + gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_TOP, TRUE); + gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_BOTTOM, FALSE); + gtk_layer_set_margin (window, GTK_LAYER_SHELL_EDGE_TOP, y); + } + else + { + gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_BOTTOM, TRUE); + gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_TOP, FALSE); + } + break; - gravity = GDK_GRAVITY_SOUTH_WEST; + case MATE_PANEL_APPLET_ORIENT_LEFT: + gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_RIGHT, TRUE); + gtk_layer_set_margin (window, GTK_LAYER_SHELL_EDGE_RIGHT, 0); + gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_LEFT, FALSE); + if (y < (panel_h - h)) + { + gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_TOP, TRUE); + gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_BOTTOM, FALSE); + gtk_layer_set_margin (window, GTK_LAYER_SHELL_EDGE_TOP, y); + } + else + { + gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_BOTTOM, TRUE); + gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_TOP, FALSE); + } + break; - break; - } + case MATE_PANEL_APPLET_ORIENT_DOWN: + gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_TOP, TRUE); + gtk_layer_set_margin (window, GTK_LAYER_SHELL_EDGE_TOP, 0); + gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_BOTTOM, FALSE); + if (x < (panel_w - w)) + { + gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_LEFT, TRUE); + gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_RIGHT, FALSE); + gtk_layer_set_margin (window, GTK_LAYER_SHELL_EDGE_LEFT, x); + } + else + { + gtk_layer_set_anchor(window, GTK_LAYER_SHELL_EDGE_RIGHT, TRUE); + gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_LEFT, FALSE); + } + break; - gtk_window_move (GTK_WINDOW (cd->calendar_popup), x, y); - gtk_window_set_gravity (GTK_WINDOW (cd->calendar_popup), gravity); + case MATE_PANEL_APPLET_ORIENT_UP: + gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_BOTTOM, TRUE); + gtk_layer_set_margin (window, GTK_LAYER_SHELL_EDGE_BOTTOM, 0); + gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_TOP, FALSE); + if (x < (panel_w - w)) + { + gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_LEFT, TRUE); + gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_RIGHT, FALSE); + gtk_layer_set_margin (window, GTK_LAYER_SHELL_EDGE_LEFT, x); + } + else + { + gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_RIGHT, TRUE); + gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_LEFT, FALSE); + } + break; + } + return; + } #endif } @@ -1049,7 +1184,6 @@ create_cities_store (ClockData *cd) } g_slist_free (list); - if (cd->prefs_window) { GtkWidget *widget = _clock_get_widget (cd, "cities_list"); gtk_tree_view_set_model (GTK_TREE_VIEW (widget), @@ -1070,35 +1204,17 @@ sort_locations_by_time (gconstpointer a, gconstpointer b) clock_location_localtime (loc_a, &tm_a); clock_location_localtime (loc_b, &tm_b); - ret = (tm_a.tm_year == tm_b.tm_year) ? 0 : 1; - if (ret) { - return (tm_a.tm_year < tm_b.tm_year) ? -1 : 1; - } - - ret = (tm_a.tm_mon == tm_b.tm_mon) ? 0 : 1; - if (ret) { - return (tm_a.tm_mon < tm_b.tm_mon) ? -1 : 1; - } - - ret = (tm_a.tm_mday == tm_b.tm_mday) ? 0 : 1; - if (ret) { - return (tm_a.tm_mday < tm_b.tm_mday) ? -1 : 1; - } - - ret = (tm_a.tm_hour == tm_b.tm_hour) ? 0 : 1; - if (ret) { - return (tm_a.tm_hour < tm_b.tm_hour) ? -1 : 1; - } - - ret = (tm_a.tm_min == tm_b.tm_min) ? 0 : 1; - if (ret) { - return (tm_a.tm_min < tm_b.tm_min) ? -1 : 1; - } - - ret = (tm_a.tm_sec == tm_b.tm_sec) ? 0 : 1; - if (ret) { - return (tm_a.tm_sec < tm_b.tm_sec) ? -1 : 1; - } + ret = tm_a.tm_year - tm_b.tm_year; + if (! ret) + ret = tm_a.tm_mon - tm_b.tm_mon; + if (! ret) + ret = tm_a.tm_mday - tm_b.tm_mday; + if (! ret) + ret = tm_a.tm_hour - tm_b.tm_hour; + if (! ret) + ret = tm_a.tm_min - tm_b.tm_min; + if (! ret) + ret = tm_a.tm_sec - tm_b.tm_sec; return ret; } @@ -1149,6 +1265,7 @@ location_tile_need_clock_format_cb(ClockLocationTile *tile, gpointer data) static void create_cities_section (ClockData *cd) { + GtkWidget *cities_box; GSList *node; GSList *cities; GSList *l; @@ -1162,8 +1279,16 @@ create_cities_section (ClockData *cd) g_slist_free (cd->location_tiles); cd->location_tiles = NULL; - cd->cities_section = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); - gtk_container_set_border_width (GTK_CONTAINER (cd->cities_section), 0); + cd->cities_section = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (cd->cities_section), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (cd->cities_section), + GTK_SHADOW_NONE); + gtk_scrolled_window_set_propagate_natural_height (GTK_SCROLLED_WINDOW (cd->cities_section), + TRUE); + + cities_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); cities = cd->locations; if (g_slist_length (cities) == 0) { @@ -1185,7 +1310,7 @@ create_cities_section (ClockData *cd) g_signal_connect (city, "need-clock-format", G_CALLBACK (location_tile_need_clock_format_cb), cd); - gtk_box_pack_start (GTK_BOX (cd->cities_section), + gtk_box_pack_start (GTK_BOX (cities_box), GTK_WIDGET (city), FALSE, FALSE, 0); @@ -1196,6 +1321,8 @@ create_cities_section (ClockData *cd) g_slist_free (node); + gtk_container_add (GTK_CONTAINER (cd->cities_section), cities_box); + gtk_box_pack_end (GTK_BOX (cd->clock_vbox), cd->cities_section, FALSE, FALSE, 0); @@ -1239,9 +1366,11 @@ static void update_calendar_popup (ClockData *cd) { if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (cd->panel_button))) { - if (cd->calendar_popup) { + if (cd->calendar_popup && !cd->calendar_popup_destroying) { + cd->calendar_popup_destroying = TRUE; gtk_widget_destroy (cd->calendar_popup); cd->calendar_popup = NULL; + cd->calendar_popup_destroying = FALSE; cd->cities_section = NULL; cd->map_widget = NULL; cd->clock_vbox = NULL; @@ -1254,7 +1383,7 @@ update_calendar_popup (ClockData *cd) return; } - if (!cd->calendar_popup) { + if (!cd->calendar_popup && !cd->calendar_popup_destroying) { cd->calendar_popup = create_calendar (cd); g_object_add_weak_pointer (G_OBJECT (cd->calendar_popup), (gpointer *) &cd->calendar_popup); @@ -1288,7 +1417,7 @@ do_not_eat_button_press (GtkWidget *widget, GdkEventButton *event) { if (event->button != 1) - g_signal_stop_emission_by_name (widget, "button_press_event"); + g_signal_stop_emission_by_name (widget, "button-press-event"); return FALSE; } @@ -1342,7 +1471,6 @@ force_no_button_vertical_padding (GtkWidget *widget) GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); g_object_unref (provider); - gtk_widget_set_name (widget, "clock-applet-button"); } @@ -1367,11 +1495,11 @@ create_main_clock_label (ClockData *cd) label = gtk_label_new (NULL); /*Fixme-this is invalid for labels with any recent GTK3 version, maybe all of them*/ /* - g_signal_connect (label, "size_request", + g_signal_connect (label, "size-request", G_CALLBACK (clock_size_request), cd); */ - g_signal_connect_swapped (label, "style_set", + g_signal_connect_swapped (label, "style-set", G_CALLBACK (unfix_size), cd); gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER); @@ -1417,11 +1545,11 @@ create_clock_widget (ClockData *cd) { /* Main toggle button */ cd->panel_button = create_main_clock_button (); - g_signal_connect (cd->panel_button, "button_press_event", + g_signal_connect (cd->panel_button, "button-press-event", G_CALLBACK (do_not_eat_button_press), NULL); g_signal_connect (cd->panel_button, "toggled", G_CALLBACK (toggle_calendar), cd); - g_signal_connect (G_OBJECT (cd->panel_button), "destroy", + g_signal_connect (cd->panel_button, "destroy", G_CALLBACK (destroy_clock), cd); gtk_widget_show (cd->panel_button); @@ -1826,11 +1954,11 @@ output_cb (GtkSpinButton *spin, { GtkAdjustment *adj; gchar *text; - int value; + gdouble value; adj = gtk_spin_button_get_adjustment (spin); - value = (int) gtk_adjustment_get_value (adj); - text = g_strdup_printf ("%02d", value); + value = gtk_adjustment_get_value (adj); + text = g_strdup_printf ("%02.0f", value); gtk_entry_set_text (GTK_ENTRY (spin), text); g_free (text); @@ -1846,7 +1974,7 @@ ensure_time_settings_window_is_created (ClockData *cd) return; cd->set_time_window = _clock_get_widget (cd, "set-time-window"); - g_signal_connect (cd->set_time_window, "delete_event", + g_signal_connect (cd->set_time_window, "delete-event", G_CALLBACK (delete_time_settings), cd); cd->calendar = _clock_get_widget (cd, "calendar"); @@ -1854,12 +1982,6 @@ ensure_time_settings_window_is_created (ClockData *cd) cd->minutes_spin = _clock_get_widget (cd, "minutes_spin"); cd->seconds_spin = _clock_get_widget (cd, "seconds_spin"); - gtk_entry_set_width_chars (GTK_ENTRY (cd->hours_spin), 2); - gtk_entry_set_width_chars (GTK_ENTRY (cd->minutes_spin), 2); - gtk_entry_set_width_chars (GTK_ENTRY (cd->seconds_spin), 2); - gtk_entry_set_alignment (GTK_ENTRY (cd->hours_spin), 1.0); - gtk_entry_set_alignment (GTK_ENTRY (cd->minutes_spin), 1.0); - gtk_entry_set_alignment (GTK_ENTRY (cd->seconds_spin), 1.0); g_signal_connect (cd->seconds_spin, "wrapped", G_CALLBACK (wrap_cb), cd); g_signal_connect (cd->minutes_spin, "wrapped", G_CALLBACK (wrap_cb), cd); g_signal_connect (cd->hours_spin, "wrapped", G_CALLBACK (wrap_cb), cd); @@ -1923,7 +2045,8 @@ format_changed (GSettings *settings, gchar *key, ClockData *clock) { - int new_format; + ClockFormat new_format; + new_format = g_settings_get_enum (settings, key); if (!clock->can_handle_format_12 && new_format == CLOCK_FORMAT_12) @@ -2126,9 +2249,6 @@ location_set_current_cb (ClockLocation *loc, static void locations_changed (ClockData *cd) { - GSList *l; - glong id; - if (!cd->locations) { if (cd->weather_obox) gtk_widget_hide (cd->weather_obox); @@ -2143,8 +2263,9 @@ locations_changed (ClockData *cd) gtk_widget_show (cd->weather_obox); } - for (l = cd->locations; l; l = l->next) { + for (GSList *l = cd->locations; l; l = l->next) { ClockLocation *loc = l->data; + glong id; id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (loc), "weather-updated")); if (id == 0) { @@ -2163,7 +2284,6 @@ locations_changed (ClockData *cd) create_cities_section (cd); } - static void set_locations (ClockData *cd, GSList *locations) { @@ -2324,21 +2444,25 @@ clock_timezone_changed (SystemTimezone *systz, refresh_click_timeout_time_only (cd); } +#define unit_combo_box_set_active(x,y) (gtk_combo_box_set_active (GTK_COMBO_BOX (gtk_builder_get_object (cd->builder, (x))), (y))) + static void temperature_unit_changed (GSettings *settings, gchar *key, ClockData *cd) { - cd->temperature_unit = g_settings_get_enum (settings, key); - if (cd->temperature_unit > 0) - { - GtkWidget *widget; - gint oldvalue; - widget = _clock_get_widget (cd, "temperature_combo"); - oldvalue = gtk_combo_box_get_active (GTK_COMBO_BOX (widget)) + 2; - if (oldvalue != cd->speed_unit) - gtk_combo_box_set_active (GTK_COMBO_BOX (widget), cd->temperature_unit - 2); - } + TempUnit temperature_unit = g_settings_get_enum (settings, key); + + if (cd->temperature_unit == temperature_unit) + return; + + /* TEMP_UNIT_INVALID = 0 */ + if (temperature_unit == TEMP_UNIT_INVALID) + return; + + unit_combo_box_set_active ("temperature_combo", + ((int) temperature_unit) - 2); + cd->temperature_unit = temperature_unit; update_weather_locations (cd); } @@ -2347,16 +2471,18 @@ speed_unit_changed (GSettings *settings, gchar *key, ClockData *cd) { - cd->speed_unit = g_settings_get_enum (settings, key); - if (cd->speed_unit > 0) - { - GtkWidget *widget; - gint oldvalue; - widget = _clock_get_widget (cd, "wind_speed_combo"); - oldvalue = gtk_combo_box_get_active (GTK_COMBO_BOX (widget)) + 2; - if (oldvalue != cd->speed_unit) - gtk_combo_box_set_active (GTK_COMBO_BOX (widget), cd->speed_unit - 2); - } + SpeedUnit speed_unit = g_settings_get_enum (settings, key); + + if (cd->speed_unit == speed_unit) + return; + + /* SPEED_UNIT_INVALID = 0 */ + if (speed_unit == SPEED_UNIT_INVALID) + return; + + unit_combo_box_set_active ("wind_speed_combo", + ((int) speed_unit) - 2); + cd->speed_unit = speed_unit; update_weather_locations (cd); } @@ -2399,18 +2525,8 @@ show_week_changed (GSettings *settings, static void setup_gsettings (ClockData *cd) { - gint format; - gchar *custom_format; - cd->settings = mate_panel_applet_settings_new (MATE_PANEL_APPLET (cd->applet), CLOCK_SCHEMA); - /* hack to allow users to set custom format in dconf-editor */ - format = g_settings_get_enum (cd->settings, KEY_FORMAT); - custom_format = g_settings_get_string (cd->settings, KEY_CUSTOM_FORMAT); - g_settings_set_enum (cd->settings, KEY_FORMAT, format); - g_settings_set_string (cd->settings, KEY_CUSTOM_FORMAT, custom_format); - g_free (custom_format); - g_signal_connect (cd->settings, "changed::" KEY_FORMAT, G_CALLBACK (format_changed), cd); g_signal_connect (cd->settings, "changed::" KEY_SHOW_SECONDS, G_CALLBACK (show_seconds_changed), cd); g_signal_connect (cd->settings, "changed::" KEY_SHOW_DATE, G_CALLBACK (show_date_changed), cd); @@ -2426,7 +2542,6 @@ setup_gsettings (ClockData *cd) static GSList * parse_gsettings_cities (ClockData *cd, gchar **values) { - gint i; LocationParserData data; GMarkupParseContext *context; @@ -2436,7 +2551,7 @@ parse_gsettings_cities (ClockData *cd, gchar **values) context = g_markup_parse_context_new (&location_parser, 0, &data, NULL); if (values) { - for (i = 0; values[i]; i++) { + for (gint i = 0; values[i]; i++) { g_markup_parse_context_parse (context, values[i], strlen(values[i]), NULL); } } @@ -2484,6 +2599,86 @@ load_gsettings (ClockData *cd) set_locations (cd, cities); } +/* When the system manager (ConsoleKit or systemd-logind) reports that + * the system has just resumed from sleep mode (e.g. suspend/hibernate), + * update the clock and the weather/temperature readings. That way, if + * the user suspended the system for an hour and then wakes the system up, + * the user will immediately see the current time and (if possible) + * updated weather data. Without this extra code, the user would most + * likely wake the system up and see the weather from an hour ago, and if + * the clock is set to not display seconds (only minutes), the clock may + * show an inaccurate time for up to a minute after resume. + */ +static void +system_manager_signal_cb (GDBusProxy *proxy, + gchar *sender_name, + gchar *signal_name, + GVariant *parameters, + ClockData *cd) +{ + if (g_strcmp0 (signal_name, "PrepareForSleep") == 0) + { + GVariant *variant; + gboolean active; + + variant = g_variant_get_child_value (parameters, 0); + active = g_variant_get_boolean (variant); + g_variant_unref (variant); + + /* The PrepareForSleep signal is emitted by ConsoleKit2 and + * logind, both before sleep mode is entered, and + * immediately after the system has resumed from sleep mode. + * Listeners of this signal distinguish between the two + * states by checking the parameter to this signal. If the + * parameter is TRUE, the system is about to enter sleep + * mode; if FALSE, the system has resumed from sleep mode. + * We only care about updating the clock after resumption, + * so test if the parameter is FALSE. + */ + if (active == FALSE) + { + update_clock (cd); + update_weather_locations (cd); + } + } +} + +static void +setup_monitor_for_resume (ClockData *cd) +{ + gboolean logind_running; + const char * service; + const char * path; + const char * interface; + + /* If logind is running, connect to logind; otherwise use ConsoleKit. + */ + logind_running = LOGIND_RUNNING (); + if (logind_running) { + service = SYSTEMD_LOGIND_SERVICE; + path = SYSTEMD_LOGIND_PATH; + interface = SYSTEMD_LOGIND_INTERFACE; + } else { + service = CK_SERVICE; + path = CK_MANAGER_PATH; + interface = CK_MANAGER_INTERFACE; + } + + cd->system_manager_proxy = g_dbus_proxy_new_for_bus_sync + (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, + NULL, + service, path, interface, + NULL, NULL); + + if (cd->system_manager_proxy != NULL) { + g_signal_connect (cd->system_manager_proxy, + "g-signal", + G_CALLBACK (system_manager_signal_cb), + cd); + } +} + static gboolean fill_clock_applet (MatePanelApplet *applet) { @@ -2517,13 +2712,11 @@ fill_clock_applet (MatePanelApplet *applet) /* we have to bind change_orient before we do applet_widget_add since we need to get an initial change_orient signal to set our initial oriantation, and we get that during the _add call */ - g_signal_connect (G_OBJECT (cd->applet), - "change_orient", + g_signal_connect (cd->applet, "change-orient", G_CALLBACK (applet_change_orient), cd); - g_signal_connect (G_OBJECT (cd->panel_button), - "size_allocate", + g_signal_connect (cd->panel_button, "size-allocate", G_CALLBACK (panel_button_change_pixel_size), cd); @@ -2555,10 +2748,15 @@ fill_clock_applet (MatePanelApplet *applet) /* Make sure the weather icon gets updated when the panel size changes*/ g_signal_connect (cd->applet, - "change_size", + "change-size", G_CALLBACK (weather_icon_updated_cb), cd); + /* If ConsoleKit or systemd-logind is available, set up to update + * the clock if/when the system resumes from sleep (e.g. suspend/ + * hibernate). */ + setup_monitor_for_resume (cd); + return TRUE; } @@ -2912,7 +3110,7 @@ run_prefs_locations_add (GtkButton *button, ClockData *cd) if (g_object_get_data (G_OBJECT (edit_window), "delete-handler") == NULL) { g_object_set_data (G_OBJECT (edit_window), "delete-handler", - GINT_TO_POINTER (g_signal_connect (edit_window, "delete_event", G_CALLBACK (edit_delete), cd))); + GINT_TO_POINTER (g_signal_connect (edit_window, "delete-event", G_CALLBACK (edit_delete), cd))); } location_update_ok_sensitivity (cd); @@ -3041,7 +3239,6 @@ speed_combo_changed (GtkComboBox *combo, ClockData *cd) g_settings_set_enum (cd->settings, KEY_SPEED_UNIT, value); } - static void fill_prefs_window (ClockData *cd) { @@ -3109,6 +3306,35 @@ fill_prefs_window (ClockData *cd) g_settings_bind (cd->settings, KEY_SHOW_TEMPERATURE, widget, "active", G_SETTINGS_BIND_DEFAULT); +#ifdef HAVE_EDS + /* Set the EDS calendar event checkboxes */ + widget = _clock_get_widget (cd, "show_calendar_events_check"); + if (widget) { + /* Bind to gsettings - this should sync with calendar window automatically */ + g_settings_bind (cd->settings, KEY_SHOW_CALENDAR_EVENTS, widget, "active", + G_SETTINGS_BIND_DEFAULT); + } else { + g_warning ("Could not find show_calendar_events_check widget in preferences"); + } + + /* Set the EDS tasks checkboxes */ + widget = _clock_get_widget (cd, "show_tasks_check"); + if (widget) { + /* Bind to gsettings - this should sync with calendar window automatically */ + g_settings_bind (cd->settings, KEY_SHOW_TASKS, widget, "active", + G_SETTINGS_BIND_DEFAULT); + } else { + g_warning ("Could not find show_tasks_check widget in preferences"); + } + +#else + /* Hide the Calendar tab if EDS is not available */ + widget = _clock_get_widget (cd, "vbox-calendar"); + gtk_widget_hide (widget); + widget = _clock_get_widget (cd, "label-calendar-tab"); + gtk_widget_hide (widget); +#endif + /* Fill the Cities list */ widget = _clock_get_widget (cd, "cities_list"); @@ -3195,36 +3421,35 @@ ensure_prefs_window_is_created (ClockData *cd) location_name_label = _clock_get_widget (cd, "location-name-label"); timezone_label = _clock_get_widget (cd, "timezone-label"); - if (!clock_locale_supports_am_pm ()) gtk_widget_hide (clock_options); selection = gtk_tree_view_get_selection (cd->prefs_locations); - g_signal_connect (G_OBJECT (selection), "changed", + g_signal_connect (selection, "changed", G_CALLBACK (prefs_locations_changed), cd); - g_signal_connect (G_OBJECT (cd->prefs_window), "delete_event", + g_signal_connect (cd->prefs_window, "delete-event", G_CALLBACK (prefs_hide_event), cd); - g_signal_connect (G_OBJECT (prefs_close_button), "clicked", + g_signal_connect (prefs_close_button, "clicked", G_CALLBACK (prefs_hide), cd); - g_signal_connect (G_OBJECT (prefs_help_button), "clicked", + g_signal_connect (prefs_help_button, "clicked", G_CALLBACK (prefs_help), cd); cd->prefs_location_remove_button = _clock_get_widget (cd, "prefs-locations-remove-button"); - g_signal_connect (G_OBJECT (cd->prefs_location_remove_button), "clicked", + g_signal_connect (cd->prefs_location_remove_button, "clicked", G_CALLBACK (run_prefs_locations_remove), cd); cd->prefs_location_add_button = _clock_get_widget (cd, "prefs-locations-add-button"); - g_signal_connect (G_OBJECT (cd->prefs_location_add_button), "clicked", + g_signal_connect (cd->prefs_location_add_button, "clicked", G_CALLBACK (run_prefs_locations_add), cd); cd->prefs_location_edit_button = _clock_get_widget (cd, "prefs-locations-edit-button"); - g_signal_connect (G_OBJECT (cd->prefs_location_edit_button), "clicked", + g_signal_connect (cd->prefs_location_edit_button, "clicked", G_CALLBACK (run_prefs_locations_edit), cd); edit_window = _clock_get_widget (cd, "edit-location-window"); @@ -3232,7 +3457,7 @@ ensure_prefs_window_is_created (ClockData *cd) gtk_window_set_transient_for (GTK_WINDOW (edit_window), GTK_WINDOW (cd->prefs_window)); - g_signal_connect (G_OBJECT (edit_window), "delete_event", + g_signal_connect (edit_window, "delete-event", G_CALLBACK (edit_hide_event), cd); edit_cancel_button = _clock_get_widget (cd, "edit-location-cancel-button"); @@ -3248,9 +3473,9 @@ ensure_prefs_window_is_created (ClockData *cd) gtk_label_set_mnemonic_widget (GTK_LABEL (location_name_label), GTK_WIDGET (cd->location_entry)); - g_signal_connect (G_OBJECT (cd->location_entry), "notify::location", + g_signal_connect (cd->location_entry, "notify::location", G_CALLBACK (location_changed), cd); - g_signal_connect (G_OBJECT (cd->location_entry), "changed", + g_signal_connect (cd->location_entry, "changed", G_CALLBACK (location_name_changed), cd); zone_box = _clock_get_widget (cd, "edit-location-timezone-box"); @@ -3260,15 +3485,15 @@ ensure_prefs_window_is_created (ClockData *cd) gtk_label_set_mnemonic_widget (GTK_LABEL (timezone_label), GTK_WIDGET (cd->zone_combo)); - g_signal_connect (G_OBJECT (cd->zone_combo), "notify::tzid", + g_signal_connect (cd->zone_combo, "notify::tzid", G_CALLBACK (location_timezone_changed), cd); mateweather_location_unref (world); - g_signal_connect (G_OBJECT (edit_cancel_button), "clicked", + g_signal_connect (edit_cancel_button, "clicked", G_CALLBACK (edit_hide), cd); - g_signal_connect (G_OBJECT (edit_ok_button), "clicked", + g_signal_connect (edit_ok_button, "clicked", G_CALLBACK (run_prefs_edit_save), cd); /* Set up the time setting section */ @@ -3346,8 +3571,7 @@ display_properties_dialog (ClockData *cd, gboolean start_in_locations_page) GtkWidget *notebook = _clock_get_widget (cd, "notebook"); gtk_widget_add_events (notebook, GDK_SCROLL_MASK); - g_signal_connect (GTK_NOTEBOOK (notebook), - "scroll-event", + g_signal_connect (notebook, "scroll-event", G_CALLBACK (on_notebook_scroll_event), NULL); diff --git a/applets/clock/clock.ui b/applets/clock/clock.ui index 822aca0b..2f9cfa78 100644 --- a/applets/clock/clock.ui +++ b/applets/clock/clock.ui @@ -1,55 +1,55 @@ <?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.22.0 --> +<!-- Generated with glade 3.38.2 --> <interface> <requires lib="gtk+" version="3.22"/> <object class="GtkAdjustment" id="adjustment1"> <property name="upper">59</property> <property name="value">59</property> - <property name="step_increment">1</property> - <property name="page_increment">30</property> + <property name="step-increment">1</property> + <property name="page-increment">30</property> </object> <object class="GtkAdjustment" id="adjustment2"> <property name="upper">59</property> <property name="value">59</property> - <property name="step_increment">1</property> - <property name="page_increment">30</property> + <property name="step-increment">1</property> + <property name="page-increment">30</property> </object> <object class="GtkAdjustment" id="adjustment3"> <property name="upper">23</property> <property name="value">23</property> - <property name="step_increment">1</property> - <property name="page_increment">12</property> + <property name="step-increment">1</property> + <property name="page-increment">12</property> </object> <object class="GtkImage" id="image1"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="icon_name">process-stop</property> + <property name="can-focus">False</property> + <property name="icon-name">process-stop</property> </object> <object class="GtkDialog" id="set-time-window"> - <property name="can_focus">False</property> - <property name="border_width">5</property> + <property name="can-focus">False</property> + <property name="border-width">5</property> <property name="title" translatable="yes">Time & Date</property> <property name="resizable">False</property> - <property name="type_hint">dialog</property> + <property name="type-hint">dialog</property> <child internal-child="vbox"> <object class="GtkBox" id="dialog-vbox2"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="orientation">vertical</property> <child internal-child="action_area"> <object class="GtkButtonBox" id="dialog-action_area2"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="homogeneous">True</property> <child> <object class="GtkButton" id="cancel-set-time-button"> <property name="label" translatable="yes">_Cancel</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="can_default">True</property> - <property name="receives_default">False</property> + <property name="can-focus">True</property> + <property name="can-default">True</property> + <property name="receives-default">False</property> <property name="image">image1</property> - <property name="use_underline">True</property> + <property name="use-underline">True</property> </object> <packing> <property name="expand">False</property> @@ -61,9 +61,9 @@ <object class="GtkButton" id="set-time-button"> <property name="label" translatable="yes">_Set System Time</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="use_underline">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="use-underline">True</property> </object> <packing> <property name="expand">False</property> @@ -75,28 +75,28 @@ <packing> <property name="expand">False</property> <property name="fill">False</property> - <property name="pack_type">end</property> + <property name="pack-type">end</property> <property name="position">0</property> </packing> </child> <child> <object class="GtkAlignment" id="alignment1"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="yscale">0</property> - <property name="top_padding">7</property> - <property name="bottom_padding">6</property> - <property name="left_padding">7</property> - <property name="right_padding">7</property> + <property name="top-padding">7</property> + <property name="bottom-padding">6</property> + <property name="left-padding">7</property> + <property name="right-padding">7</property> <child> <object class="GtkBox" id="time_settings_box"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="spacing">12</property> <child> <object class="GtkCalendar" id="calendar"> <property name="visible">True</property> - <property name="can_focus">True</property> + <property name="can-focus">True</property> <property name="year">2009</property> <property name="month">5</property> <property name="day">3</property> @@ -108,23 +108,26 @@ </packing> </child> <child> + <!-- n-columns=3 n-rows=3 --> <object class="GtkGrid" id="table1"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="valign">start</property> - <property name="row_spacing">6</property> - <property name="column_spacing">12</property> + <property name="row-spacing">6</property> + <property name="column-spacing">12</property> <child> <object class="GtkBox" id="hbox61"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <child> <object class="GtkSpinButton" id="hours_spin"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="invisible_char">●</property> + <property name="can-focus">True</property> + <property name="invisible-char">●</property> + <property name="width-chars">2</property> + <property name="xalign">1</property> <property name="adjustment">adjustment3</property> - <property name="climb_rate">1</property> + <property name="climb-rate">1</property> <property name="numeric">True</property> <property name="wrap">True</property> </object> @@ -137,10 +140,12 @@ <child> <object class="GtkSpinButton" id="minutes_spin"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="invisible_char">●</property> + <property name="can-focus">True</property> + <property name="invisible-char">●</property> + <property name="width-chars">2</property> + <property name="xalign">1</property> <property name="adjustment">adjustment2</property> - <property name="climb_rate">1</property> + <property name="climb-rate">1</property> <property name="numeric">True</property> <property name="wrap">True</property> </object> @@ -153,10 +158,12 @@ <child> <object class="GtkSpinButton" id="seconds_spin"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="invisible_char">●</property> + <property name="can-focus">True</property> + <property name="invisible-char">●</property> + <property name="width-chars">2</property> + <property name="xalign">1</property> <property name="adjustment">adjustment1</property> - <property name="climb_rate">1</property> + <property name="climb-rate">1</property> <property name="numeric">True</property> <property name="wrap">True</property> </object> @@ -168,48 +175,63 @@ </child> </object> <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> + <property name="left-attach">1</property> + <property name="top-attach">1</property> </packing> </child> <child> <object class="GtkLabel" id="current_time_label"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="label">23:59:59</property> <property name="xalign">0</property> </object> <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> + <property name="left-attach">1</property> + <property name="top-attach">0</property> </packing> </child> <child> <object class="GtkLabel" id="label236"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="label" translatable="yes">_Time:</property> - <property name="use_underline">True</property> - <property name="mnemonic_widget">hours_spin</property> + <property name="use-underline">True</property> + <property name="mnemonic-widget">hours_spin</property> <property name="xalign">0</property> </object> <packing> - <property name="left_attach">0</property> - <property name="top_attach">1</property> + <property name="left-attach">0</property> + <property name="top-attach">1</property> </packing> </child> <child> <object class="GtkLabel" id="label235"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="label" translatable="yes">Current Time:</property> <property name="xalign">0</property> </object> <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> + <property name="left-attach">0</property> + <property name="top-attach">0</property> </packing> </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> </object> <packing> <property name="expand">False</property> @@ -232,71 +254,68 @@ <action-widget response="0">cancel-set-time-button</action-widget> <action-widget response="0">set-time-button</action-widget> </action-widgets> - <child> - <placeholder/> - </child> </object> <object class="GtkImage" id="image2"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="icon_name">gtk-ok</property> + <property name="can-focus">False</property> + <property name="icon-name">gtk-ok</property> </object> <object class="GtkImage" id="image3"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="icon_name">process-stop</property> + <property name="can-focus">False</property> + <property name="icon-name">process-stop</property> </object> <object class="GtkImage" id="image4"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="icon_name">help-browser</property> + <property name="can-focus">False</property> + <property name="icon-name">help-browser</property> </object> <object class="GtkImage" id="image5"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="icon_name">window-close</property> + <property name="can-focus">False</property> + <property name="icon-name">window-close</property> </object> <object class="GtkImage" id="image6"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="icon_name">list-add</property> + <property name="can-focus">False</property> + <property name="icon-name">list-add</property> </object> <object class="GtkImage" id="image7"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="icon_name">gtk-edit</property> + <property name="can-focus">False</property> + <property name="icon-name">gtk-edit</property> </object> <object class="GtkImage" id="image8"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="icon_name">list-remove</property> + <property name="can-focus">False</property> + <property name="icon-name">list-remove</property> </object> <object class="GtkDialog" id="prefs-window"> - <property name="can_focus">False</property> - <property name="border_width">5</property> + <property name="can-focus">False</property> + <property name="border-width">5</property> <property name="title" translatable="yes">Clock Preferences</property> <property name="resizable">False</property> - <property name="window_position">center</property> - <property name="type_hint">dialog</property> + <property name="window-position">center</property> + <property name="type-hint">dialog</property> <child internal-child="vbox"> <object class="GtkBox" id="dialog-vbox1"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="orientation">vertical</property> <property name="spacing">2</property> <child internal-child="action_area"> <object class="GtkButtonBox" id="dialog-action_area1"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <child> <object class="GtkButton" id="prefs-help-button"> <property name="label" translatable="yes">_Help</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="can_default">True</property> - <property name="receives_default">False</property> + <property name="can-focus">True</property> + <property name="can-default">True</property> + <property name="receives-default">False</property> <property name="image">image4</property> - <property name="use_underline">True</property> + <property name="use-underline">True</property> </object> <packing> <property name="expand">False</property> @@ -308,10 +327,10 @@ <object class="GtkButton" id="time-settings-button"> <property name="label" translatable="yes">Time _Settings</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="can_default">True</property> - <property name="receives_default">False</property> - <property name="use_underline">True</property> + <property name="can-focus">True</property> + <property name="can-default">True</property> + <property name="receives-default">False</property> + <property name="use-underline">True</property> </object> <packing> <property name="expand">False</property> @@ -323,12 +342,12 @@ <object class="GtkButton" id="prefs-close-button"> <property name="label" translatable="yes">_Close</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="can_default">True</property> - <property name="has_default">True</property> - <property name="receives_default">False</property> + <property name="can-focus">True</property> + <property name="can-default">True</property> + <property name="has-default">True</property> + <property name="receives-default">False</property> <property name="image">image5</property> - <property name="use_underline">True</property> + <property name="use-underline">True</property> </object> <packing> <property name="expand">False</property> @@ -340,32 +359,32 @@ <packing> <property name="expand">False</property> <property name="fill">False</property> - <property name="pack_type">end</property> + <property name="pack-type">end</property> <property name="position">0</property> </packing> </child> <child> <object class="GtkNotebook" id="notebook"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="border_width">5</property> + <property name="can-focus">True</property> + <property name="border-width">5</property> <child> <object class="GtkBox" id="vbox17"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="border_width">12</property> + <property name="can-focus">False</property> + <property name="border-width">12</property> <property name="orientation">vertical</property> <property name="spacing">18</property> <child> <object class="GtkBox" id="clock-options"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="orientation">vertical</property> <property name="spacing">6</property> <child> <object class="GtkLabel" id="label210"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="label" translatable="yes">Clock Format</property> <property name="xalign">0</property> <attributes> @@ -381,21 +400,21 @@ <child> <object class="GtkAlignment" id="alignment32"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="left_padding">12</property> + <property name="can-focus">False</property> + <property name="left-padding">12</property> <child> <object class="GtkBox" id="hbox49"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="spacing">13</property> <child> <object class="GtkRadioButton" id="12hr_radio"> <property name="label" translatable="yes">_12 hour format</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="use_underline">True</property> - <property name="draw_indicator">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="use-underline">True</property> + <property name="draw-indicator">True</property> </object> <packing> <property name="expand">False</property> @@ -407,10 +426,10 @@ <object class="GtkRadioButton" id="24hr_radio"> <property name="label" translatable="yes">_24 hour format</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="use_underline">True</property> - <property name="draw_indicator">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="use-underline">True</property> + <property name="draw-indicator">True</property> <property name="group">12hr_radio</property> </object> <packing> @@ -438,13 +457,13 @@ <child> <object class="GtkBox" id="vbox29"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="orientation">vertical</property> <property name="spacing">6</property> <child> <object class="GtkLabel" id="label229"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="label" translatable="yes">Panel Display</property> <property name="xalign">0</property> <attributes> @@ -460,22 +479,22 @@ <child> <object class="GtkAlignment" id="alignment33"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="left_padding">12</property> + <property name="can-focus">False</property> + <property name="left-padding">12</property> <child> <object class="GtkBox" id="vbox30"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="orientation">vertical</property> <property name="spacing">6</property> <child> <object class="GtkCheckButton" id="date_check"> <property name="label" translatable="yes">Show the _date</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="use_underline">True</property> - <property name="draw_indicator">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="use-underline">True</property> + <property name="draw-indicator">True</property> </object> <packing> <property name="expand">False</property> @@ -487,10 +506,10 @@ <object class="GtkCheckButton" id="seconds_check"> <property name="label" translatable="yes">Show seco_nds</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="use_underline">True</property> - <property name="draw_indicator">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="use-underline">True</property> + <property name="draw-indicator">True</property> </object> <packing> <property name="expand">False</property> @@ -502,10 +521,10 @@ <object class="GtkCheckButton" id="weeks_check"> <property name="label" translatable="yes">Show wee_k numbers in calendar</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="use_underline">True</property> - <property name="draw_indicator">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="use-underline">True</property> + <property name="draw-indicator">True</property> </object> <packing> <property name="expand">False</property> @@ -517,10 +536,10 @@ <object class="GtkCheckButton" id="weather_check"> <property name="label" translatable="yes">Show _weather</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="use_underline">True</property> - <property name="draw_indicator">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="use-underline">True</property> + <property name="draw-indicator">True</property> </object> <packing> <property name="expand">False</property> @@ -532,10 +551,10 @@ <object class="GtkCheckButton" id="temperature_check"> <property name="label" translatable="yes">Show _temperature</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="use_underline">True</property> - <property name="draw_indicator">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="use-underline">True</property> + <property name="draw-indicator">True</property> </object> <packing> <property name="expand">False</property> @@ -564,38 +583,39 @@ <child type="tab"> <object class="GtkLabel" id="label209"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="label" translatable="yes">General</property> </object> <packing> - <property name="tab_fill">False</property> + <property name="tab-fill">False</property> </packing> </child> <child> <object class="GtkBox" id="vbox24"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="border_width">12</property> + <property name="can-focus">False</property> + <property name="border-width">12</property> <property name="orientation">vertical</property> <property name="spacing">18</property> <child> <object class="GtkBox" id="hbox54"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="spacing">6</property> <child> <object class="GtkScrolledWindow" id="scrolledwindow10"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hscrollbar_policy">never</property> - <property name="vscrollbar_policy">never</property> - <property name="shadow_type">in</property> + <property name="can-focus">True</property> + <property name="expand">True</property> + <property name="hscrollbar-policy">never</property> + <property name="vscrollbar-policy">automatic</property> + <property name="shadow-type">in</property> <child> <object class="GtkTreeView" id="cities_list"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="headers_visible">False</property> - <property name="hover_expand">True</property> + <property name="can-focus">True</property> + <property name="headers-visible">False</property> + <property name="hover-expand">True</property> <child internal-child="selection"> <object class="GtkTreeSelection"/> </child> @@ -611,19 +631,19 @@ <child> <object class="GtkButtonBox" id="vbuttonbox2"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="orientation">vertical</property> <property name="spacing">6</property> - <property name="layout_style">start</property> + <property name="layout-style">start</property> <child> <object class="GtkButton" id="prefs-locations-add-button"> <property name="label" translatable="yes">_Add</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="can_default">True</property> - <property name="receives_default">False</property> + <property name="can-focus">True</property> + <property name="can-default">True</property> + <property name="receives-default">False</property> <property name="image">image6</property> - <property name="use_underline">True</property> + <property name="use-underline">True</property> </object> <packing> <property name="expand">False</property> @@ -635,11 +655,11 @@ <object class="GtkButton" id="prefs-locations-edit-button"> <property name="label" translatable="yes">_Edit</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="can_default">True</property> - <property name="receives_default">False</property> + <property name="can-focus">True</property> + <property name="can-default">True</property> + <property name="receives-default">False</property> <property name="image">image7</property> - <property name="use_underline">True</property> + <property name="use-underline">True</property> </object> <packing> <property name="expand">False</property> @@ -651,11 +671,11 @@ <object class="GtkButton" id="prefs-locations-remove-button"> <property name="label" translatable="yes">_Remove</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="can_default">True</property> - <property name="receives_default">False</property> + <property name="can-focus">True</property> + <property name="can-default">True</property> + <property name="receives-default">False</property> <property name="image">image8</property> - <property name="use_underline">True</property> + <property name="use-underline">True</property> </object> <packing> <property name="expand">False</property> @@ -685,31 +705,31 @@ <child type="tab"> <object class="GtkLabel" id="label220"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="label" translatable="yes">Locations</property> </object> <packing> <property name="position">1</property> - <property name="tab_fill">False</property> + <property name="tab-fill">False</property> </packing> </child> <child> <object class="GtkBox" id="vbox27"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="border_width">12</property> + <property name="can-focus">False</property> + <property name="border-width">12</property> <property name="orientation">vertical</property> <property name="spacing">18</property> <child> <object class="GtkBox" id="vbox28"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="orientation">vertical</property> <property name="spacing">6</property> <child> <object class="GtkLabel" id="label224"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="label" translatable="yes">Display</property> <property name="xalign">0</property> <attributes> @@ -725,107 +745,120 @@ <child> <object class="GtkAlignment" id="alignment34"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="left_padding">12</property> + <property name="can-focus">False</property> + <property name="left-padding">12</property> <child> + <!-- n-columns=3 n-rows=4 --> <object class="GtkGrid" id="table25"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="row_spacing">6</property> - <property name="column_spacing">12</property> - <property name="column_homogeneous">True</property> + <property name="can-focus">False</property> + <property name="row-spacing">6</property> + <property name="column-spacing">12</property> + <property name="column-homogeneous">True</property> <child> <object class="GtkComboBox" id="visibility_combo"> - <property name="can_focus">False</property> + <property name="can-focus">False</property> </object> <packing> - <property name="left_attach">1</property> - <property name="top_attach">3</property> + <property name="left-attach">1</property> + <property name="top-attach">3</property> </packing> </child> <child> <object class="GtkLabel" id="label232"> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="label" translatable="yes">_Visibility unit:</property> - <property name="use_underline">True</property> - <property name="mnemonic_widget">visibility_combo</property> + <property name="use-underline">True</property> + <property name="mnemonic-widget">visibility_combo</property> <property name="xalign">0</property> </object> <packing> - <property name="left_attach">0</property> - <property name="top_attach">3</property> + <property name="left-attach">0</property> + <property name="top-attach">3</property> </packing> </child> <child> <object class="GtkComboBox" id="pressure_combo"> - <property name="can_focus">False</property> + <property name="can-focus">False</property> </object> <packing> - <property name="left_attach">1</property> - <property name="top_attach">2</property> + <property name="left-attach">1</property> + <property name="top-attach">2</property> </packing> </child> <child> <object class="GtkLabel" id="label231"> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="label" translatable="yes">_Pressure unit:</property> - <property name="use_underline">True</property> - <property name="mnemonic_widget">pressure_combo</property> + <property name="use-underline">True</property> + <property name="mnemonic-widget">pressure_combo</property> <property name="xalign">0</property> </object> <packing> - <property name="left_attach">0</property> - <property name="top_attach">2</property> + <property name="left-attach">0</property> + <property name="top-attach">2</property> </packing> </child> <child> <object class="GtkComboBox" id="wind_speed_combo"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> </object> <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> + <property name="left-attach">1</property> + <property name="top-attach">1</property> </packing> </child> <child> <object class="GtkComboBox" id="temperature_combo"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> </object> <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> + <property name="left-attach">1</property> + <property name="top-attach">0</property> </packing> </child> <child> <object class="GtkLabel" id="label228"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="label" translatable="yes">_Wind speed unit:</property> - <property name="use_underline">True</property> - <property name="mnemonic_widget">wind_speed_combo</property> + <property name="use-underline">True</property> + <property name="mnemonic-widget">wind_speed_combo</property> <property name="xalign">0</property> </object> <packing> - <property name="left_attach">0</property> - <property name="top_attach">1</property> + <property name="left-attach">0</property> + <property name="top-attach">1</property> </packing> </child> <child> <object class="GtkLabel" id="label227"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="label" translatable="yes">_Temperature unit:</property> - <property name="use_underline">True</property> - <property name="mnemonic_widget">temperature_combo</property> + <property name="use-underline">True</property> + <property name="mnemonic-widget">temperature_combo</property> <property name="xalign">0</property> </object> <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> + <property name="left-attach">0</property> + <property name="top-attach">0</property> </packing> </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> </object> </child> </object> @@ -850,12 +883,107 @@ <child type="tab"> <object class="GtkLabel" id="label223"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="label" translatable="yes">Weather</property> </object> <packing> <property name="position">2</property> - <property name="tab_fill">False</property> + <property name="tab-fill">False</property> + </packing> + </child> + <child> + <object class="GtkBox" id="vbox-calendar"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="border-width">12</property> + <property name="orientation">vertical</property> + <property name="spacing">18</property> + <child> + <object class="GtkBox" id="calendar-options"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label-calendar"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Evolution Calendar Integration</property> + <property name="xalign">0</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="vbox-calendar-options"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkCheckButton" id="show_calendar_events_check"> + <property name="label" translatable="yes">Show calendar _events</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="use-underline">True</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="show_tasks_check"> + <property name="label" translatable="yes">Show _tasks</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="use-underline">True</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="position">3</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel" id="label-calendar-tab"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Calendar</property> + </object> + <packing> + <property name="position">3</property> + <property name="tab-fill">False</property> </packing> </child> </object> @@ -872,9 +1000,6 @@ <action-widget response="0">time-settings-button</action-widget> <action-widget response="-7">prefs-close-button</action-widget> </action-widgets> - <child> - <placeholder/> - </child> </object> <object class="GtkListStore" id="liststore1"> <columns> @@ -905,28 +1030,28 @@ </data> </object> <object class="GtkDialog" id="edit-location-window"> - <property name="can_focus">False</property> - <property name="border_width">5</property> + <property name="can-focus">False</property> + <property name="border-width">5</property> <property name="resizable">False</property> - <property name="type_hint">dialog</property> + <property name="type-hint">dialog</property> <child internal-child="vbox"> <object class="GtkBox" id="dialog-vbox3"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="orientation">vertical</property> <child internal-child="action_area"> <object class="GtkButtonBox" id="dialog-action_area3"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <child> <object class="GtkButton" id="edit-location-cancel-button"> <property name="label" translatable="yes">_Cancel</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="can_default">True</property> - <property name="receives_default">False</property> + <property name="can-focus">True</property> + <property name="can-default">True</property> + <property name="receives-default">False</property> <property name="image">image3</property> - <property name="use_underline">True</property> + <property name="use-underline">True</property> </object> <packing> <property name="expand">False</property> @@ -938,11 +1063,11 @@ <object class="GtkButton" id="edit-location-ok-button"> <property name="label" translatable="yes">_OK</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="can_default">True</property> - <property name="receives_default">False</property> + <property name="can-focus">True</property> + <property name="can-default">True</property> + <property name="receives-default">False</property> <property name="image">image2</property> - <property name="use_underline">True</property> + <property name="use-underline">True</property> </object> <packing> <property name="expand">False</property> @@ -954,37 +1079,38 @@ <packing> <property name="expand">False</property> <property name="fill">False</property> - <property name="pack_type">end</property> + <property name="pack-type">end</property> <property name="position">0</property> </packing> </child> <child> + <!-- n-columns=3 n-rows=3 --> <object class="GtkGrid" id="table26"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="row_spacing">6</property> - <property name="column_spacing">6</property> + <property name="can-focus">False</property> + <property name="row-spacing">6</property> + <property name="column-spacing">6</property> <child> <object class="GtkBox" id="vbox34"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="orientation">vertical</property> <property name="spacing">6</property> <child> <object class="GtkScrolledWindow" id="scrolledwindow12"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hscrollbar_policy">never</property> - <property name="vscrollbar_policy">never</property> + <property name="can-focus">True</property> + <property name="hscrollbar-policy">never</property> + <property name="vscrollbar-policy">never</property> <child> <object class="GtkViewport" id="viewport3"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="shadow_type">none</property> + <property name="can-focus">False</property> + <property name="shadow-type">none</property> <child> <object class="GtkImage" id="image30"> - <property name="can_focus">False</property> - <property name="icon_name">image-missing</property> + <property name="can-focus">False</property> + <property name="icon-name">image-missing</property> </object> </child> </object> @@ -997,102 +1123,103 @@ </packing> </child> <child> + <!-- n-columns=4 n-rows=5 --> <object class="GtkGrid" id="table27"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="border_width">5</property> + <property name="can-focus">False</property> + <property name="border-width">5</property> <property name="orientation">vertical</property> - <property name="row_spacing">6</property> - <property name="column_spacing">6</property> + <property name="row-spacing">6</property> + <property name="column-spacing">6</property> <child> <object class="GtkLabel" id="label243"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="vexpand">True</property> <property name="label" translatable="yes" comments="Languages that have a single word that translates as either "state" or "province" should use that instead of "region"."><small><i>Type a city, region, or country name and then select a match from the pop-up.</i></small></property> - <property name="use_markup">True</property> + <property name="use-markup">True</property> <property name="wrap">True</property> <property name="xalign">0</property> </object> <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> + <property name="left-attach">1</property> + <property name="top-attach">1</property> <property name="width">3</property> </packing> </child> <child> <object class="GtkBox" id="edit-location-name-box"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="orientation">vertical</property> <child> <placeholder/> </child> </object> <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> + <property name="left-attach">1</property> + <property name="top-attach">0</property> <property name="width">3</property> </packing> </child> <child> <object class="GtkBox" id="edit-location-timezone-box"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="orientation">vertical</property> <child> <placeholder/> </child> </object> <packing> - <property name="left_attach">1</property> - <property name="top_attach">2</property> + <property name="left-attach">1</property> + <property name="top-attach">2</property> <property name="width">3</property> </packing> </child> <child> <object class="GtkLabel" id="timezone-label"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="label" translatable="yes">_Timezone:</property> - <property name="use_underline">True</property> + <property name="use-underline">True</property> <property name="xalign">0</property> </object> <packing> - <property name="left_attach">0</property> - <property name="top_attach">2</property> + <property name="left-attach">0</property> + <property name="top-attach">2</property> </packing> </child> <child> <object class="GtkLabel" id="location-name-label"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="label" translatable="yes">_Location Name:</property> - <property name="use_underline">True</property> + <property name="use-underline">True</property> <property name="xalign">0</property> </object> <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> + <property name="left-attach">0</property> + <property name="top-attach">0</property> </packing> </child> <child> <object class="GtkEntry" id="edit-location-latitude-entry"> <property name="visible">True</property> - <property name="can_focus">True</property> + <property name="can-focus">True</property> <property name="valign">center</property> <property name="hexpand">True</property> - <property name="invisible_char">•</property> + <property name="invisible-char">•</property> </object> <packing> - <property name="left_attach">1</property> - <property name="top_attach">3</property> + <property name="left-attach">1</property> + <property name="top-attach">3</property> </packing> </child> <child> <object class="GtkComboBox" id="edit-location-latitude-combo"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="model">liststore2</property> <child> <object class="GtkCellRendererText" id="cellrenderertext2"/> @@ -1102,42 +1229,42 @@ </child> </object> <packing> - <property name="left_attach">2</property> - <property name="top_attach">3</property> + <property name="left-attach">2</property> + <property name="top-attach">3</property> </packing> </child> <child> <object class="GtkLabel" id="label240"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="valign">center</property> <property name="label" translatable="yes"><i>(optional)</i></property> - <property name="use_markup">True</property> + <property name="use-markup">True</property> <property name="xalign">0</property> </object> <packing> - <property name="left_attach">3</property> - <property name="top_attach">3</property> + <property name="left-attach">3</property> + <property name="top-attach">3</property> </packing> </child> <child> <object class="GtkLabel" id="label239"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="valign">center</property> <property name="label" translatable="yes"><i>(optional)</i></property> - <property name="use_markup">True</property> + <property name="use-markup">True</property> <property name="xalign">0</property> </object> <packing> - <property name="left_attach">3</property> - <property name="top_attach">4</property> + <property name="left-attach">3</property> + <property name="top-attach">4</property> </packing> </child> <child> <object class="GtkComboBox" id="edit-location-longitude-combo"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="model">liststore1</property> <child> <object class="GtkCellRendererText" id="cellrenderertext1"/> @@ -1147,51 +1274,51 @@ </child> </object> <packing> - <property name="left_attach">2</property> - <property name="top_attach">4</property> + <property name="left-attach">2</property> + <property name="top-attach">4</property> </packing> </child> <child> <object class="GtkEntry" id="edit-location-longitude-entry"> <property name="visible">True</property> - <property name="can_focus">True</property> + <property name="can-focus">True</property> <property name="valign">center</property> <property name="hexpand">True</property> - <property name="invisible_char">•</property> + <property name="invisible-char">•</property> </object> <packing> - <property name="left_attach">1</property> - <property name="top_attach">4</property> + <property name="left-attach">1</property> + <property name="top-attach">4</property> </packing> </child> <child> <object class="GtkLabel" id="label238"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="label" translatable="yes">L_ongitude:</property> - <property name="use_underline">True</property> - <property name="mnemonic_widget">edit-location-longitude-entry</property> + <property name="use-underline">True</property> + <property name="mnemonic-widget">edit-location-longitude-entry</property> <property name="xalign">0</property> </object> <packing> - <property name="left_attach">0</property> - <property name="top_attach">4</property> + <property name="left-attach">0</property> + <property name="top-attach">4</property> </packing> </child> <child> <object class="GtkBox" id="vbox35"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="orientation">vertical</property> <property name="spacing">6</property> <property name="homogeneous">True</property> <child> <object class="GtkLabel" id="label237"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="label" translatable="yes">L_atitude:</property> - <property name="use_underline">True</property> - <property name="mnemonic_widget">edit-location-latitude-entry</property> + <property name="use-underline">True</property> + <property name="mnemonic-widget">edit-location-latitude-entry</property> <property name="xalign">0</property> </object> <packing> @@ -1202,8 +1329,8 @@ </child> </object> <packing> - <property name="left_attach">0</property> - <property name="top_attach">3</property> + <property name="left-attach">0</property> + <property name="top-attach">3</property> </packing> </child> <child> @@ -1218,10 +1345,34 @@ </child> </object> <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> + <property name="left-attach">0</property> + <property name="top-attach">0</property> </packing> </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> </object> <packing> <property name="expand">False</property> @@ -1235,8 +1386,5 @@ <action-widget response="-6">edit-location-cancel-button</action-widget> <action-widget response="-5">edit-location-ok-button</action-widget> </action-widgets> - <child> - <placeholder/> - </child> </object> </interface> diff --git a/applets/clock/org.mate.panel.applet.clock.gschema.xml.in b/applets/clock/org.mate.panel.applet.clock.gschema.xml.in index ee1b6902..ad822e46 100644 --- a/applets/clock/org.mate.panel.applet.clock.gschema.xml.in +++ b/applets/clock/org.mate.panel.applet.clock.gschema.xml.in @@ -84,5 +84,15 @@ <summary>Speed unit</summary> <description>The unit to use when showing wind speed.</description> </key> + <key name="show-calendar-events" type="b"> + <default>true</default> + <summary>Show calendar events</summary> + <description>If true, display calendar events from Evolution in the calendar window.</description> + </key> + <key name="show-tasks" type="b"> + <default>false</default> + <summary>Show tasks</summary> + <description>If true, display tasks from Evolution in the calendar window.</description> + </key> </schema> </schemalist> diff --git a/applets/clock/system-timezone.c b/applets/clock/system-timezone.c index 199756e0..a6f9c75d 100644 --- a/applets/clock/system-timezone.c +++ b/applets/clock/system-timezone.c @@ -213,8 +213,7 @@ system_timezone_constructor (GType type, g_object_unref (file); if (priv->monitors[i]) - g_signal_connect (G_OBJECT (priv->monitors[i]), - "changed", + g_signal_connect (priv->monitors [i], "changed", G_CALLBACK (system_timezone_monitor_changed), obj); } @@ -234,13 +233,11 @@ system_timezone_finalize (GObject *obj) systz = SYSTEM_TIMEZONE (obj); priv = system_timezone_get_instance_private (systz); - g_free (priv->tz); - g_free (priv->env_tz); + g_clear_pointer (&priv->tz, g_free); + g_clear_pointer (&priv->env_tz, g_free); for (i = 0; i < CHECK_NB; i++) { - if (priv->monitors[i]) - g_object_unref (priv->monitors[i]); - priv->monitors[i] = NULL; + g_clear_object (&priv->monitors[i]); } G_OBJECT_CLASS (system_timezone_parent_class)->finalize (obj); @@ -283,7 +280,6 @@ system_timezone_monitor_changed (GFileMonitor *handle, g_free (new_tz); } - /* * Code to deal with the system timezone on all distros. * There's no dependency on the SystemTimezone GObject here. @@ -372,7 +368,6 @@ system_timezone_write_etc_timezone (const char *tz, return retval; } - /* Read a file that looks like a key-file (but there's no need for groups) * and get the last value for a specific key */ static char * @@ -710,7 +705,6 @@ recursive_compare (struct stat *localtime_stat, return NULL; } - static gboolean files_are_identical_inode (struct stat *a_stat, struct stat *b_stat, @@ -721,7 +715,6 @@ files_are_identical_inode (struct stat *a_stat, return (a_stat->st_ino == b_stat->st_ino); } - /* Determine if /etc/localtime is a hard link to some file, by looking at * the inodes */ static char * diff --git a/applets/clock/system-timezone.h b/applets/clock/system-timezone.h index 0ede8976..8a2c22ef 100644 --- a/applets/clock/system-timezone.h +++ b/applets/clock/system-timezone.h @@ -35,7 +35,6 @@ extern "C" { #define SYSTEM_ZONEINFODIR "/usr/share/zoneinfo" #endif - #define SYSTEM_TIMEZONE_TYPE (system_timezone_get_type ()) #define SYSTEM_TIMEZONE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), SYSTEM_TIMEZONE_TYPE, SystemTimezone)) #define SYSTEM_TIMEZONE_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), SYSTEM_TIMEZONE_TYPE, SystemTimezoneClass)) |