diff options
author | Charles Kerr <charles.kerr@canonical.com> | 2013-06-21 15:01:54 +0000 |
---|---|---|
committer | Tarmac <Unknown> | 2013-06-21 15:01:54 +0000 |
commit | 854fb1a47bed41e41e6fed9c5e498dc81146302a (patch) | |
tree | c0db59a66b90162a7f8ae2519430249ce5a5ffb6 /src/service.c | |
parent | 05ba6fa097260473c774d0d077d4e3add3db134b (diff) | |
parent | 51bd7465ac148a34ec37cb8760daf3b282b5ed6a (diff) | |
download | ayatana-indicator-datetime-854fb1a47bed41e41e6fed9c5e498dc81146302a.tar.gz ayatana-indicator-datetime-854fb1a47bed41e41e6fed9c5e498dc81146302a.tar.bz2 ayatana-indicator-datetime-854fb1a47bed41e41e6fed9c5e498dc81146302a.zip |
This push extracts geoclue to its own self-contained module, fixes an errant g_critical, and merges ryan's timedate1 changes from the 13.04 branch.
Approved by PS Jenkins bot, Lars Uebernickel.
Diffstat (limited to 'src/service.c')
-rw-r--r-- | src/service.c | 1804 |
1 files changed, 1804 insertions, 0 deletions
diff --git a/src/service.c b/src/service.c new file mode 100644 index 0000000..558b03a --- /dev/null +++ b/src/service.c @@ -0,0 +1,1804 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + * Ted Gould <ted@canonical.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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/>. + */ + +#include "config.h" + +#include <string.h> /* strstr() */ + +#include <glib/gi18n.h> +#include <gio/gio.h> + +#include "dbus-shared.h" +#include "planner-eds.h" +#include "timezone-file.h" +#include "timezone-geoclue.h" +#include "service.h" +#include "settings-shared.h" +#include "utils.h" + +#define SKEW_CHECK_INTERVAL_SEC 10 +#define SKEW_DIFF_THRESHOLD_USEC ((SKEW_CHECK_INTERVAL_SEC+5) * G_USEC_PER_SEC) + +G_DEFINE_TYPE (IndicatorDatetimeService, + indicator_datetime_service, + G_TYPE_OBJECT) + +enum +{ + SIGNAL_NAME_LOST, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +enum +{ + SECTION_HEADER = (1<<0), + SECTION_CALENDAR = (1<<1), + SECTION_APPOINTMENTS = (1<<2), + SECTION_LOCATIONS = (1<<3), + SECTION_SETTINGS = (1<<4), +}; + +enum +{ + PROFILE_DESKTOP, + PROFILE_GREETER, + N_PROFILES +}; + +static const char * const menu_names[N_PROFILES] = +{ + "desktop", + "greeter" +}; + +struct ProfileMenuInfo +{ + /* the root level -- the header is the only child of this */ + GMenu * menu; + + /* parent of the sections. This is the header's submenu */ + GMenu * submenu; + + guint export_id; +}; + +struct _IndicatorDatetimeServicePrivate +{ + GCancellable * cancellable; + + GSettings * settings; + + IndicatorDatetimeTimezone * tz_file; + IndicatorDatetimeTimezone * tz_geoclue; + IndicatorDatetimePlanner * planner; + + guint own_id; + guint actions_export_id; + GDBusConnection * conn; + + guint rebuild_id; + int rebuild_flags; + struct ProfileMenuInfo menus[N_PROFILES]; + + GDateTime * skew_time; + guint skew_timer; + + guint header_timer; + guint timezone_timer; + + /* Which year/month to show in the calendar, + and which day should get the cursor. + This value is reflected in the calendar action's state */ + GDateTime * calendar_date; + + GSimpleActionGroup * actions; + GSimpleAction * header_action; + GSimpleAction * calendar_action; + + GDBusProxy * login1_manager; +}; + +typedef IndicatorDatetimeServicePrivate priv_t; + +/*** +**** +***/ + +static void +indicator_clear_timer (guint * tag) +{ + if (*tag) + { + g_source_remove (*tag); + *tag = 0; + } +} + +/*** +**** +***/ + +static void rebuild_now (IndicatorDatetimeService * self, int section); +static void rebuild_soon (IndicatorDatetimeService * self, int section); + +static inline void +rebuild_header_soon (IndicatorDatetimeService * self) +{ + rebuild_soon (self, SECTION_HEADER); +} + +static inline void +rebuild_calendar_section_soon (IndicatorDatetimeService * self) +{ + rebuild_soon (self, SECTION_CALENDAR); +} + +static inline void +rebuild_appointments_section_soon (IndicatorDatetimeService * self) +{ + rebuild_soon (self, SECTION_APPOINTMENTS); +} + +static inline void +rebuild_locations_section_soon (IndicatorDatetimeService * self) +{ + rebuild_soon (self, SECTION_LOCATIONS); +} + +static inline void +rebuild_settings_section_soon (IndicatorDatetimeService * self) +{ + rebuild_soon (self, SECTION_SETTINGS); +} + +/*** +**** TIMEZONE TIMER +***/ + +/* + * Periodically rebuild the sections that have time format strings + * that are dependent on the current time: + * + * 1. appointment menuitems' time format strings depend on the + * current time; for example, they don't show the day of week + * if the appointment is today. + * + * 2. location menuitems' time format strings depend on the + * current time; for example, they don't show the day of the week + * if the local date and location date are the same. + * + * 3. the "local date" menuitem in the calendar section is, + * obviously, dependent on the local time. + * + * In short, we want to update whenever the number of days between two zone + * might have changed. We do that by updating when either zone's day changes. + * + * Since not all UTC offsets are evenly divisible by hours + * (examples: Newfoundland UTC-03:30, Nepal UTC+05:45), refreshing on the hour + * is not enough. We need to refresh at HH:00, HH:15, HH:30, and HH:45. + */ + +static guint +calculate_seconds_until_next_fifteen_minutes (GDateTime * now) +{ + char * str; + gint minute; + guint seconds; + GTimeSpan diff; + GDateTime * next; + GDateTime * start_of_next; + + minute = g_date_time_get_minute (now); + minute = 15 - (minute % 15); + next = g_date_time_add_minutes (now, minute); + start_of_next = g_date_time_new_local (g_date_time_get_year (next), + g_date_time_get_month (next), + g_date_time_get_day_of_month (next), + g_date_time_get_hour (next), + g_date_time_get_minute (next), + 1); + + str = g_date_time_format (start_of_next, "%F %T"); + g_debug ("%s %s the next timestamp rebuild will be at %s", G_STRLOC, G_STRFUNC, str); + g_free (str); + + diff = g_date_time_difference (start_of_next, now); + seconds = (diff + (G_TIME_SPAN_SECOND-1)) / G_TIME_SPAN_SECOND; + + g_date_time_unref (start_of_next); + g_date_time_unref (next); + + return seconds; +} + +static void start_timezone_timer (IndicatorDatetimeService * self); + +static gboolean +on_timezone_timer (gpointer gself) +{ + IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (gself); + + rebuild_soon (self, SECTION_CALENDAR | + SECTION_APPOINTMENTS | + SECTION_LOCATIONS); + + /* Restarting the timer to recalculate the interval. This helps us to hit + our marks despite clock skew, suspend+resume, leap seconds, etc */ + start_timezone_timer (self); + return G_SOURCE_REMOVE; +} + +static void +start_timezone_timer (IndicatorDatetimeService * self) +{ + GDateTime * now; + guint seconds; + priv_t * p = self->priv; + + indicator_clear_timer (&p->timezone_timer); + + now = indicator_datetime_service_get_localtime (self); + seconds = calculate_seconds_until_next_fifteen_minutes (now); + p->timezone_timer = g_timeout_add_seconds (seconds, on_timezone_timer, self); + g_date_time_unref (now); +} + +/*** +**** HEADER TIMER +***/ + +/* + * This is to periodically rebuild the header's action's state. + * + * If the label shows seconds, update when we reach the next second. + * Otherwise, update when we reach the next minute. + */ + +static guint +calculate_milliseconds_until_next_minute (GDateTime * now) +{ + GDateTime * next; + GDateTime * start_of_next; + GTimeSpan interval_usec; + guint interval_msec; + + next = g_date_time_add_minutes (now, 1); + start_of_next = g_date_time_new_local (g_date_time_get_year (next), + g_date_time_get_month (next), + g_date_time_get_day_of_month (next), + g_date_time_get_hour (next), + g_date_time_get_minute (next), + 0); + + interval_usec = g_date_time_difference (start_of_next, now); + interval_msec = (interval_usec + 999) / 1000; + + g_date_time_unref (start_of_next); + g_date_time_unref (next); + + return interval_msec; +} + +static gint +calculate_milliseconds_until_next_second (GDateTime * now) +{ + gint interval_usec; + guint interval_msec; + + interval_usec = G_USEC_PER_SEC - g_date_time_get_microsecond (now); + interval_msec = (interval_usec + 999) / 1000; + + return interval_msec; +} + +static void start_header_timer (IndicatorDatetimeService * self); + +static gboolean +on_header_timer (gpointer gself) +{ + IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (gself); + + rebuild_now (self, SECTION_HEADER); + + /* Restarting the timer to recalculate the interval. This helps us to hit + our marks despite clock skew, suspend+resume, leap seconds, etc */ + start_header_timer (self); + return G_SOURCE_REMOVE; +} + +static char * get_header_label_format_string (IndicatorDatetimeService *); + +static void +start_header_timer (IndicatorDatetimeService * self) +{ + guint interval_msec; + gboolean header_shows_seconds = FALSE; + priv_t * p = self->priv; + GDateTime * now = indicator_datetime_service_get_localtime (self); + + indicator_clear_timer (&p->header_timer); + + if (g_settings_get_boolean (self->priv->settings, SETTINGS_SHOW_CLOCK_S)) + { + char * fmt = get_header_label_format_string (self); + header_shows_seconds = fmt && (strstr(fmt,"%s") || strstr(fmt,"%S") || + strstr(fmt,"%T") || strstr(fmt,"%X") || + strstr(fmt,"%c")); + g_free (fmt); + } + + if (header_shows_seconds) + interval_msec = calculate_milliseconds_until_next_second (now); + else + interval_msec = calculate_milliseconds_until_next_minute (now); + + interval_msec += 50; /* add a small margin to ensure the callback + fires /after/ next is reached */ + + p->header_timer = g_timeout_add_full (G_PRIORITY_HIGH, + interval_msec, + on_header_timer, + self, + NULL); + + g_date_time_unref (now); +} + +/** + * General purpose handler for rebuilding sections and restarting their timers + * when time jumps for whatever reason: + * + * - clock skew + * - laptop suspend + resume + * - geoclue detects that we've changed timezones + * - Unity is running inside a TARDIS + */ +static void +on_local_time_jumped (IndicatorDatetimeService * self) +{ + g_debug ("%s %s", G_STRLOC, G_STRFUNC); + + /* these calls accomplish two things: + 1. rebuild the necessary states / menuitems when time jumps + 2. restart the timers so their new wait interval is correct */ + + on_header_timer (self); + on_timezone_timer (self); +} + +static gboolean +skew_timer_func (gpointer gself) +{ + IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (gself); + GDateTime * now = indicator_datetime_service_get_localtime (self); + priv_t * p = self->priv; + + /* check for clock skew: has too much time passed since the last check? */ + if (p->skew_time != NULL) + { + const GTimeSpan diff = g_date_time_difference (now, p->skew_time); + + if (diff > SKEW_DIFF_THRESHOLD_USEC) + on_local_time_jumped (self); + } + + g_clear_pointer (&p->skew_time, g_date_time_unref); + p->skew_time = now; + return G_SOURCE_CONTINUE; +} + +/*** +**** +**** HEADER SECTION +**** +***/ + +typedef enum +{ + TIME_FORMAT_MODE_LOCALE_DEFAULT, + TIME_FORMAT_MODE_12_HOUR, + TIME_FORMAT_MODE_24_HOUR, + TIME_FORMAT_MODE_CUSTOM +} +TimeFormatMode; + +/* gets the user's time-format from GSettings */ +static TimeFormatMode +get_time_format_mode (IndicatorDatetimeService * self) +{ + char * str; + TimeFormatMode mode; + + str = g_settings_get_string (self->priv->settings, SETTINGS_TIME_FORMAT_S); + + if (!g_strcmp0 ("12-hour", str)) + mode = TIME_FORMAT_MODE_12_HOUR; + else if (!g_strcmp0 ("24-hour", str)) + mode = TIME_FORMAT_MODE_24_HOUR; + else if (!g_strcmp0 ("custom", str)) + mode = TIME_FORMAT_MODE_CUSTOM; + else + mode = TIME_FORMAT_MODE_LOCALE_DEFAULT; + + g_free (str); + return mode; +} + +static gchar * +get_header_label_format_string (IndicatorDatetimeService * self) +{ + char * fmt; + const TimeFormatMode mode = get_time_format_mode (self); + GSettings * s = self->priv->settings; + + if (mode == TIME_FORMAT_MODE_CUSTOM) + { + fmt = g_settings_get_string (s, SETTINGS_CUSTOM_TIME_FORMAT_S); + } + else + { + gboolean show_day = g_settings_get_boolean (s, SETTINGS_SHOW_DAY_S); + gboolean show_date = g_settings_get_boolean (s, SETTINGS_SHOW_DATE_S); + fmt = generate_format_string_full (show_day, show_date); + } + + return fmt; +} + +static GVariant * +create_header_state (IndicatorDatetimeService * self) +{ + GVariantBuilder b; + gchar * fmt; + gchar * str; + gboolean visible; + GDateTime * now; + priv_t * p = self->priv; + + visible = g_settings_get_boolean (p->settings, SETTINGS_SHOW_CLOCK_S); + + /* build the time string for the label & a11y */ + fmt = get_header_label_format_string (self); + now = indicator_datetime_service_get_localtime (self); + str = g_date_time_format (now, fmt); + if (str == NULL) + { + str = g_strdup (_("Unsupported date format")); + g_warning ("%s", str); + } + + g_variant_builder_init (&b, G_VARIANT_TYPE("a{sv}")); + g_variant_builder_add (&b, "{sv}", "accessible-desc", g_variant_new_string (str)); + g_variant_builder_add (&b, "{sv}", "label", g_variant_new_string (str)); + g_variant_builder_add (&b, "{sv}", "visible", g_variant_new_boolean (visible)); + + /* cleanup */ + g_date_time_unref (now); + g_free (str); + g_free (fmt); + return g_variant_builder_end (&b); +} + + +/*** +**** +**** CALENDAR SECTION +**** +***/ + +static GDateTime * +get_calendar_date (IndicatorDatetimeService * self) +{ + GDateTime * date; + priv_t * p = self->priv; + + if (p->calendar_date) + date = g_date_time_ref (p->calendar_date); + else + date = indicator_datetime_service_get_localtime (self); + + return date; +} + +static GSList * +get_all_appointments_this_month (IndicatorDatetimeService * self) +{ + GSList * appointments = NULL; + priv_t * p = self->priv; + + if (p->planner != NULL) + { + GDateTime * calendar_date; + GDateTime * begin; + GDateTime * end; + int y, m, d; + + calendar_date = get_calendar_date (self); + g_date_time_get_ymd (calendar_date, &y, &m, &d); + begin = g_date_time_new_local (y, m, 0, + 0, 0, 0); + end = g_date_time_new_local (y, m, g_date_get_days_in_month(m,y), + 23, 59, 0); + + appointments = indicator_datetime_planner_get_appointments (p->planner, + begin, + end); + + g_date_time_unref (end); + g_date_time_unref (begin); + g_date_time_unref (calendar_date); + } + + return appointments; +} + +static GVariant * +create_calendar_state (IndicatorDatetimeService * self) +{ + guint i; + const char * key; + gboolean days[32] = { 0 }; + GVariantBuilder dict_builder; + GVariantBuilder day_builder; + GDateTime * date; + GSList * l; + GSList * appts; + gboolean b; + priv_t * p = self->priv; + + g_variant_builder_init (&dict_builder, G_VARIANT_TYPE_DICTIONARY); + + key = "appointment-days"; + appts = get_all_appointments_this_month (self); + for (l=appts; l!=NULL; l=l->next) + { + const struct IndicatorDatetimeAppt * appt = l->data; + days[g_date_time_get_day_of_month (appt->begin)] = TRUE; + } + g_variant_builder_init (&day_builder, G_VARIANT_TYPE("ai")); + for (i=0; i<G_N_ELEMENTS(days); i++) + if (days[i]) + g_variant_builder_add (&day_builder, "i", i); + g_variant_builder_add (&dict_builder, "{sv}", key, + g_variant_builder_end (&day_builder)); + g_slist_free_full (appts, (GDestroyNotify)indicator_datetime_appt_free); + + key = "calendar-day"; + date = get_calendar_date (self); + g_variant_builder_add (&dict_builder, "{sv}", key, + g_variant_new_int64 (g_date_time_to_unix (date))); + g_date_time_unref (date); + + key = "show-week-numbers"; + b = g_settings_get_boolean (p->settings, SETTINGS_SHOW_WEEK_NUMBERS_S); + g_variant_builder_add (&dict_builder, "{sv}", key, g_variant_new_boolean (b)); + + return g_variant_builder_end (&dict_builder); +} + +static void +update_calendar_action_state (IndicatorDatetimeService * self) +{ + g_simple_action_set_state (self->priv->calendar_action, + create_calendar_state (self)); +} + +static GMenuModel * +create_calendar_section (IndicatorDatetimeService * self) +{ + char * label; + GMenuItem * menu_item; + GDateTime * now; + GMenu * menu = g_menu_new (); + + /* create the local date menuitem */ + now = indicator_datetime_service_get_localtime (self); + label = g_date_time_format (now, _("%A, %e %B %Y")); + menu_item = g_menu_item_new (label, NULL); + g_menu_item_set_action_and_target_value (menu_item, "indicator.activate-planner", + g_variant_new_int64(0)); + g_menu_append_item (menu, menu_item); + g_object_unref (menu_item); + g_free (label); + g_date_time_unref (now); + + /* create the calendar menuitem */ + if (g_settings_get_boolean (self->priv->settings, SETTINGS_SHOW_CALENDAR_S)) + { + label = g_strdup ("[calendar]"); + menu_item = g_menu_item_new ("[calendar]", NULL); + g_menu_item_set_action_and_target_value (menu_item, + "indicator.calendar", + g_variant_new_int64(0)); + g_menu_item_set_attribute (menu_item, "x-canonical-type", + "s", "com.canonical.indicator.calendar"); + g_menu_item_set_attribute (menu_item, "activation-action", + "s", "indicator.activate-planner"); + g_menu_append_item (menu, menu_item); + g_object_unref (menu_item); + g_free (label); + } + + return G_MENU_MODEL (menu); +} + +/*** +**** +**** APPOINTMENTS SECTION +**** +***/ + +/* gets the next MAX_APPTS appointments */ +static GSList * +get_upcoming_appointments (IndicatorDatetimeService * self) +{ + const int MAX_APPTS = 5; + GSList * l; + GSList * appts = NULL; + priv_t * p = self->priv; + + if (p->planner != NULL) + { + GDateTime * begin = get_calendar_date (self); + GDateTime * end = g_date_time_add_months (begin, 1); + + appts = indicator_datetime_planner_get_appointments (p->planner, + begin, + end); + + g_date_time_unref (end); + g_date_time_unref (begin); + } + + /* truncate at MAX_APPTS */ + if ((l = g_slist_nth (appts, MAX_APPTS-1))) + { + g_slist_free_full (l->next, (GDestroyNotify)indicator_datetime_appt_free); + l->next = NULL; + } + + return appts; +} + +static char * +get_appointment_time_format (struct IndicatorDatetimeAppt * appt, GDateTime * now) +{ + char * fmt; + gboolean full_day = g_date_time_difference (appt->end, appt->begin) == G_TIME_SPAN_DAY; + + if (full_day) + { + /* TRANSLATORS: This is a strftime string for the day for full day events + in the menu. It should most likely be either '%A' for a full text day + (Wednesday) or '%a' for a shortened one (Wed). You should only need to + change for '%a' in the case of langauges with very long day names. */ + fmt = g_strdup (_("%A")); + } + else + { + fmt = generate_format_string_at_time (now, appt->begin); + } + + return fmt; +} + +static GMenuModel * +create_appointments_section (IndicatorDatetimeService * self) +{ + priv_t * p = self->priv; + GMenu * menu = g_menu_new (); + + if (g_settings_get_boolean (p->settings, SETTINGS_SHOW_EVENTS_S)) + { + GSList * l; + GSList * appts; + GMenuItem * menu_item; + GDateTime * now = indicator_datetime_service_get_localtime (self); + + /* build appointment menuitems */ + appts = get_upcoming_appointments (self); + for (l=appts; l!=NULL; l=l->next) + { + struct IndicatorDatetimeAppt * appt = l->data; + char * fmt = get_appointment_time_format (appt, now); + const gint64 unix_time = g_date_time_to_unix (appt->begin); + + menu_item = g_menu_item_new (appt->summary, NULL); + g_menu_item_set_attribute (menu_item, "x-canonical-color", + "s", appt->color); + g_menu_item_set_attribute (menu_item, "x-canonical-time", + "x", unix_time); + g_menu_item_set_attribute (menu_item, "x-canonical-time-format", + "s", fmt); + g_menu_item_set_attribute (menu_item, "x-canonical-type", + "s", "com.canonical.indicator.appointment"); + g_menu_item_set_action_and_target_value (menu_item, + "indicator.activate-planner", + g_variant_new_int64 (unix_time)); + g_menu_append_item (menu, menu_item); + g_object_unref (menu_item); + g_free (fmt); + } + + /* build 'add event' menuitem */ + menu_item = g_menu_item_new (_("Add Event…"), NULL); + g_menu_item_set_action_and_target_value (menu_item, + "indicator.activate-planner", + g_variant_new_int64 (0)); + g_menu_append_item (menu, menu_item); + g_object_unref (menu_item); + + /* cleanup */ + g_date_time_unref (now); + g_slist_free_full (appts, (GDestroyNotify)indicator_datetime_appt_free); + } + + return G_MENU_MODEL (menu); +} + + +/*** +**** +**** LOCATIONS SECTION +**** +***/ + +static void +on_current_timezone_changed (IndicatorDatetimeService * self) +{ + on_local_time_jumped (self); +} + +/* When the 'auto-detect timezone' boolean setting changes, + start or stop watching geoclue and /etc/timezone */ +static void +set_detect_location_enabled (IndicatorDatetimeService * self, gboolean enabled) +{ + gboolean changed = FALSE; + priv_t * p = self->priv; + + /* geoclue */ + + if (!p->tz_geoclue && enabled) + { + p->tz_geoclue = indicator_datetime_timezone_geoclue_new (); + g_signal_connect_swapped (p->tz_geoclue, "notify::timezone", + G_CALLBACK(on_current_timezone_changed), + self); + changed = TRUE; + } + else if (p->tz_geoclue && !enabled) + { + g_signal_handlers_disconnect_by_func (p->tz_geoclue, + on_current_timezone_changed, + self); + g_clear_object (&p->tz_geoclue); + changed = TRUE; + } + + /* timezone file */ + + if (!p->tz_file && enabled) + { + p->tz_file = indicator_datetime_timezone_file_new (TIMEZONE_FILE); + g_signal_connect_swapped (p->tz_file, "notify::timezone", + G_CALLBACK(on_current_timezone_changed), + self); + changed = TRUE; + } + else if (p->tz_file && !enabled) + { + g_signal_handlers_disconnect_by_func (p->tz_file, + on_current_timezone_changed, + self); + g_clear_object (&p->tz_file); + changed = TRUE; + } + + if (changed) + on_current_timezone_changed (self); +} + +/* A temp struct used by create_locations_section() + for pruning duplicates and sorting. */ +struct TimeLocation +{ + GTimeSpan offset; + gchar * zone; + gchar * name; + gboolean visible; + GDateTime * local_time; +}; + +static void +time_location_free (struct TimeLocation * loc) +{ + g_date_time_unref (loc->local_time); + g_free (loc->name); + g_free (loc->zone); + g_free (loc); +} + +static struct TimeLocation* +time_location_new (const char * zone, + const char * name, + gboolean visible) +{ + struct TimeLocation * loc = g_new (struct TimeLocation, 1); + GTimeZone * tz = g_time_zone_new (zone); + loc->zone = g_strdup (zone); + loc->name = g_strdup (name); + loc->visible = visible; + loc->local_time = g_date_time_new_now (tz); + loc->offset = g_date_time_get_utc_offset (loc->local_time); + g_time_zone_unref (tz); + return loc; +} + +static int +time_location_compare (const struct TimeLocation * a, + const struct TimeLocation * b) +{ + int ret = 0; + + if (!ret && (a->offset != b->offset)) /* primary key */ + ret = (a->offset < b->offset) ? -1 : 1; + + if (!ret) + ret = g_strcmp0 (a->name, b->name); /* secondary key */ + + if (!ret) + ret = a->visible - b->visible; /* tertiary key */ + + return ret; +} + +static GSList* +locations_add (GSList * locations, + const char * zone, + const char * name, + gboolean visible) +{ + struct TimeLocation * loc = time_location_new (zone, name, visible); + + if (g_slist_find_custom (locations, loc, (GCompareFunc)time_location_compare)) + { + g_debug("%s Skipping duplicate zone '%s' name '%s'", G_STRLOC, zone, name); + time_location_free (loc); + } + else + { + g_debug ("%s Adding zone '%s', name '%s'", G_STRLOC, zone, name); + locations = g_slist_append (locations, loc); + } + + return locations; +} + +static GMenuModel * +create_locations_section (IndicatorDatetimeService * self) +{ + guint i; + GMenu * menu; + GSList * l; + GSList * locations = NULL; + gchar ** user_locations; + gboolean visible; + IndicatorDatetimeTimezone * detected_timezones[2]; + priv_t * p = self->priv; + GDateTime * now = indicator_datetime_service_get_localtime (self); + + set_detect_location_enabled (self, + g_settings_get_boolean (p->settings, SETTINGS_SHOW_DETECTED_S)); + + menu = g_menu_new (); + + /*** + **** Build a list of locations to add: use geo_timezone, + **** current_timezone, and SETTINGS_LOCATIONS_S, but omit duplicates. + ***/ + + /* maybe add the auto-detected timezones */ + detected_timezones[0] = p->tz_geoclue; + detected_timezones[1] = p->tz_file; + visible = g_settings_get_boolean (p->settings, SETTINGS_SHOW_DETECTED_S); + for (i=0; i<G_N_ELEMENTS(detected_timezones); i++) + { + if (detected_timezones[i] != NULL) + { + const char * tz = indicator_datetime_timezone_get_timezone (detected_timezones[i]); + if (tz && *tz) + { + gchar * name = get_current_zone_name (tz); + locations = locations_add (locations, tz, name, visible); + g_free (name); + } + } + } + + /* maybe add the user-specified locations */ + user_locations = g_settings_get_strv (p->settings, SETTINGS_LOCATIONS_S); + if (user_locations != NULL) + { + visible = g_settings_get_boolean (p->settings, SETTINGS_SHOW_LOCATIONS_S); + + for (i=0; user_locations[i] != NULL; i++) + { + gchar * zone; + gchar * name; + split_settings_location (user_locations[i], &zone, &name); + locations = locations_add (locations, zone, name, visible); + g_free (name); + g_free (zone); + } + + g_strfreev (user_locations); + user_locations = NULL; + } + + /* now build menuitems for all the locations */ + for (l=locations; l!=NULL; l=l->next) + { + struct TimeLocation * loc = l->data; + if (loc->visible) + { + char * label; + char * detailed_action; + char * fmt; + GMenuItem * menu_item; + + label = g_strdup (loc->name); + detailed_action = g_strdup_printf ("indicator.set-location::%s %s", + loc->zone, + loc->name); + fmt = generate_format_string_at_time (now, loc->local_time); + + menu_item = g_menu_item_new (label, detailed_action); + g_menu_item_set_attribute (menu_item, "x-canonical-type", + "s", "com.canonical.indicator.location"); + g_menu_item_set_attribute (menu_item, "x-canonical-timezone", + "s", loc->zone); + g_menu_item_set_attribute (menu_item, "x-canonical-time-format", + "s", fmt); + g_menu_append_item (menu, menu_item); + + g_object_unref (menu_item); + g_free (fmt); + g_free (detailed_action); + g_free (label); + } + } + + g_date_time_unref (now); + g_slist_free_full (locations, (GDestroyNotify)time_location_free); + return G_MENU_MODEL (menu); +} + +/*** +**** SET LOCATION +***/ + +struct setlocation_data +{ + IndicatorDatetimeService * service; + char * timezone_id; + char * name; +}; + +static void +setlocation_data_free (struct setlocation_data * data) +{ + g_free (data->timezone_id); + g_free (data->name); + g_free (data); +} + +static void +on_datetime1_set_timezone_response (GObject * object, + GAsyncResult * res, + gpointer gdata) +{ + GError * err; + GVariant * answers; + struct setlocation_data * data = gdata; + + err = NULL; + answers = g_dbus_proxy_call_finish (G_DBUS_PROXY(object), res, &err); + if (err != NULL) + { + if (!g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Could not set new timezone: %s", err->message); + + g_error_free (err); + } + else + { + char * timezone_name = g_strdup_printf ("%s %s", + data->timezone_id, + data->name); + + g_settings_set_string (data->service->priv->settings, + SETTINGS_TIMEZONE_NAME_S, + timezone_name); + + g_free (timezone_name); + g_variant_unref (answers); + } + + setlocation_data_free (data); +} + +static void +on_datetime1_proxy_ready (GObject * object G_GNUC_UNUSED, + GAsyncResult * res, + gpointer gdata) +{ + GError * err; + GDBusProxy * proxy; + struct setlocation_data * data = gdata; + + err = NULL; + proxy = g_dbus_proxy_new_for_bus_finish (res, &err); + if (err != NULL) + { + if (!g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Could not grab DBus proxy for timedated: %s", err->message); + + g_error_free (err); + setlocation_data_free (data); + } + else + { + g_dbus_proxy_call (proxy, + "SetTimezone", + g_variant_new ("(sb)", data->timezone_id, TRUE), + G_DBUS_CALL_FLAGS_NONE, + -1, + data->service->priv->cancellable, + on_datetime1_set_timezone_response, + data); + + g_object_unref (proxy); + } +} + +static void +indicator_datetime_service_set_location (IndicatorDatetimeService * self, + const char * timezone_id, + const char * name) +{ + priv_t * p = self->priv; + struct setlocation_data * data; + + g_return_if_fail (INDICATOR_IS_DATETIME_SERVICE (self)); + g_return_if_fail (name && *name); + g_return_if_fail (timezone_id && *timezone_id); + + data = g_new0 (struct setlocation_data, 1); + data->timezone_id = g_strdup (timezone_id); + data->name = g_strdup (name); + data->service = self; + + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.timedate1", + "/org/freedesktop/timedate1", + "org.freedesktop.timedate1", + p->cancellable, + on_datetime1_proxy_ready, + data); +} + +static void +on_set_location (GSimpleAction * a G_GNUC_UNUSED, + GVariant * param, + gpointer gself) +{ + char * zone; + char * name; + IndicatorDatetimeService * self; + + self = INDICATOR_DATETIME_SERVICE (gself); + split_settings_location (g_variant_get_string (param, NULL), &zone, &name); + indicator_datetime_service_set_location (self, zone, name); + + g_free (name); + g_free (zone); +} + +/*** +**** +***/ + +static GMenuModel * +create_settings_section (IndicatorDatetimeService * self G_GNUC_UNUSED) +{ + GMenu * menu = g_menu_new (); + g_menu_append (menu, _("Date and Time Settings\342\200\246"), "indicator.activate-settings"); + return G_MENU_MODEL (menu); +} + +static void +create_menu (IndicatorDatetimeService * self, int profile) +{ + GMenu * menu; + GMenu * submenu; + GMenuItem * header; + GMenuModel * sections[16]; + int i; + int n = 0; + + g_assert (0<=profile && profile<N_PROFILES); + g_assert (self->priv->menus[profile].menu == NULL); + + if (profile == PROFILE_DESKTOP) + { + sections[n++] = create_calendar_section (self); + sections[n++] = create_appointments_section (self); + sections[n++] = create_locations_section (self); + sections[n++] = create_settings_section (self); + } + else if (profile == PROFILE_GREETER) + { + sections[n++] = create_calendar_section (self); + } + + /* add sections to the submenu */ + + submenu = g_menu_new (); + + for (i=0; i<n; ++i) + { + g_menu_append_section (submenu, NULL, sections[i]); + g_object_unref (sections[i]); + } + + /* add submenu to the header */ + header = g_menu_item_new (NULL, "indicator._header"); + g_menu_item_set_attribute (header, "x-canonical-type", + "s", "com.canonical.indicator.root"); + g_menu_item_set_submenu (header, G_MENU_MODEL (submenu)); + g_object_unref (submenu); + + /* add header to the menu */ + menu = g_menu_new (); + g_menu_append_item (menu, header); + g_object_unref (header); + + self->priv->menus[profile].menu = menu; + self->priv->menus[profile].submenu = submenu; +} + +/*** +**** GActions +***/ + +/* Run a particular program based on an activation */ +static void +execute_command (const gchar * cmd) +{ + GError * err = NULL; + + g_debug ("Issuing command '%s'", cmd); + + if (!g_spawn_command_line_async (cmd, &err)) + { + g_warning ("Unable to start %s: %s", cmd, err->message); + g_error_free (err); + } +} + +static void +on_settings_activated (GSimpleAction * a G_GNUC_UNUSED, + GVariant * param G_GNUC_UNUSED, + gpointer gself G_GNUC_UNUSED) +{ +#ifdef HAVE_CCPANEL + execute_command ("gnome-control-center indicator-datetime"); +#else + execute_command ("gnome-control-center datetime"); +#endif +} + +static void +on_activate_planner (GSimpleAction * a G_GNUC_UNUSED, + GVariant * param, + gpointer gself) +{ + priv_t * p = INDICATOR_DATETIME_SERVICE(gself)->priv; + + if (p->planner != NULL) + { + const gint64 t = g_variant_get_int64 (param); + if (t) + { + GDateTime * date_time = g_date_time_new_from_unix_local (t); + indicator_datetime_planner_activate_time (p->planner, date_time); + g_date_time_unref (date_time); + } + else /* no time specified... */ + { + indicator_datetime_planner_activate (p->planner); + } + } +} + +static void +on_calendar_action_activated (GSimpleAction * action G_GNUC_UNUSED, + GVariant * state, + gpointer gself) +{ + gint64 unix_time; + IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (gself); + + if ((unix_time = g_variant_get_int64 (state))) + { + GDateTime * date = g_date_time_new_from_unix_local (unix_time); + indicator_datetime_service_set_calendar_date (self, date); + g_date_time_unref (date); + } + else /* unset */ + { + indicator_datetime_service_set_calendar_date (self, NULL); + } +} + + +static void +init_gactions (IndicatorDatetimeService * self) +{ + GSimpleAction * a; + priv_t * p = self->priv; + + GActionEntry entries[] = { + { "activate-settings", on_settings_activated }, + { "activate-planner", on_activate_planner, "x", NULL }, + { "set-location", on_set_location, "s" } + }; + + p->actions = g_simple_action_group_new (); + + g_action_map_add_action_entries (G_ACTION_MAP(p->actions), + entries, + G_N_ELEMENTS(entries), + self); + + /* add the header action */ + a = g_simple_action_new_stateful ("_header", NULL, create_header_state (self)); + g_simple_action_group_insert (p->actions, G_ACTION(a)); + p->header_action = a; + + /* add the calendar action */ + a = g_simple_action_new_stateful ("calendar", + G_VARIANT_TYPE_INT64, + create_calendar_state (self)); + g_simple_action_group_insert (p->actions, G_ACTION(a)); + g_signal_connect (a, "activate", + G_CALLBACK(on_calendar_action_activated), self); + p->calendar_action = a; + + rebuild_now (self, SECTION_HEADER); +} + +/*** +**** +***/ + +/** + * A small helper function for rebuild_now(). + * - removes the previous section + * - adds and unrefs the new section + */ +static void +rebuild_section (GMenu * parent, int pos, GMenuModel * new_section) +{ + g_menu_remove (parent, pos); + g_menu_insert_section (parent, pos, NULL, new_section); + g_object_unref (new_section); +} + +static void +rebuild_now (IndicatorDatetimeService * self, int sections) +{ + priv_t * p = self->priv; + struct ProfileMenuInfo * desktop = &p->menus[PROFILE_DESKTOP]; + struct ProfileMenuInfo * greeter = &p->menus[PROFILE_GREETER]; + + if (sections & SECTION_HEADER) + { + g_simple_action_set_state (p->header_action, create_header_state (self)); + } + + if (sections & SECTION_CALENDAR) + { + rebuild_section (desktop->submenu, 0, create_calendar_section (self)); + rebuild_section (greeter->submenu, 0, create_calendar_section (self)); + } + + if (sections & SECTION_APPOINTMENTS) + { + rebuild_section (desktop->submenu, 1, create_appointments_section (self)); + } + + if (sections & SECTION_LOCATIONS) + { + rebuild_section (desktop->submenu, 2, create_locations_section (self)); + } + + if (sections & SECTION_SETTINGS) + { + rebuild_section (desktop->submenu, 3, create_settings_section (self)); + } +} + +static int +rebuild_timeout_func (IndicatorDatetimeService * self) +{ + priv_t * p = self->priv; + rebuild_now (self, p->rebuild_flags); + p->rebuild_flags = 0; + p->rebuild_id = 0; + return G_SOURCE_REMOVE; +} + +static void +rebuild_soon (IndicatorDatetimeService * self, int section) +{ + priv_t * p = self->priv; + + p->rebuild_flags |= section; + + if (p->rebuild_id == 0) + { + /* Change events seem to come over the bus in small bursts. This msec + value is an arbitrary number that tries to be large enough to fold + multiple events into a single rebuild, but small enough that the + user won't notice any lag. */ + static const int REBUILD_INTERVAL_MSEC = 500; + + p->rebuild_id = g_timeout_add (REBUILD_INTERVAL_MSEC, + (GSourceFunc)rebuild_timeout_func, + self); + } +} + +/*** +**** org.freedesktop.login1.Manager +***/ + +static void +on_login1_manager_signal (GDBusProxy * proxy G_GNUC_UNUSED, + gchar * sender_name G_GNUC_UNUSED, + gchar * signal_name, + GVariant * parameters, + gpointer gself) +{ + if (!g_strcmp0 (signal_name, "PrepareForSleep")) + { + gboolean sleeping = FALSE; + g_variant_get (parameters, "(b)", &sleeping); + if (!sleeping) + on_local_time_jumped (INDICATOR_DATETIME_SERVICE (gself)); + } +} + +static void +on_login1_manager_proxy_ready (GObject * object G_GNUC_UNUSED, + GAsyncResult * res, + gpointer gself) +{ + GError * err; + GDBusProxy * proxy; + + err = NULL; + proxy = g_dbus_proxy_new_for_bus_finish (res, &err); + + if (err != NULL) + { + if (!g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Could not grab DBus proxy for logind: %s", err->message); + + g_error_free (err); + } + else + { + IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (gself); + self->priv->login1_manager = proxy; + g_signal_connect (proxy, "g-signal", + G_CALLBACK(on_login1_manager_signal), self); + } +} + +/*** +**** GDBus +***/ + +static void +on_bus_acquired (GDBusConnection * connection, + const gchar * name, + gpointer gself) +{ + int i; + guint id; + GError * err = NULL; + IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE(gself); + priv_t * p = self->priv; + + g_debug ("bus acquired: %s", name); + + p->conn = g_object_ref (G_OBJECT (connection)); + + /* export the actions */ + if ((id = g_dbus_connection_export_action_group (connection, + BUS_PATH, + G_ACTION_GROUP (p->actions), + &err))) + { + p->actions_export_id = id; + } + else + { + g_warning ("cannot export action group: %s", err->message); + g_clear_error (&err); + } + + /* export the menus */ + for (i=0; i<N_PROFILES; ++i) + { + char * path = g_strdup_printf ("%s/%s", BUS_PATH, menu_names[i]); + struct ProfileMenuInfo * menu = &p->menus[i]; + + if (menu->menu == NULL) + create_menu (self, i); + + if ((id = g_dbus_connection_export_menu_model (connection, + path, + G_MENU_MODEL (menu->menu), + &err))) + { + menu->export_id = id; + } + else + { + g_warning ("cannot export %s menu: %s", menu_names[i], err->message); + g_clear_error (&err); + } + + g_free (path); + } +} + +static void +unexport (IndicatorDatetimeService * self) +{ + int i; + priv_t * p = self->priv; + + /* unexport the menus */ + for (i=0; i<N_PROFILES; ++i) + { + guint * id = &self->priv->menus[i].export_id; + + if (*id) + { + g_dbus_connection_unexport_menu_model (p->conn, *id); + *id = 0; + } + } + + /* unexport the actions */ + if (p->actions_export_id) + { + g_dbus_connection_unexport_action_group (p->conn, p->actions_export_id); + p->actions_export_id = 0; + } +} + +static void +on_name_lost (GDBusConnection * connection G_GNUC_UNUSED, + const gchar * name, + gpointer gself) +{ + IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (gself); + + g_debug ("%s %s name lost %s", G_STRLOC, G_STRFUNC, name); + + unexport (self); + + g_signal_emit (self, signals[SIGNAL_NAME_LOST], 0, NULL); +} + + +/*** +**** GObject virtual functions +***/ + +static void +my_dispose (GObject * o) +{ + int i; + IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE(o); + priv_t * p = self->priv; + + if (p->own_id) + { + g_bus_unown_name (p->own_id); + p->own_id = 0; + } + + unexport (self); + + if (p->cancellable != NULL) + { + g_cancellable_cancel (p->cancellable); + g_clear_object (&p->cancellable); + } + + set_detect_location_enabled (self, FALSE); + + if (p->planner != NULL) + { + g_signal_handlers_disconnect_by_data (p->planner, self); + g_clear_object (&p->planner); + } + + if (p->login1_manager != NULL) + { + g_signal_handlers_disconnect_by_data (p->login1_manager, self); + g_clear_object (&p->login1_manager); + } + + indicator_clear_timer (&p->skew_timer); + indicator_clear_timer (&p->rebuild_id); + indicator_clear_timer (&p->timezone_timer); + indicator_clear_timer (&p->header_timer); + + if (p->settings != NULL) + { + g_signal_handlers_disconnect_by_data (p->settings, self); + g_clear_object (&p->settings); + } + + g_clear_object (&p->actions); + + for (i=0; i<N_PROFILES; ++i) + g_clear_object (&p->menus[i].menu); + + g_clear_object (&p->planner); + g_clear_object (&p->calendar_action); + g_clear_object (&p->header_action); + g_clear_object (&p->conn); + + G_OBJECT_CLASS (indicator_datetime_service_parent_class)->dispose (o); +} + +static void +my_finalize (GObject * o) +{ + IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE(o); + priv_t * p = self->priv; + + g_clear_pointer (&p->skew_time, g_date_time_unref); + g_clear_pointer (&p->calendar_date, g_date_time_unref); + + G_OBJECT_CLASS (indicator_datetime_service_parent_class)->finalize (o); +} + +/*** +**** Instantiation +***/ + +static void +indicator_datetime_service_init (IndicatorDatetimeService * self) +{ + guint i, n; + priv_t * p; + GString * gstr = g_string_new (NULL); + + /* these are the settings that affect the + contents of the respective sections */ + const char * const header_settings[] = { + SETTINGS_SHOW_CLOCK_S, + SETTINGS_TIME_FORMAT_S, + SETTINGS_SHOW_SECONDS_S, + SETTINGS_SHOW_DAY_S, + SETTINGS_SHOW_DATE_S, + SETTINGS_CUSTOM_TIME_FORMAT_S + }; + const char * const calendar_settings[] = { + SETTINGS_SHOW_CALENDAR_S, + SETTINGS_SHOW_WEEK_NUMBERS_S + }; + const char * const appointment_settings[] = { + SETTINGS_SHOW_EVENTS_S, + SETTINGS_TIME_FORMAT_S, + SETTINGS_SHOW_SECONDS_S + }; + const char * const location_settings[] = { + SETTINGS_TIME_FORMAT_S, + SETTINGS_SHOW_SECONDS_S, + SETTINGS_CUSTOM_TIME_FORMAT_S, + SETTINGS_SHOW_LOCATIONS_S, + SETTINGS_LOCATIONS_S, + SETTINGS_SHOW_DETECTED_S, + SETTINGS_TIMEZONE_NAME_S + }; + const char * const time_format_string_settings[] = { + SETTINGS_TIME_FORMAT_S, + SETTINGS_SHOW_SECONDS_S, + SETTINGS_CUSTOM_TIME_FORMAT_S + }; + + + /* init the priv pointer */ + + p = G_TYPE_INSTANCE_GET_PRIVATE (self, + INDICATOR_TYPE_DATETIME_SERVICE, + IndicatorDatetimeServicePrivate); + self->priv = p; + + p->cancellable = g_cancellable_new (); + + /*** + **** Create the planner and listen for changes + ***/ + + p->planner = indicator_datetime_planner_eds_new (); + + g_signal_connect_swapped (p->planner, "appointments-changed", + G_CALLBACK(rebuild_calendar_section_soon), self); + + + /*** + **** Create the settings object and listen for changes + ***/ + + p->settings = g_settings_new (SETTINGS_INTERFACE); + for (i=0, n=G_N_ELEMENTS(header_settings); i<n; i++) + { + g_string_printf (gstr, "changed::%s", header_settings[i]); + g_signal_connect_swapped (p->settings, gstr->str, + G_CALLBACK(rebuild_header_soon), self); + } + + for (i=0, n=G_N_ELEMENTS(calendar_settings); i<n; i++) + { + g_string_printf (gstr, "changed::%s", calendar_settings[i]); + g_signal_connect_swapped (p->settings, gstr->str, + G_CALLBACK(rebuild_calendar_section_soon), self); + } + + for (i=0, n=G_N_ELEMENTS(appointment_settings); i<n; i++) + { + g_string_printf (gstr, "changed::%s", appointment_settings[i]); + g_signal_connect_swapped (p->settings, gstr->str, + G_CALLBACK(rebuild_appointments_section_soon), self); + } + + for (i=0, n=G_N_ELEMENTS(location_settings); i<n; i++) + { + g_string_printf (gstr, "changed::%s", location_settings[i]); + g_signal_connect_swapped (p->settings, gstr->str, + G_CALLBACK(rebuild_locations_section_soon), self); + } + + /* The keys in time_format_string_settings affect the time format strings we build. + When these change, we need to rebuild everything that has a time format string. */ + for (i=0, n=G_N_ELEMENTS(time_format_string_settings); i<n; i++) + { + g_string_printf (gstr, "changed::%s", time_format_string_settings[i]); + g_signal_connect_swapped (p->settings, gstr->str, + G_CALLBACK(on_local_time_jumped), self); + } + + init_gactions (self); + + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + p->cancellable, + on_login1_manager_proxy_ready, + self); + + p->skew_timer = g_timeout_add_seconds (SKEW_CHECK_INTERVAL_SEC, + skew_timer_func, + self); + + p->own_id = g_bus_own_name (G_BUS_TYPE_SESSION, + BUS_NAME, + G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT, + on_bus_acquired, + NULL, + on_name_lost, + self, + NULL); + + on_local_time_jumped (self); + + g_string_free (gstr, TRUE); +} + +static void +indicator_datetime_service_class_init (IndicatorDatetimeServiceClass * klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = my_dispose; + object_class->finalize = my_finalize; + + g_type_class_add_private (klass, sizeof (IndicatorDatetimeServicePrivate)); + + signals[SIGNAL_NAME_LOST] = g_signal_new ( + INDICATOR_DATETIME_SERVICE_SIGNAL_NAME_LOST, + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (IndicatorDatetimeServiceClass, name_lost), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +/*** +**** Public API +***/ + +IndicatorDatetimeService * +indicator_datetime_service_new (void) +{ + GObject * o = g_object_new (INDICATOR_TYPE_DATETIME_SERVICE, NULL); + + return INDICATOR_DATETIME_SERVICE (o); +} + +/* This currently just returns the system time, + As we add test coverage, we'll need this to bypass the system time. */ +GDateTime * +indicator_datetime_service_get_localtime (IndicatorDatetimeService * self G_GNUC_UNUSED) +{ + return g_date_time_new_now_local (); +} + +void +indicator_datetime_service_set_calendar_date (IndicatorDatetimeService * self, + GDateTime * date) +{ + gboolean dirty; + priv_t * p = self->priv; + + dirty = !date || !p->calendar_date || g_date_time_compare (date, p->calendar_date); + + /* update calendar_date */ + g_clear_pointer (&p->calendar_date, g_date_time_unref); + if (date != NULL) + p->calendar_date = g_date_time_ref (date); + + /* sync the menuitems and action states */ + if (dirty) + { + update_calendar_action_state (self); + rebuild_appointments_section_soon (self); + } +} |