diff options
Diffstat (limited to 'src')
40 files changed, 3697 insertions, 3257 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 15f29ea..b8c62fd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,8 @@ set (SERVICE_LIB "indicatordatetimeservice") set (SERVICE_EXEC "indicator-datetime-service") +SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g ${CXX_WARNING_ARGS} ${GCOV_FLAGS}") + add_definitions (-DTIMEZONE_FILE="/etc/timezone" -DG_LOG_DOMAIN="Indicator-Datetime") @@ -10,36 +12,28 @@ if (BUILD_PANEL) endif () add_library (${SERVICE_LIB} STATIC - clock.c - clock.h - clock-live.c - clock-live.h - planner.c - planner.h - planner-eds.c - planner-eds.h - service.c - service.h - timezone.c - timezone.h - timezone-file.c - timezone-file.h - timezone-geoclue.c - timezone-geoclue.h - utils.c - utils.h - dbus-shared.h - settings-shared.h) -include_directories (${SERVICE_DEPS_INCLUDE_DIRS}) + actions.cpp + actions-live.cpp + clock.cpp + clock-live.cpp + date-time.cpp + exporter.cpp + formatter.cpp + formatter-desktop.cpp + locations.cpp + locations-settings.cpp + menu.cpp + planner-eds.cpp + settings-live.cpp + state-live.cpp + timezone-file.cpp + timezone-geoclue.cpp + timezones-live.cpp + utils.cpp) +include_directories (${CMAKE_SOURCE_DIR}) link_directories (${SERVICE_DEPS_LIBRARY_DIRS}) -add_executable (${SERVICE_EXEC} main.c) +add_executable (${SERVICE_EXEC} main.cpp) target_link_libraries (${SERVICE_EXEC} ${SERVICE_LIB} ${SERVICE_DEPS_LIBRARIES} ${GCOV_LIBS}) install (TARGETS ${SERVICE_EXEC} RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_PKGLIBEXECDIR}) -# common properties -set_property (TARGET ${SERVICE_LIB} ${SERVICE_EXEC} - APPEND_STRING PROPERTY COMPILE_FLAGS - " -g ${CC_WARNING_ARGS} ${GCOV_FLAGS}") - - diff --git a/src/actions-live.cpp b/src/actions-live.cpp new file mode 100644 index 0000000..afd83f4 --- /dev/null +++ b/src/actions-live.cpp @@ -0,0 +1,212 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * 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/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <datetime/actions-live.h> + +#include <url-dispatcher.h> + +#include <glib.h> + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +void LiveActions::execute_command(const std::string& cmdstr) +{ + const auto cmd = cmdstr.c_str(); + g_debug("Issuing command '%s'", cmd); + + GError* error = nullptr; + if (!g_spawn_command_line_async(cmd, &error)) + { + g_warning("Unable to start \"%s\": %s", cmd, error->message); + g_error_free(error); + } +} + +void LiveActions::dispatch_url(const std::string& url) +{ + url_dispatch_send(url.c_str(), nullptr, nullptr); +} + +/*** +**** +***/ + +void LiveActions::open_desktop_settings() +{ + auto path = g_find_program_in_path("unity-control-center"); + + if ((path != nullptr) && (g_strcmp0 (g_getenv ("XDG_CURRENT_DESKTOP"), "Unity") == 0)) + { + execute_command("unity-control-center datetime"); + } + else + { +#ifdef HAVE_CCPANEL + execute_command("gnome-control-center indicator-datetime"); +#else + execute_command("gnome-control-center datetime"); +#endif + } + + g_free (path); +} + +void LiveActions::open_planner() +{ + execute_command("evolution -c calendar"); +} + +void LiveActions::open_phone_settings() +{ + dispatch_url("settings:///system/time-date"); +} + +void LiveActions::open_phone_clock_app() +{ + dispatch_url("appid://com.ubuntu.clock/clock/current-user-version"); +} + +void LiveActions::open_planner_at(const DateTime& dt) +{ + auto cmd = dt.format("evolution \"calendar:///?startdate=%Y%m%d\""); + execute_command(cmd.c_str()); +} + +void LiveActions::open_appointment(const std::string& uid) +{ + for(const auto& appt : state()->planner->upcoming.get()) + { + if(appt.uid != uid) + continue; + + if (!appt.url.empty()) + dispatch_url(appt.url); + break; + } +} + +/*** +**** +***/ + +namespace +{ + +struct setlocation_data +{ + std::string tzid; + std::string name; + std::shared_ptr<Settings> settings; +}; + +static void +on_datetime1_set_timezone_response(GObject * object, + GAsyncResult * res, + gpointer gdata) +{ + GError* err = nullptr; + auto response = g_dbus_proxy_call_finish(G_DBUS_PROXY(object), res, &err); + auto data = static_cast<struct setlocation_data*>(gdata); + + if (err != nullptr) + { + 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 + { + data->settings->timezone_name.set(data->tzid + " " + data->name); + g_variant_unref(response); + } + + delete data; +} + +static void +on_datetime1_proxy_ready (GObject * object G_GNUC_UNUSED, + GAsyncResult * res, + gpointer gdata) +{ + auto data = static_cast<struct setlocation_data*>(gdata); + + GError * err = nullptr; + auto 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); + + delete data; + } + else + { + g_dbus_proxy_call(proxy, + "SetTimezone", + g_variant_new ("(sb)", data->tzid.c_str(), TRUE), + G_DBUS_CALL_FLAGS_NONE, + -1, + nullptr, + on_datetime1_set_timezone_response, + data); + + g_object_unref (proxy); + } +} + +} // unnamed namespace + + +void LiveActions::set_location(const std::string& tzid, const std::string& name) +{ + g_return_if_fail(!tzid.empty()); + g_return_if_fail(!name.empty()); + + auto data = new struct setlocation_data; + data->tzid = tzid; + data->name = name; + data->settings = state()->settings; + + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + nullptr, + "org.freedesktop.timedate1", + "/org/freedesktop/timedate1", + "org.freedesktop.timedate1", + nullptr, + on_datetime1_proxy_ready, + data); +} + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/actions.cpp b/src/actions.cpp new file mode 100644 index 0000000..a6a7c0b --- /dev/null +++ b/src/actions.cpp @@ -0,0 +1,241 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * 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/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <datetime/actions.h> +#include <datetime/utils.h> // split_settings_location() + +#include <glib.h> +#include <gio/gio.h> + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +namespace +{ + +void on_desktop_settings_activated(GSimpleAction * /*action*/, + GVariant * /*param*/, + gpointer gself) +{ + static_cast<Actions*>(gself)->open_desktop_settings(); +} + +void on_phone_settings_activated(GSimpleAction * /*action*/, + GVariant * /*param*/, + gpointer gself) +{ + static_cast<Actions*>(gself)->open_phone_settings(); +} + +void on_phone_clock_activated(GSimpleAction * /*action*/, + GVariant * /*param*/, + gpointer gself) +{ + static_cast<Actions*>(gself)->open_phone_clock_app(); +} + +void on_activate_appointment(GSimpleAction * /*action*/, + GVariant * param, + gpointer gself) +{ + const auto uid = g_variant_get_string(param, nullptr); + auto self = static_cast<Actions*>(gself); + + g_return_if_fail(uid && *uid); + + // find url of the upcoming appointment with this uid + for (auto& appt : self->state()->planner->upcoming.get()) + { + if (appt.uid == uid) + { + const auto url = appt.url; + g_debug("%s: uid[%s] -> url[%s]", G_STRFUNC, uid, url.c_str()); + self->open_appointment(url); + break; + } + } +} + +void on_activate_planner(GSimpleAction * /*action*/, + GVariant * param, + gpointer gself) +{ + const auto at = g_variant_get_int64(param); + auto self = static_cast<Actions*>(gself); + + if (at) + { + auto gdt = g_date_time_new_from_unix_local(at); + self->open_planner_at(DateTime(gdt)); + g_date_time_unref(gdt); + } + else // no time specified... + { + self->open_planner(); + } +} + +void on_set_location(GSimpleAction * /*action*/, + GVariant * param, + gpointer gself) +{ + char * zone; + char * name; + split_settings_location(g_variant_get_string(param, nullptr), &zone, &name); + static_cast<Actions*>(gself)->set_location(zone, name); + g_free(name); + g_free(zone); +} + +static void on_calendar_activated(GSimpleAction * /*action*/, + GVariant * state, + gpointer gself) +{ + const time_t t = g_variant_get_int64(state); + + g_return_if_fail(t != 0); + + static_cast<Actions*>(gself)->set_calendar_date(DateTime(t)); +} + +GVariant* create_default_header_state() +{ + GVariantBuilder b; + g_variant_builder_init(&b, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&b, "{sv}", "accessible-desc", g_variant_new_string("accessible-desc")); + g_variant_builder_add(&b, "{sv}", "label", g_variant_new_string("label")); + g_variant_builder_add(&b, "{sv}", "title", g_variant_new_string("title")); + g_variant_builder_add(&b, "{sv}", "visible", g_variant_new_boolean(true)); + return g_variant_builder_end(&b); +} + +GVariant* create_calendar_state(const std::shared_ptr<State>& state) +{ + gboolean days[32] = { 0 }; + for(const auto& appt : state->planner->thisMonth.get()) + days[appt.begin.day_of_month()] = true; + + GVariantBuilder day_builder; + g_variant_builder_init(&day_builder, G_VARIANT_TYPE("ai")); + for (guint i=0; i<G_N_ELEMENTS(days); i++) + if (days[i]) + g_variant_builder_add(&day_builder, "i", i); + + GVariantBuilder dict_builder; + g_variant_builder_init(&dict_builder, G_VARIANT_TYPE_DICTIONARY); + + auto key = "appointment-days"; + auto v = g_variant_builder_end(&day_builder); + g_variant_builder_add(&dict_builder, "{sv}", key, v); + + key = "calendar-day"; + v = g_variant_new_int64(state->planner->time.get().to_unix()); + g_variant_builder_add(&dict_builder, "{sv}", key, v); + + key = "show-week-numbers"; + v = g_variant_new_boolean(state->settings->show_week_numbers.get()); + g_variant_builder_add(&dict_builder, "{sv}", key, v); + + return g_variant_builder_end(&dict_builder); +} +} // unnamed namespace + +/*** +**** +***/ + +Actions::Actions(const std::shared_ptr<State>& state): + m_state(state), + m_actions(g_simple_action_group_new()) +{ + GActionEntry entries[] = { + { "activate-desktop-settings", on_desktop_settings_activated }, + { "activate-phone-settings", on_phone_settings_activated }, + { "activate-phone-clock-app", on_phone_clock_activated }, + { "activate-appointment", on_activate_appointment, "s", nullptr }, + { "activate-planner", on_activate_planner, "x", nullptr }, + { "set-location", on_set_location, "s" } + }; + + g_action_map_add_action_entries(G_ACTION_MAP(m_actions), + entries, + G_N_ELEMENTS(entries), + this); + + // add the header actions + auto gam = G_ACTION_MAP(m_actions); + auto v = create_default_header_state(); + auto a = g_simple_action_new_stateful("desktop-header", nullptr, v); + g_action_map_add_action(gam, G_ACTION(a)); + a = g_simple_action_new_stateful("desktop_greeter-header", nullptr, v); + g_action_map_add_action(gam, G_ACTION(a)); + a = g_simple_action_new_stateful("phone-header", nullptr, v); + g_action_map_add_action(gam, G_ACTION(a)); + a = g_simple_action_new_stateful("phone_greeter-header", nullptr, v); + g_action_map_add_action(gam, G_ACTION(a)); + + // add the calendar action + v = create_calendar_state(state); + a = g_simple_action_new_stateful("calendar", G_VARIANT_TYPE_INT64, v); + g_action_map_add_action(gam, G_ACTION(a)); + g_signal_connect(a, "activate", G_CALLBACK(on_calendar_activated), this); + + /// + /// Keep our GActionGroup's action's states in sync with m_state + /// + + m_state->planner->time.changed().connect([this](const DateTime&){ + update_calendar_state(); + }); + m_state->planner->thisMonth.changed().connect([this](const std::vector<Appointment>&){ + update_calendar_state(); + }); + m_state->settings->show_week_numbers.changed().connect([this](bool){ + update_calendar_state(); + }); + + // FIXME: rebuild the calendar state when show-week-number changes +} + +Actions::~Actions() +{ + g_clear_object(&m_actions); +} + +void Actions::update_calendar_state() +{ + g_action_group_change_action_state(action_group(), + "calendar", + create_calendar_state(m_state)); +} + +void Actions::set_calendar_date(const DateTime& date) +{ + m_state->planner->time.set(date); +} + + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/clock-live.c b/src/clock-live.c deleted file mode 100644 index 2a375fd..0000000 --- a/src/clock-live.c +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * Authors: - * Charles Kerr <charles.kerr@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 <glib.h> -#include <gio/gio.h> - -#include "clock-live.h" -#include "settings-shared.h" -#include "timezone-file.h" -#include "timezone-geoclue.h" - -/*** -**** private struct -***/ - -struct _IndicatorDatetimeClockLivePriv -{ - GSettings * settings; - - GSList * timezones; /* IndicatorDatetimeTimezone */ - gchar ** timezones_strv; - GTimeZone * localtime_zone; -}; - -typedef IndicatorDatetimeClockLivePriv priv_t; - -/*** -**** GObject boilerplate -***/ - -static void indicator_datetime_clock_interface_init ( - IndicatorDatetimeClockInterface * iface); - -G_DEFINE_TYPE_WITH_CODE ( - IndicatorDatetimeClockLive, - indicator_datetime_clock_live, - G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (INDICATOR_TYPE_DATETIME_CLOCK, - indicator_datetime_clock_interface_init)) - -/*** -**** Timezones -***/ - -static void -on_current_timezone_changed (IndicatorDatetimeClockLive * self) -{ - priv_t * p = self->priv; - - /* Invalidate the timezone information. - These fields will be lazily regenerated by rebuild_timezones() */ - g_clear_pointer (&p->timezones_strv, g_strfreev); - g_clear_pointer (&p->localtime_zone, g_time_zone_unref); - - indicator_datetime_clock_emit_changed (INDICATOR_DATETIME_CLOCK (self)); -} - -static void -set_detect_location_enabled (IndicatorDatetimeClockLive * self, gboolean enabled) -{ - GSList * l; - priv_t * p = self->priv; - gboolean changed = FALSE; - - /* clear out the old timezone objects */ - if (p->timezones != NULL) - { - for (l=p->timezones; l!=NULL; l=l->next) - { - g_signal_handlers_disconnect_by_func (l->data, on_current_timezone_changed, self); - g_object_unref (l->data); - } - - g_slist_free (p->timezones); - p->timezones = NULL; - changed = TRUE; - } - - /* maybe add new timezone objects */ - if (enabled) - { - p->timezones = g_slist_append (p->timezones, indicator_datetime_timezone_geoclue_new ()); - p->timezones = g_slist_append (p->timezones, indicator_datetime_timezone_file_new (TIMEZONE_FILE)); - - for (l=p->timezones; l!=NULL; l=l->next) - { - g_signal_connect_swapped (l->data, "notify::timezone", - G_CALLBACK(on_current_timezone_changed), self); - } - - changed = TRUE; - } - - if (changed) - on_current_timezone_changed (self); -} - -/* When the 'auto-detect timezone' boolean setting changes, - start or stop watching geoclue and /etc/timezone */ -static void -on_detect_location_changed (IndicatorDatetimeClockLive * self) -{ - const gboolean enabled = g_settings_get_boolean (self->priv->settings, SETTINGS_SHOW_DETECTED_S); - set_detect_location_enabled (self, enabled); -} - -/*** -**** IndicatorDatetimeClock virtual functions -***/ - -static void -rebuild_timezones (IndicatorDatetimeClockLive * self) -{ - priv_t * p; - GHashTable * hash; - GSList * l; - int i; - GHashTableIter iter; - gpointer key; - - p = self->priv; - - /* Build a hashtable of timezone strings. - This will weed out duplicates. */ - hash = g_hash_table_new (g_str_hash, g_str_equal); - for (l=p->timezones; l!=NULL; l=l->next) - { - const gchar * tz = indicator_datetime_timezone_get_timezone (l->data); - if (tz && *tz) - g_hash_table_add (hash, (gpointer) tz); - } - - /* rebuild p->timezone_strv */ - g_strfreev (p->timezones_strv); - p->timezones_strv = g_new0 (gchar*, g_hash_table_size(hash) + 1); - i = 0; - g_hash_table_iter_init (&iter, hash); - while (g_hash_table_iter_next (&iter, &key, NULL)) - p->timezones_strv[i++] = g_strdup (key); - - /* rebuild localtime_zone */ - g_clear_pointer (&p->localtime_zone, g_time_zone_unref); - p->localtime_zone = g_time_zone_new (p->timezones_strv ? p->timezones_strv[0] : NULL); - - /* cleanup */ - g_hash_table_unref (hash); -} - -static const gchar ** -my_get_timezones (IndicatorDatetimeClock * clock) -{ - IndicatorDatetimeClockLive * self = INDICATOR_DATETIME_CLOCK_LIVE (clock); - priv_t * p = self->priv; - - if (G_UNLIKELY (p->timezones_strv == NULL)) - rebuild_timezones (self); - - return (const gchar **) p->timezones_strv; -} - -static GDateTime * -my_get_localtime (IndicatorDatetimeClock * clock) -{ - IndicatorDatetimeClockLive * self = INDICATOR_DATETIME_CLOCK_LIVE (clock); - priv_t * p = self->priv; - - if (G_UNLIKELY (p->localtime_zone == NULL)) - rebuild_timezones (self); - - return g_date_time_new_now (p->localtime_zone); -} - -/*** -**** GObject virtual functions -***/ - -static void -my_dispose (GObject * o) -{ - IndicatorDatetimeClockLive * self; - priv_t * p; - - self = INDICATOR_DATETIME_CLOCK_LIVE(o); - p = self->priv; - - if (p->settings != NULL) - { - g_signal_handlers_disconnect_by_data (p->settings, self); - g_clear_object (&p->settings); - } - - set_detect_location_enabled (self, FALSE); - - G_OBJECT_CLASS (indicator_datetime_clock_live_parent_class)->dispose (o); -} - -static void -my_finalize (GObject * o) -{ - IndicatorDatetimeClockLive * self; - priv_t * p; - - self = INDICATOR_DATETIME_CLOCK_LIVE(o); - p = self->priv; - - g_clear_pointer (&p->localtime_zone, g_time_zone_unref); - g_strfreev (p->timezones_strv); - - G_OBJECT_CLASS (indicator_datetime_clock_live_parent_class)->dispose (o); -} - -/*** -**** Instantiation -***/ - -static void -indicator_datetime_clock_live_class_init (IndicatorDatetimeClockLiveClass * 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 (IndicatorDatetimeClockLivePriv)); -} - -static void -indicator_datetime_clock_interface_init (IndicatorDatetimeClockInterface * iface) -{ - iface->get_localtime = my_get_localtime; - iface->get_timezones = my_get_timezones; -} - -static void -indicator_datetime_clock_live_init (IndicatorDatetimeClockLive * self) -{ - IndicatorDatetimeClockLivePriv * p; - - p = G_TYPE_INSTANCE_GET_PRIVATE (self, - INDICATOR_TYPE_DATETIME_CLOCK_LIVE, - IndicatorDatetimeClockLivePriv); - self->priv = p; - - p->settings = g_settings_new (SETTINGS_INTERFACE); - g_signal_connect_swapped (p->settings, "changed::" SETTINGS_SHOW_DETECTED_S, - G_CALLBACK(on_detect_location_changed), self); - - on_detect_location_changed (self); -} - -/*** -**** Public API -***/ - -IndicatorDatetimeClock * -indicator_datetime_clock_live_new (void) -{ - gpointer o = g_object_new (INDICATOR_TYPE_DATETIME_CLOCK_LIVE, NULL); - - return INDICATOR_DATETIME_CLOCK (o); -} diff --git a/src/clock-live.cpp b/src/clock-live.cpp new file mode 100644 index 0000000..69ebda7 --- /dev/null +++ b/src/clock-live.cpp @@ -0,0 +1,163 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * 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/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <datetime/clock.h> +#include <datetime/timezones.h> + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +namespace +{ + +void clearTimer(guint& tag) +{ + if (tag) + { + g_source_remove(tag); + tag = 0; + } +} + +guint calculate_milliseconds_until_next_minute(const DateTime& now) +{ + auto next = g_date_time_add_minutes(now.get(), 1); + auto start_of_next = g_date_time_add_seconds (next, -g_date_time_get_seconds(next)); + const auto interval_usec = g_date_time_difference(start_of_next, now.get()); + const guint interval_msec = (interval_usec + 999) / 1000; + g_date_time_unref(start_of_next); + g_date_time_unref(next); + g_assert (interval_msec <= 60000); + return interval_msec; +} + +} // unnamed namespace + + +class LiveClock::Impl +{ +public: + + Impl(LiveClock& owner, const std::shared_ptr<Timezones>& tzd): + m_owner(owner), + m_timezones(tzd) + { + if (m_timezones) + { + m_timezones->timezone.changed().connect([this](const std::string& z) {setTimezone(z);}); + setTimezone(m_timezones->timezone.get()); + } + + restart_minute_timer(); + } + + ~Impl() + { + clearTimer(m_timer); + + g_clear_pointer(&m_timezone, g_time_zone_unref); + } + + DateTime localtime() const + { + g_assert(m_timezone != nullptr); + + auto gdt = g_date_time_new_now(m_timezone); + DateTime ret(gdt); + g_date_time_unref(gdt); + return ret; + } + +private: + + void setTimezone(const std::string& str) + { + g_clear_pointer(&m_timezone, g_time_zone_unref); + m_timezone = g_time_zone_new(str.c_str()); + m_owner.minuteChanged(); + } + + /*** + **** + ***/ + + void restart_minute_timer() + { + clearTimer(m_timer); + + // maybe emit change signals + const auto now = localtime(); + if (!DateTime::is_same_minute(m_prev_datetime, now)) + m_owner.minuteChanged(); + if (!DateTime::is_same_day(m_prev_datetime, now)) + m_owner.dateChanged(); + + // queue up a timer to fire at the next minute + m_prev_datetime = now; + auto interval_msec = calculate_milliseconds_until_next_minute(now); + interval_msec += 50; // add a small margin to ensure the callback + // fires /after/ next is reached + m_timer = g_timeout_add_full(G_PRIORITY_HIGH, + interval_msec, + on_minute_timer_reached, + this, + nullptr); + } + + static gboolean on_minute_timer_reached(gpointer gself) + { + static_cast<LiveClock::Impl*>(gself)->restart_minute_timer(); + return G_SOURCE_REMOVE; + } + +protected: + + LiveClock& m_owner; + GTimeZone* m_timezone = nullptr; + std::shared_ptr<Timezones> m_timezones; + + DateTime m_prev_datetime; + unsigned int m_timer = 0; +}; + +LiveClock::LiveClock(const std::shared_ptr<Timezones>& tzd): + p(new Impl(*this, tzd)) +{ +} + +LiveClock::~LiveClock() =default; + +DateTime LiveClock::localtime() const +{ + return p->localtime(); +} + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity + diff --git a/src/clock-live.h b/src/clock-live.h deleted file mode 100644 index 4425f5b..0000000 --- a/src/clock-live.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * Authors: - * Charles Kerr <charles.kerr@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/>. - */ - -#ifndef __INDICATOR_DATETIME_CLOCK_LIVE__H__ -#define __INDICATOR_DATETIME_CLOCK_LIVE__H__ - -#include <glib-object.h> /* parent class */ - -#include "clock.h" - -G_BEGIN_DECLS - -#define INDICATOR_TYPE_DATETIME_CLOCK_LIVE \ - (indicator_datetime_clock_live_get_type()) - -#define INDICATOR_DATETIME_CLOCK_LIVE(o) \ - (G_TYPE_CHECK_INSTANCE_CAST ((o), \ - INDICATOR_TYPE_DATETIME_CLOCK_LIVE, \ - IndicatorDatetimeClockLive)) - -#define INDICATOR_DATETIME_CLOCK_LIVE_GET_CLASS(o) \ - (G_TYPE_INSTANCE_GET_CLASS ((o), \ - INDICATOR_TYPE_DATETIME_CLOCK_LIVE, \ - IndicatorDatetimeClockLiveClass)) - -#define INDICATOR_IS_DATETIME_CLOCK_LIVE(o) \ - (G_TYPE_CHECK_INSTANCE_TYPE ((o), \ - INDICATOR_TYPE_DATETIME_CLOCK_LIVE)) - -typedef struct _IndicatorDatetimeClockLive - IndicatorDatetimeClockLive; -typedef struct _IndicatorDatetimeClockLivePriv - IndicatorDatetimeClockLivePriv; -typedef struct _IndicatorDatetimeClockLiveClass - IndicatorDatetimeClockLiveClass; - -/** - * An IndicatorDatetimeClock which gives live clock times - * from timezones determined by geoclue and /etc/timezone - */ -struct _IndicatorDatetimeClockLive -{ - GObject parent_instance; - - IndicatorDatetimeClockLivePriv * priv; -}; - -struct _IndicatorDatetimeClockLiveClass -{ - GObjectClass parent_class; -}; - -IndicatorDatetimeClock * indicator_datetime_clock_live_new (void); - -G_END_DECLS - -#endif /* __INDICATOR_DATETIME_CLOCK_LIVE__H__ */ diff --git a/src/clock.c b/src/clock.c deleted file mode 100644 index 2c2fec2..0000000 --- a/src/clock.c +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * Authors: - * Charles Kerr <charles.kerr@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 "clock.h" - -enum -{ - SIGNAL_CHANGED, - SIGNAL_LAST -}; - -static guint signals[SIGNAL_LAST] = { 0 }; - -G_DEFINE_INTERFACE (IndicatorDatetimeClock, - indicator_datetime_clock, - G_TYPE_OBJECT); - -static void -indicator_datetime_clock_default_init (IndicatorDatetimeClockInterface * klass) -{ - signals[SIGNAL_CHANGED] = g_signal_new ( - "changed", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (IndicatorDatetimeClockInterface, changed), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); -} - -/*** -**** PUBLIC API -***/ - -/** - * Get a strv of timezones. - * - * Return value: (element-type char*) - * (transfer full): - * array of timezone strings - */ -const gchar ** -indicator_datetime_clock_get_timezones (IndicatorDatetimeClock * self) -{ - const gchar ** timezones; - IndicatorDatetimeClockInterface * iface; - - g_return_val_if_fail (INDICATOR_IS_DATETIME_CLOCK(self), NULL); - iface = INDICATOR_DATETIME_CLOCK_GET_INTERFACE(self); - - if (iface->get_timezones != NULL) - timezones = iface->get_timezones (self); - else - timezones = NULL; - - return timezones; -} - -/** - * Get the current time. - * - * Return value: (element-type GDateTime*) - * (transfer full): - * the current time. - */ -GDateTime * -indicator_datetime_clock_get_localtime (IndicatorDatetimeClock * self) -{ - GDateTime * now; - IndicatorDatetimeClockInterface * iface; - - g_return_val_if_fail (INDICATOR_IS_DATETIME_CLOCK(self), NULL); - iface = INDICATOR_DATETIME_CLOCK_GET_INTERFACE(self); - - if (iface->get_localtime != NULL) - now = iface->get_localtime (self); - else - now = NULL; - - return now; -} - -/** - * Emits the "changed" signal. - * - * This should only be called by subclasses. - */ -void -indicator_datetime_clock_emit_changed (IndicatorDatetimeClock * self) -{ - g_return_if_fail (INDICATOR_IS_DATETIME_CLOCK (self)); - - g_signal_emit (self, signals[SIGNAL_CHANGED], 0, NULL); -} diff --git a/src/clock.cpp b/src/clock.cpp new file mode 100644 index 0000000..d5293cc --- /dev/null +++ b/src/clock.cpp @@ -0,0 +1,93 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * 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/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <datetime/clock.h> + +#include <glib.h> +#include <gio/gio.h> + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +Clock::Clock(): + m_cancellable(g_cancellable_new()) +{ + g_bus_get(G_BUS_TYPE_SYSTEM, m_cancellable, onSystemBusReady, this); +} + +Clock::~Clock() +{ + g_cancellable_cancel(m_cancellable); + g_clear_object(&m_cancellable); + + if (m_sleep_subscription_id) + g_dbus_connection_signal_unsubscribe(m_system_bus, m_sleep_subscription_id); + + g_clear_object(&m_system_bus); +} + +void +Clock::onSystemBusReady(GObject*, GAsyncResult * res, gpointer gself) +{ + GDBusConnection * system_bus; + + if ((system_bus = g_bus_get_finish(res, nullptr))) + { + auto self = static_cast<Clock*>(gself); + + self->m_system_bus = system_bus; + + self->m_sleep_subscription_id = g_dbus_connection_signal_subscribe( + system_bus, + nullptr, + "org.freedesktop.login1.Manager", // interface + "PrepareForSleep", // signal name + "/org/freedesktop/login1", // object path + nullptr, // arg0 + G_DBUS_SIGNAL_FLAGS_NONE, + onPrepareForSleep, + self, + nullptr); + } +} + +void +Clock::onPrepareForSleep(GDBusConnection* /*connection*/, + const gchar* /*sender_name*/, + const gchar* /*object_path*/, + const gchar* /*interface_name*/, + const gchar* /*signal_name*/, + GVariant* /*parameters*/, + gpointer gself) +{ + static_cast<Clock*>(gself)->minuteChanged(); +} + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/clock.h b/src/clock.h deleted file mode 100644 index 40cdf1c..0000000 --- a/src/clock.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * Authors: - * Charles Kerr <charles.kerr@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/>. - */ - -#ifndef __INDICATOR_DATETIME_CLOCK__H__ -#define __INDICATOR_DATETIME_CLOCK__H__ - -#include <glib-object.h> - -G_BEGIN_DECLS - -#define INDICATOR_TYPE_DATETIME_CLOCK \ - (indicator_datetime_clock_get_type ()) - -#define INDICATOR_DATETIME_CLOCK(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ - INDICATOR_TYPE_DATETIME_CLOCK, \ - IndicatorDatetimeClock)) - -#define INDICATOR_IS_DATETIME_CLOCK(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE ((obj), INDICATOR_TYPE_DATETIME_CLOCK)) - -#define INDICATOR_DATETIME_CLOCK_GET_INTERFACE(inst) \ - (G_TYPE_INSTANCE_GET_INTERFACE ((inst), \ - INDICATOR_TYPE_DATETIME_CLOCK, \ - IndicatorDatetimeClockInterface)) - -typedef struct _IndicatorDatetimeClock - IndicatorDatetimeClock; - -typedef struct _IndicatorDatetimeClockInterface - IndicatorDatetimeClockInterface; - -struct _IndicatorDatetimeClockInterface -{ - GTypeInterface parent_iface; - - /* signals */ - void (*changed) (IndicatorDatetimeClock * self); - - /* virtual functions */ - const gchar** (*get_timezones) (IndicatorDatetimeClock * self); - GDateTime* (*get_localtime) (IndicatorDatetimeClock * self); -}; - -GType indicator_datetime_clock_get_type (void); - -/*** -**** -***/ - -const gchar ** indicator_datetime_clock_get_timezones (IndicatorDatetimeClock * clock); - -GDateTime * indicator_datetime_clock_get_localtime (IndicatorDatetimeClock * clock); - -void indicator_datetime_clock_emit_changed (IndicatorDatetimeClock * clock); - - -G_END_DECLS - -#endif /* __INDICATOR_DATETIME_CLOCK__H__ */ diff --git a/src/date-time.cpp b/src/date-time.cpp new file mode 100644 index 0000000..40c638f --- /dev/null +++ b/src/date-time.cpp @@ -0,0 +1,142 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * 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/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <datetime/date-time.h> + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +DateTime::DateTime(time_t t) +{ + GDateTime * gdt = g_date_time_new_from_unix_local(t); + reset(gdt); + g_date_time_unref(gdt); +} + +DateTime DateTime::NowLocal() +{ + GDateTime * gdt = g_date_time_new_now_local(); + DateTime dt(gdt); + g_date_time_unref(gdt); + return dt; +} + +DateTime DateTime::to_timezone(const std::string& zone) const +{ + auto gtz = g_time_zone_new(zone.c_str()); + auto gdt = g_date_time_to_timezone(get(), gtz); + DateTime dt(gdt); + g_time_zone_unref(gtz); + g_date_time_unref(gdt); + return dt; +} + +GDateTime* DateTime::get() const +{ + g_assert(m_dt); + return m_dt.get(); +} + +std::string DateTime::format(const std::string& fmt) const +{ + const auto str = g_date_time_format(get(), fmt.c_str()); + std::string ret = str; + g_free(str); + return ret; +} + +int DateTime::day_of_month() const +{ + return g_date_time_get_day_of_month(get()); +} + +int64_t DateTime::to_unix() const +{ + return g_date_time_to_unix(get()); +} + +void DateTime::reset(GDateTime* in) +{ + if (in) + { + auto deleter = [](GDateTime* dt){g_date_time_unref(dt);}; + m_dt = std::shared_ptr<GDateTime>(g_date_time_ref(in), deleter); + g_assert(m_dt); + } + else + { + m_dt.reset(); + } +} + +bool DateTime::operator<(const DateTime& that) const +{ + return g_date_time_compare(get(), that.get()) < 0; +} + +bool DateTime::operator!=(const DateTime& that) const +{ + // return true if this isn't set, or if it's not equal + return (!m_dt) || !(*this == that); +} + +bool DateTime::operator==(const DateTime& that) const +{ + auto dt = get(); + auto tdt = that.get(); + if (!dt && !tdt) return true; + if (!dt || !tdt) return false; + return g_date_time_compare(get(), that.get()) == 0; +} + +bool DateTime::is_same_day(const DateTime& a, const DateTime& b) +{ + // it's meaningless to compare uninitialized dates + if (!a.m_dt || !b.m_dt) + return false; + + const auto adt = a.get(); + const auto bdt = b.get(); + return (g_date_time_get_year(adt) == g_date_time_get_year(bdt)) + && (g_date_time_get_day_of_year(adt) == g_date_time_get_day_of_year(bdt)); +} + +bool DateTime::is_same_minute(const DateTime& a, const DateTime& b) +{ + if (!is_same_day(a,b)) + return false; + + const auto adt = a.get(); + const auto bdt = b.get(); + return (g_date_time_get_hour(adt) == g_date_time_get_hour(bdt)) + && (g_date_time_get_minute(adt) == g_date_time_get_minute(bdt)); +} + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/dbus-shared.h b/src/dbus-shared.h deleted file mode 100644 index 24319e3..0000000 --- a/src/dbus-shared.h +++ /dev/null @@ -1,24 +0,0 @@ -/* -An indicator to show date and time information. - -Copyright 2010 Canonical Ltd. - -Authors: - 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/>. -*/ - -#define BUS_NAME "com.canonical.indicator.datetime" -#define BUS_PATH "/com/canonical/indicator/datetime" - diff --git a/src/exporter.cpp b/src/exporter.cpp new file mode 100644 index 0000000..86e3670 --- /dev/null +++ b/src/exporter.cpp @@ -0,0 +1,145 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * 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/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <datetime/dbus-shared.h> +#include <datetime/exporter.h> + +#include <glib/gi18n.h> +#include <gio/gio.h> + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +Exporter::~Exporter() +{ + if (m_dbus_connection != nullptr) + { + for(auto& id : m_exported_menu_ids) + g_dbus_connection_unexport_menu_model(m_dbus_connection, id); + + if (m_exported_actions_id) + g_dbus_connection_unexport_action_group(m_dbus_connection, m_exported_actions_id); + } + + if (m_own_id) + g_bus_unown_name(m_own_id); + + g_clear_object(&m_dbus_connection); +} + +/*** +**** +***/ + +void +Exporter::on_bus_acquired(GDBusConnection* connection, const gchar* name, gpointer gthis) +{ + g_debug("bus acquired: %s", name); + static_cast<Exporter*>(gthis)->on_bus_acquired(connection, name); +} + +void +Exporter::on_bus_acquired(GDBusConnection* connection, const gchar* /*name*/) +{ + m_dbus_connection = static_cast<GDBusConnection*>(g_object_ref(G_OBJECT(connection))); + + // export the actions + GError * error = nullptr; + const auto id = g_dbus_connection_export_action_group(m_dbus_connection, + BUS_PATH, + m_actions->action_group(), + &error); + if (id) + { + m_exported_actions_id = id; + } + else + { + g_warning("cannot export action group: %s", error->message); + g_clear_error(&error); + } + + // export the menus + for(auto& menu : m_menus) + { + const auto path = std::string(BUS_PATH) + "/" + menu->name(); + const auto id = g_dbus_connection_export_menu_model(m_dbus_connection, path.c_str(), menu->menu_model(), &error); + if (id) + { + m_exported_menu_ids.insert(id); + } + else + { + if (error != nullptr) + g_warning("cannot export %s menu: %s", menu->name().c_str(), error->message); + g_clear_error(&error); + } + } +} + +/*** +**** +***/ + +void +Exporter::on_name_lost(GDBusConnection* connection, const gchar* name, gpointer gthis) +{ + g_debug("name lost: %s", name); + static_cast<Exporter*>(gthis)->on_name_lost(connection, name); +} + +void +Exporter::on_name_lost(GDBusConnection* /*connection*/, const gchar* /*name*/) +{ + name_lost(); +} + +/*** +**** +***/ + +void +Exporter::publish(std::shared_ptr<Actions>& actions, + std::vector<std::shared_ptr<Menu>>& menus) +{ + m_actions = actions; + m_menus = menus; + m_own_id = g_bus_own_name(G_BUS_TYPE_SESSION, + BUS_NAME, + G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT, + on_bus_acquired, + nullptr, + on_name_lost, + this, + nullptr); +} + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity + diff --git a/src/formatter-desktop.cpp b/src/formatter-desktop.cpp new file mode 100644 index 0000000..5efdf8b --- /dev/null +++ b/src/formatter-desktop.cpp @@ -0,0 +1,168 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * 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/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <datetime/formatter.h> + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +namespace +{ + +std::string joinDateAndTimeFormatStrings(const char* date_string, + const char* time_string) +{ + std::string str; + + if (date_string && time_string) + { + /* TRANSLATORS: This is a format string passed to strftime to + * combine the date and the time. The value of "%s\u2003%s" + * will result in a string like this in US English 12-hour time: + * 'Fri Jul 16 11:50 AM'. The space in between date and time is + * a Unicode en space (E28082 in UTF-8 hex). */ + str = date_string; + str += "\u2003"; + str += time_string; + } + else if (date_string) + { + str = date_string; + } + else // time_string + { + str = time_string; + } + + return str; +} +} // unnamed namespace + +/*** +**** +***/ + +DesktopFormatter::DesktopFormatter(const std::shared_ptr<Clock>& clock_in, + const std::shared_ptr<Settings>& settings_in): + Formatter(clock_in), + m_settings(settings_in) +{ + m_settings->show_day.changed().connect([this](bool){rebuildHeaderFormat();}); + m_settings->show_date.changed().connect([this](bool){rebuildHeaderFormat();}); + m_settings->show_year.changed().connect([this](bool){rebuildHeaderFormat();}); + m_settings->show_seconds.changed().connect([this](bool){rebuildHeaderFormat();}); + m_settings->time_format_mode.changed().connect([this](TimeFormatMode){rebuildHeaderFormat();}); + m_settings->custom_time_format.changed().connect([this](const std::string&){rebuildHeaderFormat();}); + + rebuildHeaderFormat(); +} + +void DesktopFormatter::rebuildHeaderFormat() +{ + headerFormat.set(getHeaderLabelFormatString()); +} + +std::string DesktopFormatter::getHeaderLabelFormatString() const +{ + std::string fmt; + const auto mode = m_settings->time_format_mode.get(); + + if (mode == TIME_FORMAT_MODE_CUSTOM) + { + fmt = m_settings->custom_time_format.get(); + } + else + { + const auto show_day = m_settings->show_day.get(); + const auto show_date = m_settings->show_date.get(); + const auto show_year = show_date && m_settings->show_year.get(); + const auto date_fmt = getDateFormat(show_day, show_date, show_year); + const auto time_fmt = getFullTimeFormatString(); + fmt = joinDateAndTimeFormatStrings(date_fmt, time_fmt); + } + + return fmt; +} + +const gchar* DesktopFormatter::getFullTimeFormatString() const +{ + const auto show_seconds = m_settings->show_seconds.get(); + + bool twelvehour; + switch (m_settings->time_format_mode.get()) + { + case TIME_FORMAT_MODE_LOCALE_DEFAULT: + twelvehour = is_locale_12h(); + break; + + case TIME_FORMAT_MODE_24_HOUR: + twelvehour = false; + break; + + default: + twelvehour = true; + break; + } + + return getDefaultHeaderTimeFormat(twelvehour, show_seconds); +} + +const gchar* DesktopFormatter::getDateFormat(bool show_day, bool show_date, bool show_year) const +{ + const char * fmt; + + if (show_day && show_date && show_year) + /* TRANSLATORS: a strftime(3) format showing the weekday, date, and year */ + fmt = T_("%a %b %e %Y"); + else if (show_day && show_date) + /* TRANSLATORS: a strftime(3) format showing the weekday and date */ + fmt = T_("%a %b %e"); + else if (show_day && show_year) + /* TRANSLATORS: a strftime(3) format showing the weekday and year. */ + fmt = T_("%a %Y"); + else if (show_day) + /* TRANSLATORS: a strftime(3) format showing the weekday. */ + fmt = T_("%a"); + else if (show_date && show_year) + /* TRANSLATORS: a strftime(3) format showing the date and year */ + fmt = T_("%b %e %Y"); + else if (show_date) + /* TRANSLATORS: a strftime(3) format showing the date */ + fmt = T_("%b %e"); + else if (show_year) + /* TRANSLATORS: a strftime(3) format showing the year */ + fmt = T_("%Y"); + else + fmt = nullptr; + + return fmt; +} + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/formatter.cpp b/src/formatter.cpp new file mode 100644 index 0000000..989781a --- /dev/null +++ b/src/formatter.cpp @@ -0,0 +1,429 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * 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/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <datetime/formatter.h> + +#include <datetime/clock.h> +#include <datetime/utils.h> + +#include <glib.h> +#include <glib/gi18n.h> + +#include <locale.h> // setlocale() +#include <langinfo.h> // nl_langinfo() +#include <string.h> // strstr() + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +namespace +{ + +void clear_timer(guint& tag) +{ + if (tag) + { + g_source_remove(tag); + tag = 0; + } +} + +gint calculate_milliseconds_until_next_second(const DateTime& now) +{ + gint interval_usec; + guint interval_msec; + + interval_usec = G_USEC_PER_SEC - g_date_time_get_microsecond(now.get()); + interval_msec = (interval_usec + 999) / 1000; + return interval_msec; +} + +/* + * We 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. + */ +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), + 0.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; +} +} // unnamed namespace + + +class Formatter::Impl +{ +public: + + Impl(Formatter* owner, const std::shared_ptr<Clock>& clock): + m_owner(owner), + m_clock(clock) + { + m_owner->headerFormat.changed().connect([this](const std::string& /*fmt*/){update_header();}); + m_clock->minuteChanged.connect([this](){update_header();}); + update_header(); + + restartRelativeTimer(); + } + + ~Impl() + { + clear_timer(m_header_seconds_timer); + clear_timer(m_relative_timer); + } + +private: + + static bool format_shows_seconds(const std::string& fmt) + { + return (fmt.find("%s") != std::string::npos) + || (fmt.find("%S") != std::string::npos) + || (fmt.find("%T") != std::string::npos) + || (fmt.find("%X") != std::string::npos) + || (fmt.find("%c") != std::string::npos); + } + + void update_header() + { + // update the header property + const auto fmt = m_owner->headerFormat.get(); + const auto str = m_clock->localtime().format(fmt); + m_owner->header.set(str); + + // if the header needs to show seconds, set a timer. + if (format_shows_seconds(fmt)) + start_header_timer(); + else + clear_timer(m_header_seconds_timer); + } + + // we've got a header format that shows seconds, + // so we need to update it every second + void start_header_timer() + { + clear_timer(m_header_seconds_timer); + + const auto now = m_clock->localtime(); + auto interval_msec = calculate_milliseconds_until_next_second(now); + interval_msec += 50; // add a small margin to ensure the callback + // fires /after/ next is reached + m_header_seconds_timer = g_timeout_add_full(G_PRIORITY_HIGH, + interval_msec, + on_header_timer, + this, + nullptr); + } + + static gboolean on_header_timer(gpointer gself) + { + static_cast<Formatter::Impl*>(gself)->update_header(); + return G_SOURCE_REMOVE; + } + +private: + + void restartRelativeTimer() + { + clear_timer(m_relative_timer); + + const auto now = m_clock->localtime(); + const auto seconds = calculate_seconds_until_next_fifteen_minutes(now.get()); + m_relative_timer = g_timeout_add_seconds(seconds, onRelativeTimer, this); + } + + static gboolean onRelativeTimer(gpointer gself) + { + auto self = static_cast<Formatter::Impl*>(gself); + self->m_owner->relativeFormatChanged(); + self->restartRelativeTimer(); + return G_SOURCE_REMOVE; + } + +private: + Formatter* const m_owner; + guint m_header_seconds_timer = 0; + guint m_relative_timer = 0; + +public: + std::shared_ptr<Clock> m_clock; +}; + +/*** +**** +***/ + +Formatter::Formatter(const std::shared_ptr<Clock>& clock): + p(new Formatter::Impl(this, clock)) +{ +} + +Formatter::~Formatter() +{ +} + +bool +Formatter::is_locale_12h() +{ + return ::is_locale_12h(); +} + +const char* +Formatter::T_(const char *msg) +{ + /* General strategy here is to make sure LANGUAGE is empty (since that + trumps all LC_* vars) and then to temporarily swap LC_TIME and + LC_MESSAGES. Then have gettext translate msg. + + We strdup the strings because the setlocale & *env functions do not + guarantee anything about the storage used for the string, and thus + the string may not be portably safe after multiple calls. + + Note that while you might think g_dcgettext would do the trick here, + that actually looks in /usr/share/locale/XX/LC_TIME, not the + LC_MESSAGES directory, so we won't find any translation there. + */ + + auto message_locale = g_strdup(setlocale(LC_MESSAGES, nullptr)); + const auto time_locale = setlocale(LC_TIME, nullptr); + auto language = g_strdup(g_getenv("LANGUAGE")); + + if (language) + g_unsetenv("LANGUAGE"); + setlocale(LC_MESSAGES, time_locale); + + /* Get the LC_TIME version */ + const auto rv = _(msg); + + /* Put everything back the way it was */ + setlocale(LC_MESSAGES, message_locale); + if (language) + g_setenv("LANGUAGE", language, TRUE); + + g_free(message_locale); + g_free(language); + return rv; +} + +const char* +Formatter::getDefaultHeaderTimeFormat(bool twelvehour, bool show_seconds) +{ + const char* fmt; + + if (twelvehour && show_seconds) + /* TRANSLATORS: a strftime(3) format for 12hr time w/seconds */ + fmt = T_("%l:%M:%S %p"); + else if (twelvehour) + /* TRANSLATORS: a strftime(3) format for 12hr time */ + fmt = T_("%l:%M %p"); + else if (show_seconds) + /* TRANSLATORS: a strftime(3) format for 24hr time w/seconds */ + fmt = T_("%H:%M:%S"); + else + /* TRANSLATORS: a strftime(3) format for 24hr time */ + fmt = T_("%H:%M"); + + return fmt; +} + +/*** +**** +***/ + +namespace +{ +typedef enum +{ + DATE_PROXIMITY_TODAY, + DATE_PROXIMITY_TOMORROW, + DATE_PROXIMITY_WEEK, + DATE_PROXIMITY_FAR +} +date_proximity_t; + +date_proximity_t getDateProximity(GDateTime* now, GDateTime* time) +{ + auto prox = DATE_PROXIMITY_FAR; + gint now_year, now_month, now_day; + gint time_year, time_month, time_day; + + // does it happen today? + g_date_time_get_ymd(now, &now_year, &now_month, &now_day); + g_date_time_get_ymd(time, &time_year, &time_month, &time_day); + if ((now_year == time_year) && (now_month == time_month) && (now_day == time_day)) + prox = DATE_PROXIMITY_TODAY; + + // does it happen tomorrow? + if (prox == DATE_PROXIMITY_FAR) + { + auto tomorrow = g_date_time_add_days(now, 1); + + gint tom_year, tom_month, tom_day; + g_date_time_get_ymd(tomorrow, &tom_year, &tom_month, &tom_day); + if ((tom_year == time_year) && (tom_month == time_month) && (tom_day == time_day)) + prox = DATE_PROXIMITY_TOMORROW; + + g_date_time_unref(tomorrow); + } + + // does it happen this week? + if (prox == DATE_PROXIMITY_FAR) + { + auto week = g_date_time_add_days(now, 6); + auto week_bound = g_date_time_new_local(g_date_time_get_year(week), + g_date_time_get_month(week), + g_date_time_get_day_of_month(week), + 23, 59, 59.9); + + if (g_date_time_compare(time, week_bound) <= 0) + prox = DATE_PROXIMITY_WEEK; + + g_date_time_unref(week_bound); + g_date_time_unref(week); + } + + return prox; +} +} // unnamed namespace + +/** + * _ a time today should be shown as just the time (e.g. “3:55 PM”) + * _ a full-day event today should be shown as “Today” + * _ a time any other day this week should be shown as the short version of the + * day and time (e.g. “Wed 3:55 PM”) + * _ a full-day event tomorrow should be shown as “Tomorrow” + * _ a full-day event another day this week should be shown as the + * weekday (e.g. “Friday”) + * _ a time after this week should be shown as the short version of the day, + * date, and time (e.g. “Wed 21 Apr 3:55 PM”) + * _ a full-day event after this week should be shown as the short version of + * the day and date (e.g. “Wed 21 Apr”). + * _ in addition, when presenting the times of upcoming events, the time should + * be followed by the timezone if it is different from the one the computer + * is currently set to. For example, “Wed 3:55 PM UTC−5”. + */ +std::string +Formatter::getRelativeFormat(GDateTime* then, GDateTime* then_end) const +{ + std::string ret; + const auto now = p->m_clock->localtime().get(); + + if (then != nullptr) + { + const bool full_day = then_end && (g_date_time_difference(then_end, then) >= G_TIME_SPAN_DAY); + const auto prox = getDateProximity(now, then); + + if (full_day) + { + switch (prox) + { + case DATE_PROXIMITY_TODAY: ret = _("Today"); break; + case DATE_PROXIMITY_TOMORROW: ret = _("Tomorrow"); break; + case DATE_PROXIMITY_WEEK: ret = T_("%A"); break; + case DATE_PROXIMITY_FAR: ret = T_("%a %d %b"); break; + } + } + else if (is_locale_12h()) + { + switch (prox) + { + case DATE_PROXIMITY_TODAY: ret = T_("%l:%M %p"); break; + case DATE_PROXIMITY_TOMORROW: ret = T_("Tomorrow\u2003%l:%M %p"); break; + case DATE_PROXIMITY_WEEK: ret = T_("%a\u2003%l:%M %p"); break; + case DATE_PROXIMITY_FAR: ret = T_("%a %d %b\u2003%l:%M %p"); break; + } + } + else + { + switch (prox) + { + case DATE_PROXIMITY_TODAY: ret = T_("%H:%M"); break; + case DATE_PROXIMITY_TOMORROW: ret = T_("Tomorrow\u2003%H:%M"); break; + case DATE_PROXIMITY_WEEK: ret = T_("%a\u2003%H:%M"); break; + case DATE_PROXIMITY_FAR: ret = T_("%a %d %b\u2003%H:%M"); break; + } + } + + /* if it's an appointment in a different timezone (and doesn't run for a full day) + then the time should be followed by its timezone. */ + if ((then_end != nullptr) && + (!full_day) && + ((g_date_time_get_utc_offset(now) != g_date_time_get_utc_offset(then)))) + { + ret += ' '; + ret += g_date_time_get_timezone_abbreviation(then); + } + } + + return ret; +} + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/locations-settings.cpp b/src/locations-settings.cpp new file mode 100644 index 0000000..9b90bc0 --- /dev/null +++ b/src/locations-settings.cpp @@ -0,0 +1,92 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * 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/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <datetime/locations-settings.h> + +#include <datetime/settings-shared.h> +#include <datetime/timezones.h> +#include <datetime/utils.h> + +#include <algorithm> // std::find() + +namespace unity { +namespace indicator { +namespace datetime { + +SettingsLocations::SettingsLocations(const std::shared_ptr<Settings>& settings, + const std::shared_ptr<Timezones>& timezones): + m_settings(settings), + m_timezones(timezones) +{ + m_settings->locations.changed().connect([this](const std::vector<std::string>&){reload();}); + m_settings->show_locations.changed().connect([this](bool){reload();}); + m_timezones->timezone.changed().connect([this](const std::string&){reload();}); + m_timezones->timezones.changed().connect([this](const std::set<std::string>&){reload();}); + + reload(); +} + +void +SettingsLocations::reload() +{ + std::vector<Location> v; + const std::string timezone_name = m_settings->timezone_name.get(); + + // add the primary timezone first + auto zone = m_timezones->timezone.get(); + if (!zone.empty()) + { + gchar * name = get_beautified_timezone_name(zone.c_str(), timezone_name.c_str()); + Location l(zone, name); + v.push_back(l); + g_free(name); + } + + // add the other detected timezones + for(const auto& zone : m_timezones->timezones.get()) + { + gchar * name = get_beautified_timezone_name(zone.c_str(), timezone_name.c_str()); + Location l(zone, name); + if (std::find(v.begin(), v.end(), l) == v.end()) + v.push_back(l); + g_free(name); + } + + // maybe add the user-specified locations + if (m_settings->show_locations.get()) + { + for(const auto& locstr : m_settings->locations.get()) + { + gchar* zone; + gchar* name; + split_settings_location(locstr.c_str(), &zone, &name); + Location loc(zone, name); + if (std::find(v.begin(), v.end(), loc) == v.end()) + v.push_back(loc); + g_free(name); + g_free(zone); + } + } + + locations.set(v); +} + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/locations.cpp b/src/locations.cpp new file mode 100644 index 0000000..d6ab73a --- /dev/null +++ b/src/locations.cpp @@ -0,0 +1,64 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * 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/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <datetime/locations.h> + +#include <glib.h> + +namespace unity { +namespace indicator { +namespace datetime { + +Location::Location(const std::string& zone_, const std::string& name_): + m_zone(zone_), + m_name(name_) +{ + auto gzone = g_time_zone_new (zone().c_str()); + auto gtime = g_date_time_new_now (gzone); + m_offset = g_date_time_get_utc_offset (gtime); + g_date_time_unref (gtime); + g_time_zone_unref (gzone); +} + +#if 0 +DateTime Location::localtime(const DateTime& reference_point) const +{ +GDateTime * g_date_time_to_timezone (GDateTime *datetime, + GTimeZone *tz); + auto gzone = g_time_zone_new(zone().c_str()); + const auto gtime = reference_point.get(); + auto glocal = g_date_time_new (gzone, + g_date_time_get_year(gtime), + g_date_time_get_month(gtime), + g_date_time_get_day_of_month(gtime), + g_date_time_get_hour(gtime), + g_date_time_get_minute(gtime), + g_date_time_get_seconds(gtime)); + DateTime local(glocal); + g_date_time_unref(glocal); + g_message("reference: %zu", (size_t)reference_point.to_unix(), (size_t)local.to_unix()); + //g_date_time_unref(gtime); + g_time_zone_unref(gzone); + return local; +} +#endif + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/main.c b/src/main.c deleted file mode 100644 index 868d41b..0000000 --- a/src/main.c +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * Authors: - * Charles Kerr <charles.kerr@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 <locale.h> -#include <stdlib.h> /* exit() */ - -#include <glib/gi18n.h> -#include <gio/gio.h> -#include <libnotify/notify.h> - -#include "clock-live.h" -#include "planner-eds.h" -#include "service.h" - -/*** -**** -***/ - -static void -on_name_lost (gpointer instance G_GNUC_UNUSED, gpointer loop) -{ - g_message ("exiting: service couldn't acquire or lost ownership of busname"); - - g_main_loop_quit ((GMainLoop*)loop); -} - -int -main (int argc G_GNUC_UNUSED, char ** argv G_GNUC_UNUSED) -{ - IndicatorDatetimeClock * clock; - IndicatorDatetimePlanner * planner; - IndicatorDatetimeService * service; - GMainLoop * loop; - - /* Work around a deadlock in glib's type initialization. It can be - * removed when https://bugzilla.gnome.org/show_bug.cgi?id=674885 is - * fixed. - */ - g_type_ensure (G_TYPE_DBUS_CONNECTION); - - /* boilerplate i18n */ - setlocale (LC_ALL, ""); - bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); - textdomain (GETTEXT_PACKAGE); - - /* init libnotify */ - if (!notify_init ("indicator-datetime-service")) - g_critical ("libnotify initialization failed"); - - /* create the service */ - clock = indicator_datetime_clock_live_new (); - planner = indicator_datetime_planner_eds_new (); - service = indicator_datetime_service_new (clock, planner); - - /* run */ - loop = g_main_loop_new (NULL, FALSE); - g_signal_connect (service, INDICATOR_DATETIME_SERVICE_SIGNAL_NAME_LOST, - G_CALLBACK(on_name_lost), loop); - g_main_loop_run (loop); - g_main_loop_unref (loop); - - /* cleanup */ - g_object_unref (service); - g_object_unref (planner); - g_object_unref (clock); - return 0; -} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..2c4f160 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,71 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * Authors: + * Charles Kerr <charles.kerr@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 <datetime/actions-live.h> +#include <datetime/exporter.h> +#include <datetime/menu.h> +#include <datetime/state-live.h> + +#include <glib/gi18n.h> // bindtextdomain() +#include <gio/gio.h> +#include <libnotify/notify.h> + +#include <locale.h> +#include <stdlib.h> // exit() + +using namespace unity::indicator::datetime; + +int +main(int /*argc*/, char** /*argv*/) +{ + // Work around a deadlock in glib's type initialization. + // It can be removed when https://bugzilla.gnome.org/show_bug.cgi?id=674885 is fixed. + g_type_ensure(G_TYPE_DBUS_CONNECTION); + + // boilerplate i18n + setlocale(LC_ALL, ""); + bindtextdomain(GETTEXT_PACKAGE, GNOMELOCALEDIR); + textdomain(GETTEXT_PACKAGE); + + // init libnotify + if(!notify_init("indicator-datetime-service")) + g_critical("libnotify initialization failed"); + + // build the state and actions for the MenuFactory to use + std::shared_ptr<State> state(new LiveState); + std::shared_ptr<Actions> actions(new LiveActions(state)); + MenuFactory factory(actions, state); + + // create the menus + std::vector<std::shared_ptr<Menu>> menus; + for(int i=0, n=Menu::NUM_PROFILES; i<n; i++) + menus.push_back(factory.buildMenu(Menu::Profile(i))); + + // export them & run until we lose the busname + auto loop = g_main_loop_new(nullptr, false); + Exporter exporter; + exporter.name_lost.connect([loop](){ + g_message("%s exiting; failed/lost bus ownership", GETTEXT_PACKAGE); + g_main_loop_quit(loop); + }); + exporter.publish(actions, menus); + g_main_loop_run(loop); + g_main_loop_unref(loop); + return 0; +} diff --git a/src/menu.cpp b/src/menu.cpp new file mode 100644 index 0000000..9e76d4b --- /dev/null +++ b/src/menu.cpp @@ -0,0 +1,557 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * 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/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <datetime/menu.h> + +#include <datetime/formatter.h> +#include <datetime/state.h> + +#include <json-glib/json-glib.h> + +#include <glib/gi18n.h> +#include <gio/gio.h> + +namespace unity { +namespace indicator { +namespace datetime { + +/**** +***** +****/ + +#define FALLBACK_ALARM_CLOCK_ICON_NAME "clock" +#define CALENDAR_ICON_NAME "calendar" + +class MenuImpl: public Menu +{ +protected: + MenuImpl(const Menu::Profile profile_in, + const std::string& name_in, + std::shared_ptr<State>& state, + std::shared_ptr<Actions>& actions, + std::shared_ptr<Formatter> formatter): + Menu(profile_in, name_in), + m_state(state), + m_actions(actions), + m_formatter(formatter) + { + // preload the alarm icon from click + m_serialized_alarm_icon = create_alarm_icon(); + + // initialize the menu + create_gmenu(); + for (int i=0; i<NUM_SECTIONS; i++) + update_section(Section(i)); + + // listen for state changes so we can update the menu accordingly + m_formatter->header.changed().connect([this](const std::string&){ + update_header(); + }); + m_formatter->headerFormat.changed().connect([this](const std::string&){ + update_section(Locations); // need to update x-canonical-time-format + }); + m_formatter->relativeFormatChanged.connect([this](){ + update_section(Appointments); // uses formatter.getRelativeFormat() + update_section(Locations); // uses formatter.getRelativeFormat() + }); + m_state->settings->show_clock.changed().connect([this](bool){ + update_header(); // update header's label + update_section(Locations); // locations' relative time may have changed + }); + m_state->settings->show_events.changed().connect([this](bool){ + update_section(Appointments); // showing events got toggled + }); + m_state->planner->upcoming.changed().connect([this](const std::vector<Appointment>&){ + update_section(Appointments); // "upcoming" is the list of Appointments we show + }); + m_state->clock->dateChanged.connect([this](){ + update_section(Calendar); // need to update the Date menuitem + update_section(Locations); // locations' relative time may have changed + }); + m_state->locations->locations.changed().connect([this](const std::vector<Location>&) { + update_section(Locations); // "locations" is the list of Locations we show + }); + } + + virtual ~MenuImpl() + { + g_clear_object(&m_menu); + g_clear_pointer(&m_serialized_alarm_icon, g_variant_unref); + g_clear_pointer(&m_serialized_calendar_icon, g_variant_unref); + } + + virtual GVariant* create_header_state() =0; + + void update_header() + { + auto action_group = m_actions->action_group(); + auto action_name = name() + "-header"; + auto state = create_header_state(); + g_action_group_change_action_state(action_group, action_name.c_str(), state); + } + + std::shared_ptr<State> m_state; + std::shared_ptr<Actions> m_actions; + std::shared_ptr<Formatter> m_formatter; + GMenu* m_submenu = nullptr; + + GVariant* get_serialized_alarm_icon() { return m_serialized_alarm_icon; } + +private: + + /* try to get the clock app's filename from click. (/$pkgdir/$icon) */ + static GVariant* create_alarm_icon() + { + GVariant* serialized = nullptr; + gchar* icon_filename = nullptr; + gchar* standard_error = nullptr; + gchar* pkgdir = nullptr; + + g_spawn_command_line_sync("click pkgdir com.ubuntu.clock", &pkgdir, &standard_error, nullptr, nullptr); + g_clear_pointer(&standard_error, g_free); + if (pkgdir != nullptr) + { + gchar* manifest = nullptr; + g_strstrip(pkgdir); + g_spawn_command_line_sync("click info com.ubuntu.clock", &manifest, &standard_error, nullptr, nullptr); + g_clear_pointer(&standard_error, g_free); + if (manifest != nullptr) + { + JsonParser* parser = json_parser_new(); + if (json_parser_load_from_data(parser, manifest, -1, nullptr)) + { + JsonNode* root = json_parser_get_root(parser); /* transfer-none */ + if ((root != nullptr) && (JSON_NODE_TYPE(root) == JSON_NODE_OBJECT)) + { + JsonObject* o = json_node_get_object(root); /* transfer-none */ + const gchar* icon_name = json_object_get_string_member(o, "icon"); + if (icon_name != nullptr) + icon_filename = g_build_filename(pkgdir, icon_name, nullptr); + } + } + g_object_unref(parser); + g_free(manifest); + } + g_free(pkgdir); + } + + if (icon_filename != nullptr) + { + GFile* file = g_file_new_for_path(icon_filename); + GIcon* icon = g_file_icon_new(file); + + serialized = g_icon_serialize(icon); + + g_object_unref(icon); + g_object_unref(file); + g_free(icon_filename); + } + + if (serialized == nullptr) + { + auto i = g_themed_icon_new_with_default_fallbacks(FALLBACK_ALARM_CLOCK_ICON_NAME); + serialized = g_icon_serialize(i); + g_object_unref(i); + } + + return serialized; + } + + GVariant* get_serialized_calendar_icon() + { + if (G_UNLIKELY(m_serialized_calendar_icon == nullptr)) + { + auto i = g_themed_icon_new_with_default_fallbacks(CALENDAR_ICON_NAME); + m_serialized_calendar_icon = g_icon_serialize(i); + g_object_unref(i); + } + + return m_serialized_calendar_icon; + } + + void create_gmenu() + { + g_assert(m_submenu == nullptr); + + m_submenu = g_menu_new(); + + // build placeholders for the sections + for(int i=0; i<NUM_SECTIONS; i++) + { + GMenuItem * item = g_menu_item_new(nullptr, nullptr); + g_menu_append_item(m_submenu, item); + g_object_unref(item); + } + + // add submenu to the header + const auto detailed_action = std::string("indicator.") + name() + "-header"; + auto header = g_menu_item_new(nullptr, detailed_action.c_str()); + g_menu_item_set_attribute(header, "x-canonical-type", "s", + "com.canonical.indicator.root"); + g_menu_item_set_submenu(header, G_MENU_MODEL(m_submenu)); + g_object_unref(m_submenu); + + // add header to the menu + m_menu = g_menu_new(); + g_menu_append_item(m_menu, header); + g_object_unref(header); + } + + GMenuModel* create_calendar_section(Profile profile) + { + const bool allow_activation = (profile == Desktop) + || (profile == Phone); + const bool show_calendar = (profile == Desktop) + || (profile == DesktopGreeter); + auto menu = g_menu_new(); + + // add a menuitem that shows the current date + auto label = m_state->clock->localtime().format(_("%A, %e %B %Y")); + auto item = g_menu_item_new (label.c_str(), nullptr); + auto v = get_serialized_calendar_icon(); + g_menu_item_set_attribute_value (item, G_MENU_ATTRIBUTE_ICON, v); + if (allow_activation) + { + v = g_variant_new_int64(0); + const char* action = "indicator.activate-planner"; + g_menu_item_set_action_and_target_value (item, action, v); + } + g_menu_append_item(menu, item); + g_object_unref(item); + + // add calendar + if (show_calendar) + { + item = g_menu_item_new ("[calendar]", NULL); + v = g_variant_new_int64(0); + g_menu_item_set_action_and_target_value (item, "indicator.calendar", v); + g_menu_item_set_attribute (item, "x-canonical-type", + "s", "com.canonical.indicator.calendar"); + if (allow_activation) + { + g_menu_item_set_attribute (item, "activation-action", + "s", "indicator.activate-planner"); + } + g_menu_append_item (menu, item); + g_object_unref (item); + } + + return G_MENU_MODEL(menu); + } + + void add_appointments(GMenu* menu, Profile profile) + { + int n = 0; + const int MAX_APPTS = 5; + std::set<std::string> added; + + for (const auto& appt : m_state->planner->upcoming.get()) + { + if (n++ >= MAX_APPTS) + break; + + if (added.count(appt.uid)) + continue; + + added.insert(appt.uid); + + GDateTime* begin = appt.begin(); + GDateTime* end = appt.end(); + auto fmt = m_formatter->getRelativeFormat(begin, end); + auto unix_time = g_date_time_to_unix(begin); + + auto menu_item = g_menu_item_new (appt.summary.c_str(), nullptr); + 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.c_str()); + + if (appt.has_alarms) + { + g_menu_item_set_attribute (menu_item, "x-canonical-type", "s", "com.canonical.indicator.alarm"); + g_menu_item_set_attribute_value(menu_item, G_MENU_ATTRIBUTE_ICON, get_serialized_alarm_icon()); + } + else + { + g_menu_item_set_attribute (menu_item, "x-canonical-type", "s", "com.canonical.indicator.appointment"); + + if (!appt.color.empty()) + g_menu_item_set_attribute (menu_item, "x-canonical-color", "s", appt.color.c_str()); + } + + if (profile == Phone) + g_menu_item_set_action_and_target_value (menu_item, + "indicator.activate-appointment", + g_variant_new_string (appt.uid.c_str())); + else + 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); + } + } + + GMenuModel* create_appointments_section(Profile profile) + { + auto menu = g_menu_new(); + + if (((profile==Phone) || (profile==Desktop)) && m_state->settings->show_events.get()) + { + add_appointments (menu, profile); + + // add the 'Add Event…' menuitem + auto menu_item = g_menu_item_new(_("Add Event…"), nullptr); + const gchar* action_name = "indicator.activate_planner"; + auto v = g_variant_new_int64(0); + g_menu_item_set_action_and_target_value(menu_item, action_name, v); + g_menu_append_item(menu, menu_item); + g_object_unref(menu_item); + } + + return G_MENU_MODEL(menu); + } + + GMenuModel* create_locations_section(Profile profile) + { + GMenu* menu = g_menu_new(); + + if (profile == Desktop) + { + const auto now = m_state->clock->localtime(); + + for(const auto& location : m_state->locations->locations.get()) + { + const auto& zone = location.zone(); + const auto& name = location.name(); + const auto zone_now = now.to_timezone(zone); + const auto fmt = m_formatter->getRelativeFormat(zone_now.get()); + auto detailed_action = g_strdup_printf("indicator.set-location::%s %s", zone.c_str(), name.c_str()); + auto i = g_menu_item_new (name.c_str(), detailed_action); + g_menu_item_set_attribute(i, "x-canonical-type", "s", "com.canonical.indicator.location"); + g_menu_item_set_attribute(i, "x-canonical-timezone", "s", zone.c_str()); + g_menu_item_set_attribute(i, "x-canonical-time-format", "s", fmt.c_str()); + g_menu_append_item (menu, i); + g_object_unref(i); + g_free(detailed_action); + } + } + + return G_MENU_MODEL(menu); + } + + GMenuModel* create_settings_section(Profile profile) + { + auto menu = g_menu_new(); + + if (profile == Desktop) + { + g_menu_append (menu, _("Date & Time Settings…"), "indicator.activate-desktop-settings"); + } + else if (profile == Phone) + { + g_menu_append (menu, _("Time & Date settings…"), "indicator.activate-phone-settings"); + } + + return G_MENU_MODEL (menu); + } + + void update_section(Section section) + { + GMenuModel * model; + const auto p = profile(); + + switch (section) + { + case Calendar: model = create_calendar_section(p); break; + case Appointments: model = create_appointments_section(p); break; + case Locations: model = create_locations_section(p); break; + case Settings: model = create_settings_section(p); break; + default: g_warn_if_reached(); + } + + if (model) + { + g_menu_remove(m_submenu, section); + g_menu_insert_section(m_submenu, section, nullptr, model); + g_object_unref(model); + } + } + +//private: + GVariant * m_serialized_alarm_icon = nullptr; + GVariant * m_serialized_calendar_icon = nullptr; + +}; // class MenuImpl + + + +/*** +**** +***/ + +class DesktopBaseMenu: public MenuImpl +{ +protected: + DesktopBaseMenu(Menu::Profile profile_, + const std::string& name_, + std::shared_ptr<State>& state_, + std::shared_ptr<Actions>& actions_): + MenuImpl(profile_, name_, state_, actions_, + std::shared_ptr<Formatter>(new DesktopFormatter(state_->clock, state_->settings))) + { + update_header(); + } + + GVariant* create_header_state() + { + const auto visible = m_state->settings->show_clock.get(); + const auto title = _("Date and Time"); + auto label = g_variant_new_string(m_formatter->header.get().c_str()); + + GVariantBuilder b; + g_variant_builder_init(&b, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&b, "{sv}", "accessible-desc", label); + g_variant_builder_add(&b, "{sv}", "label", label); + g_variant_builder_add(&b, "{sv}", "title", g_variant_new_string(title)); + g_variant_builder_add(&b, "{sv}", "visible", g_variant_new_boolean(visible)); + return g_variant_builder_end(&b); + } +}; + +class DesktopMenu: public DesktopBaseMenu +{ +public: + DesktopMenu(std::shared_ptr<State>& state_, std::shared_ptr<Actions>& actions_): + DesktopBaseMenu(Desktop,"desktop", state_, actions_) {} +}; + +class DesktopGreeterMenu: public DesktopBaseMenu +{ +public: + DesktopGreeterMenu(std::shared_ptr<State>& state_, std::shared_ptr<Actions>& actions_): + DesktopBaseMenu(DesktopGreeter,"desktop_greeter", state_, actions_) {} +}; + +class PhoneBaseMenu: public MenuImpl +{ +protected: + PhoneBaseMenu(Menu::Profile profile_, + const std::string& name_, + std::shared_ptr<State>& state_, + std::shared_ptr<Actions>& actions_): + MenuImpl(profile_, name_, state_, actions_, + std::shared_ptr<Formatter>(new PhoneFormatter(state_->clock))) + { + update_header(); + } + + GVariant* create_header_state() + { + // are there alarms? + bool has_alarms = false; + for(const auto& appointment : m_state->planner->upcoming.get()) + if((has_alarms = appointment.has_alarms)) + break; + + GVariantBuilder b; + g_variant_builder_init(&b, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&b, "{sv}", "title", g_variant_new_string (_("Upcoming"))); + g_variant_builder_add(&b, "{sv}", "visible", g_variant_new_boolean (TRUE)); + if (has_alarms) + { + auto label = m_formatter->header.get(); + auto a11y = g_strdup_printf(_("%s (has alarms)"), label.c_str()); + g_variant_builder_add(&b, "{sv}", "label", g_variant_new_string(label.c_str())); + g_variant_builder_add(&b, "{sv}", "accessible-desc", g_variant_new_take_string(a11y)); + g_variant_builder_add(&b, "{sv}", "icon", get_serialized_alarm_icon()); + } + else + { + auto v = g_variant_new_string(m_formatter->header.get().c_str()); + g_variant_builder_add(&b, "{sv}", "label", v); + g_variant_builder_add(&b, "{sv}", "accessible-desc", v); + } + return g_variant_builder_end (&b); + } +}; + +class PhoneMenu: public PhoneBaseMenu +{ +public: + PhoneMenu(std::shared_ptr<State>& state_, + std::shared_ptr<Actions>& actions_): + PhoneBaseMenu(Phone, "phone", state_, actions_) {} +}; + +class PhoneGreeterMenu: public PhoneBaseMenu +{ +public: + PhoneGreeterMenu(std::shared_ptr<State>& state_, + std::shared_ptr<Actions>& actions_): + PhoneBaseMenu(PhoneGreeter, "phone_greeter", state_, actions_) {} +}; + +/**** +***** +****/ + +MenuFactory::MenuFactory(std::shared_ptr<Actions>& actions_, + std::shared_ptr<State>& state_): + m_actions(actions_), + m_state(state_) +{ +} + +std::shared_ptr<Menu> +MenuFactory::buildMenu(Menu::Profile profile) +{ + std::shared_ptr<Menu> menu; + + switch (profile) + { + case Menu::Desktop: + menu.reset(new DesktopMenu(m_state, m_actions)); + break; + + case Menu::DesktopGreeter: + menu.reset(new DesktopGreeterMenu(m_state, m_actions)); + break; + + case Menu::Phone: + menu.reset(new PhoneMenu(m_state, m_actions)); + break; + + case Menu::PhoneGreeter: + menu.reset(new PhoneGreeterMenu(m_state, m_actions)); + break; + + default: + g_warn_if_reached(); + break; + } + + return menu; +} + +/**** +***** +****/ + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/planner-eds.c b/src/planner-eds.c deleted file mode 100644 index cc2b8c5..0000000 --- a/src/planner-eds.c +++ /dev/null @@ -1,653 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * Authors: - * Charles Kerr <charles.kerr@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 <libical/ical.h> -#include <libical/icaltime.h> -#include <libecal/libecal.h> -#include <libedataserver/libedataserver.h> - -#include "planner-eds.h" - -struct _IndicatorDatetimePlannerEdsPriv -{ - GSList * sources; - GCancellable * cancellable; - ESourceRegistry * source_registry; -}; - -typedef IndicatorDatetimePlannerEdsPriv priv_t; - -G_DEFINE_TYPE (IndicatorDatetimePlannerEds, - indicator_datetime_planner_eds, - INDICATOR_TYPE_DATETIME_PLANNER) - -G_DEFINE_QUARK ("source-client", source_client) - -/*** -**** -**** my_get_appointments() helpers -**** -***/ - -/* whole-task data that all the subtasks can see */ -struct appointment_task_data -{ - /* a ref to the planner's cancellable */ - GCancellable * cancellable; - - /* how many subtasks are still running on */ - int subtask_count; - - /* the list of appointments to be returned */ - GSList * appointments; -}; - -static struct appointment_task_data * -appointment_task_data_new (GCancellable * cancellable) -{ - struct appointment_task_data * data; - - data = g_slice_new0 (struct appointment_task_data); - data->cancellable = g_object_ref (cancellable); - return data; -} - -static void -appointment_task_data_free (gpointer gdata) -{ - struct appointment_task_data * data = gdata; - - g_object_unref (data->cancellable); - - g_slice_free (struct appointment_task_data, data); -} - -static void -appointment_task_done (GTask * task) -{ - struct appointment_task_data * data = g_task_get_task_data (task); - - g_task_return_pointer (task, data->appointments, NULL); - g_object_unref (task); -} - -static void -appointment_task_decrement_subtasks (GTask * task) -{ - struct appointment_task_data * data = g_task_get_task_data (task); - - if (g_atomic_int_dec_and_test (&data->subtask_count)) - appointment_task_done (task); -} - -static void -appointment_task_increment_subtasks (GTask * task) -{ - struct appointment_task_data * data = g_task_get_task_data (task); - - g_atomic_int_inc (&data->subtask_count); -} - -/** -*** get-the-appointment's-uri subtasks -**/ - -struct appointment_uri_subtask_data -{ - /* The parent task */ - GTask * task; - - /* The appointment whose uri we're looking for. - This pointer is owned by the Task and isn't reffed/unreffed by the subtask */ - struct IndicatorDatetimeAppt * appt; -}; - -static void -appointment_uri_subtask_done (struct appointment_uri_subtask_data * subdata) -{ - GTask * task = subdata->task; - - /* free the subtask data */ - g_slice_free (struct appointment_uri_subtask_data, subdata); - - appointment_task_decrement_subtasks (task); -} - -static struct appointment_uri_subtask_data * -appointment_uri_subtask_data_new (GTask * task, struct IndicatorDatetimeAppt * appt) -{ - struct appointment_uri_subtask_data * subdata; - - appointment_task_increment_subtasks (task); - - subdata = g_slice_new0 (struct appointment_uri_subtask_data); - subdata->task = task; - subdata->appt = appt; - return subdata; -} - -static void -on_appointment_uris_ready (GObject * client, - GAsyncResult * res, - gpointer gsubdata) -{ - GSList * uris; - GError * error; - struct appointment_uri_subtask_data * subdata = gsubdata; - - uris = NULL; - error = NULL; - e_cal_client_get_attachment_uris_finish (E_CAL_CLIENT(client), res, &uris, &error); - if (error != NULL) - { - if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - g_warning ("Error getting appointment uris: %s", error->message); - - g_error_free (error); - } - else if (uris != NULL) - { - struct IndicatorDatetimeAppt * appt = subdata->appt; - appt->url = g_strdup (uris->data); /* copy the first URL */ - g_debug ("found url '%s' for appointment '%s'", appt->url, appt->summary); - e_client_util_free_string_slist (uris); - } - - appointment_uri_subtask_done (subdata); -} - -/** -*** enumerate-the-components subtasks -**/ - -/* data struct for the enumerate-components subtask */ -struct appointment_component_subtask_data -{ - /* The parent task */ - GTask * task; - - /* The client we're walking through. The subtask owns a ref to this */ - ECalClient * client; - - /* The appointment's color coding. The subtask owns this string */ - gchar * color; -}; - -static void -on_appointment_component_subtask_done (gpointer gsubdata) -{ - struct appointment_component_subtask_data * subdata = gsubdata; - GTask * task = subdata->task; - - /* free the subtask data */ - g_free (subdata->color); - g_object_unref (subdata->client); - g_slice_free (struct appointment_component_subtask_data, subdata); - - appointment_task_decrement_subtasks (task); -} - -static struct appointment_component_subtask_data * -appointment_component_subtask_data_new (GTask * task, ECalClient * client, const gchar * color) -{ - struct appointment_component_subtask_data * subdata; - - appointment_task_increment_subtasks (task); - - subdata = g_slice_new0 (struct appointment_component_subtask_data); - subdata->task = task; - subdata->client = g_object_ref (client); - subdata->color = g_strdup (color); - return subdata; -} - -static gboolean -my_get_appointments_foreach (ECalComponent * component, - time_t begin, - time_t end, - gpointer gsubdata) -{ - const ECalComponentVType vtype = e_cal_component_get_vtype (component); - struct appointment_component_subtask_data * subdata = gsubdata; - struct appointment_task_data * data = g_task_get_task_data (subdata->task); - - if ((vtype == E_CAL_COMPONENT_EVENT) || (vtype == E_CAL_COMPONENT_TODO)) - { - const gchar * uid = NULL; - icalproperty_status status = 0; - - e_cal_component_get_uid (component, &uid); - e_cal_component_get_status (component, &status); - - if ((uid != NULL) && - (status != ICAL_STATUS_COMPLETED) && - (status != ICAL_STATUS_CANCELLED)) - { - GList * alarm_uids; - GSList * l; - GSList * recur_list; - ECalComponentText text; - struct IndicatorDatetimeAppt * appt; - struct appointment_uri_subtask_data * uri_subdata; - - appt = g_slice_new0 (struct IndicatorDatetimeAppt); - - /* Determine whether this is a recurring event. - NB: icalrecurrencetype supports complex recurrence patterns; - however, since design only allows daily recurrence, - that's all we support here. */ - e_cal_component_get_rrule_list (component, &recur_list); - for (l=recur_list; l!=NULL; l=l->next) - { - const struct icalrecurrencetype * recur = l->data; - appt->is_daily |= ((recur->freq == ICAL_DAILY_RECURRENCE) - && (recur->interval == 1)); - } - e_cal_component_free_recur_list (recur_list); - - text.value = ""; - e_cal_component_get_summary (component, &text); - - appt->begin = g_date_time_new_from_unix_local (begin); - appt->end = g_date_time_new_from_unix_local (end); - appt->color = g_strdup (subdata->color); - appt->is_event = vtype == E_CAL_COMPONENT_EVENT; - appt->summary = g_strdup (text.value); - appt->uid = g_strdup (uid); - - alarm_uids = e_cal_component_get_alarm_uids (component); - appt->has_alarms = alarm_uids != NULL; - cal_obj_uid_list_free (alarm_uids); - - data->appointments = g_slist_prepend (data->appointments, appt); - - /* start a new subtask to get the associated URIs */ - uri_subdata = appointment_uri_subtask_data_new (subdata->task, appt); - e_cal_client_get_attachment_uris (subdata->client, - uid, - NULL, - data->cancellable, - on_appointment_uris_ready, - uri_subdata); - } - } - - return G_SOURCE_CONTINUE; -} - -/*** -**** IndicatorDatetimePlanner virtual funcs -***/ - -static void -my_get_appointments (IndicatorDatetimePlanner * planner, - GDateTime * begin_datetime, - GDateTime * end_datetime, - GAsyncReadyCallback callback, - gpointer user_data) -{ - GSList * l; - priv_t * p; - const char * str; - icaltimezone * default_timezone; - const int64_t begin = g_date_time_to_unix (begin_datetime); - const int64_t end = g_date_time_to_unix (end_datetime); - GTask * task; - gboolean subtasks_added; - - p = INDICATOR_DATETIME_PLANNER_EDS (planner)->priv; - - /** - *** init the default timezone - **/ - - default_timezone = NULL; - - if ((str = indicator_datetime_planner_get_timezone (planner))) - { - default_timezone = icaltimezone_get_builtin_timezone (str); - - if (default_timezone == NULL) /* maybe str is a tzid? */ - default_timezone = icaltimezone_get_builtin_timezone_from_tzid (str); - } - - /** - *** walk through the sources to build the appointment list - **/ - - task = g_task_new (planner, p->cancellable, callback, user_data); - g_task_set_task_data (task, - appointment_task_data_new (p->cancellable), - appointment_task_data_free); - - subtasks_added = FALSE; - for (l=p->sources; l!=NULL; l=l->next) - { - ESource * source; - ECalClient * client; - const char * color; - struct appointment_component_subtask_data * subdata; - - source = l->data; - client = g_object_get_qdata (l->data, source_client_quark()); - if (client == NULL) - continue; - - if (default_timezone != NULL) - e_cal_client_set_default_timezone (client, default_timezone); - - /* start a new subtask to enumerate all the components in this client. */ - color = e_source_selectable_get_color (e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR)); - subdata = appointment_component_subtask_data_new (task, client, color); - subtasks_added = TRUE; - e_cal_client_generate_instances (client, - begin, - end, - p->cancellable, - my_get_appointments_foreach, - subdata, - on_appointment_component_subtask_done); - } - - if (!subtasks_added) - appointment_task_done (task); -} - -static GSList * -my_get_appointments_finish (IndicatorDatetimePlanner * self G_GNUC_UNUSED, - GAsyncResult * res, - GError ** error) -{ - return g_task_propagate_pointer (G_TASK(res), error); -} - -static gboolean -my_is_configured (IndicatorDatetimePlanner * planner) -{ - IndicatorDatetimePlannerEds * self; - - /* confirm that it's installed... */ - gchar *evo = g_find_program_in_path ("evolution"); - if (evo == NULL) - return FALSE; - - g_debug ("found calendar app: '%s'", evo); - g_free (evo); - - /* see if there are any calendar sources */ - self = INDICATOR_DATETIME_PLANNER_EDS (planner); - return self->priv->sources != NULL; -} - -static void -my_activate (IndicatorDatetimePlanner * self G_GNUC_UNUSED) -{ - GError * error = NULL; - const char * const command = "evolution -c calendar"; - - if (!g_spawn_command_line_async (command, &error)) - { - g_warning ("Unable to start %s: %s", command, error->message); - g_error_free (error); - } -} - -static void -my_activate_time (IndicatorDatetimePlanner * self G_GNUC_UNUSED, - GDateTime * activate_time) -{ - gchar * isodate; - gchar * command; - GError * err; - - isodate = g_date_time_format (activate_time, "%Y%m%d"); - command = g_strdup_printf ("evolution \"calendar:///?startdate=%s\"", isodate); - err = 0; - if (!g_spawn_command_line_async (command, &err)) - { - g_warning ("Unable to start %s: %s", command, err->message); - g_error_free (err); - } - - g_free (command); - g_free (isodate); -} - -/*** -**** Source / Client Wrangling -***/ - -static void -on_client_connected (GObject * unused G_GNUC_UNUSED, - GAsyncResult * res, - gpointer gself) -{ - GError * error; - EClient * client; - - error = NULL; - client = e_cal_client_connect_finish (res, &error); - if (error != NULL) - { - if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - g_warning ("indicator-datetime cannot connect to EDS source: %s", error->message); - - g_error_free (error); - } - else - { - /* we've got a new connected ECalClient, so store it & notify clients */ - - g_object_set_qdata_full (G_OBJECT(e_client_get_source(client)), - source_client_quark(), - client, - g_object_unref); - - indicator_datetime_planner_emit_appointments_changed (gself); - } -} - -static void -on_source_enabled (ESourceRegistry * registry G_GNUC_UNUSED, - ESource * source, - gpointer gself) -{ - IndicatorDatetimePlannerEds * self = INDICATOR_DATETIME_PLANNER_EDS (gself); - priv_t * p = self->priv; - - e_cal_client_connect (source, - E_CAL_CLIENT_SOURCE_TYPE_EVENTS, - p->cancellable, - on_client_connected, - self); -} - -static void -on_source_added (ESourceRegistry * registry, - ESource * source, - gpointer gself) -{ - IndicatorDatetimePlannerEds * self = INDICATOR_DATETIME_PLANNER_EDS (gself); - priv_t * p = self->priv; - - p->sources = g_slist_prepend (p->sources, g_object_ref(source)); - - if (e_source_get_enabled (source)) - on_source_enabled (registry, source, gself); -} - -static void -on_source_disabled (ESourceRegistry * registry G_GNUC_UNUSED, - ESource * source, - gpointer gself) -{ - ECalClient * client; - - /* If this source has a connected ECalClient, remove it & notify clients */ - if ((client = g_object_steal_qdata (G_OBJECT(source), source_client_quark()))) - { - g_object_unref (client); - indicator_datetime_planner_emit_appointments_changed (gself); - } -} - -static void -on_source_removed (ESourceRegistry * registry, - ESource * source, - gpointer gself) -{ - IndicatorDatetimePlannerEds * self = INDICATOR_DATETIME_PLANNER_EDS (gself); - priv_t * p = self->priv; - - on_source_disabled (registry, source, gself); - - p->sources = g_slist_remove (p->sources, source); - g_object_unref (source); -} - -static void -on_source_changed (ESourceRegistry * registry G_GNUC_UNUSED, - ESource * source G_GNUC_UNUSED, - gpointer gself) -{ - indicator_datetime_planner_emit_appointments_changed (gself); -} - -static void -on_source_registry_ready (GObject * source_object G_GNUC_UNUSED, - GAsyncResult * res, - gpointer gself) -{ - GError * error; - ESourceRegistry * r; - - error = NULL; - r = e_source_registry_new_finish (res, &error); - if (error != NULL) - { - if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - g_warning ("indicator-datetime cannot show EDS appointments: %s", error->message); - - g_error_free (error); - } - else - { - IndicatorDatetimePlannerEds * self; - priv_t * p; - GList * l; - GList * sources; - - self = INDICATOR_DATETIME_PLANNER_EDS (gself); - p = self->priv; - - g_signal_connect (r, "source-added", G_CALLBACK(on_source_added), self); - g_signal_connect (r, "source-removed", G_CALLBACK(on_source_removed), self); - g_signal_connect (r, "source-changed", G_CALLBACK(on_source_changed), self); - g_signal_connect (r, "source-disabled", G_CALLBACK(on_source_disabled), self); - g_signal_connect (r, "source-enabled", G_CALLBACK(on_source_enabled), self); - - p->source_registry = r; - - sources = e_source_registry_list_sources (r, E_SOURCE_EXTENSION_CALENDAR); - for (l=sources; l!=NULL; l=l->next) - on_source_added (r, l->data, self); - g_list_free_full (sources, g_object_unref); - } -} - -/*** -**** GObject virtual funcs -***/ - -static void -my_dispose (GObject * o) -{ - IndicatorDatetimePlannerEds * self = INDICATOR_DATETIME_PLANNER_EDS (o); - priv_t * p = self->priv; - - if (p->cancellable != NULL) - { - g_cancellable_cancel (p->cancellable); - g_clear_object (&p->cancellable); - } - - if (p->source_registry != NULL) - { - g_signal_handlers_disconnect_by_func (p->source_registry, - indicator_datetime_planner_emit_appointments_changed, - self); - - g_clear_object (&self->priv->source_registry); - } - - G_OBJECT_CLASS (indicator_datetime_planner_eds_parent_class)->dispose (o); -} - -/*** -**** Instantiation -***/ - -static void -indicator_datetime_planner_eds_class_init (IndicatorDatetimePlannerEdsClass * klass) -{ - GObjectClass * object_class; - IndicatorDatetimePlannerClass * planner_class; - - object_class = G_OBJECT_CLASS (klass); - object_class->dispose = my_dispose; - - planner_class = INDICATOR_DATETIME_PLANNER_CLASS (klass); - planner_class->is_configured = my_is_configured; - planner_class->activate = my_activate; - planner_class->activate_time = my_activate_time; - planner_class->get_appointments = my_get_appointments; - planner_class->get_appointments_finish = my_get_appointments_finish; - - g_type_class_add_private (klass, sizeof (IndicatorDatetimePlannerEdsPriv)); -} - -static void -indicator_datetime_planner_eds_init (IndicatorDatetimePlannerEds * self) -{ - priv_t * p; - - p = G_TYPE_INSTANCE_GET_PRIVATE (self, - INDICATOR_TYPE_DATETIME_PLANNER_EDS, - IndicatorDatetimePlannerEdsPriv); - - self->priv = p; - - p->cancellable = g_cancellable_new (); - - e_source_registry_new (p->cancellable, - on_source_registry_ready, - self); -} - -/*** -**** Public -***/ - -IndicatorDatetimePlanner * -indicator_datetime_planner_eds_new (void) -{ - gpointer o = g_object_new (INDICATOR_TYPE_DATETIME_PLANNER_EDS, NULL); - - return INDICATOR_DATETIME_PLANNER (o); -} diff --git a/src/planner-eds.cpp b/src/planner-eds.cpp new file mode 100644 index 0000000..54332ce --- /dev/null +++ b/src/planner-eds.cpp @@ -0,0 +1,421 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * 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/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <datetime/planner-eds.h> + +#include <datetime/appointment.h> + +#include <libical/ical.h> +#include <libical/icaltime.h> +#include <libecal/libecal.h> +#include <libedataserver/libedataserver.h> + +namespace unity { +namespace indicator { +namespace datetime { + +/**** +***** +****/ + +G_DEFINE_QUARK("source-client", source_client) + + +class PlannerEds::Impl +{ +public: + + Impl(PlannerEds& owner): + m_owner(owner), + m_cancellable(g_cancellable_new()) + { + e_source_registry_new(m_cancellable, on_source_registry_ready, this); + + m_owner.time.changed().connect([this](const DateTime& dt) { + g_debug("planner's datetime property changed to %s; calling rebuildSoon()", dt.format("%F %T").c_str()); + rebuildSoon(); + }); + + rebuildSoon(); + } + + ~Impl() + { + g_cancellable_cancel(m_cancellable); + g_clear_object(&m_cancellable); + + if (m_rebuild_tag) + g_source_remove(m_rebuild_tag); + + if (m_source_registry) + g_signal_handlers_disconnect_by_data(m_source_registry, this); + g_clear_object(&m_source_registry); + } + +private: + + static void on_source_registry_ready(GObject* /*source*/, GAsyncResult* res, gpointer gself) + { + GError * error = nullptr; + auto r = e_source_registry_new_finish(res, &error); + if (error != nullptr) + { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("indicator-datetime cannot show EDS appointments: %s", error->message); + + g_error_free(error); + } + else + { + auto self = static_cast<Impl*>(gself); + + g_signal_connect(r, "source-added", G_CALLBACK(on_source_added), self); + g_signal_connect(r, "source-removed", G_CALLBACK(on_source_removed), self); + g_signal_connect(r, "source-changed", G_CALLBACK(on_source_changed), self); + g_signal_connect(r, "source-disabled", G_CALLBACK(on_source_disabled), self); + g_signal_connect(r, "source-enabled", G_CALLBACK(on_source_enabled), self); + + self->m_source_registry = r; + + GList* sources = e_source_registry_list_sources(r, E_SOURCE_EXTENSION_CALENDAR); + for (auto l=sources; l!=nullptr; l=l->next) + on_source_added(r, E_SOURCE(l->data), gself); + g_list_free_full(sources, g_object_unref); + } + } + + static void on_source_added(ESourceRegistry* registry, ESource* source, gpointer gself) + { + auto self = static_cast<PlannerEds::Impl*>(gself); + + self->m_sources.insert(E_SOURCE(g_object_ref(source))); + + if (e_source_get_enabled(source)) + on_source_enabled(registry, source, gself); + } + + static void on_source_enabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself) + { + auto self = static_cast<PlannerEds::Impl*>(gself); + + e_cal_client_connect(source, + E_CAL_CLIENT_SOURCE_TYPE_EVENTS, + self->m_cancellable, + on_client_connected, + gself); + } + + static void on_client_connected(GObject* /*source*/, GAsyncResult * res, gpointer gself) + { + GError * error = nullptr; + EClient * client = e_cal_client_connect_finish(res, &error); + if (error) + { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("indicator-datetime cannot connect to EDS source: %s", error->message); + + g_error_free(error); + } + else + { + // we've got a new connected ECalClient, so store it & notify clients + g_object_set_qdata_full(G_OBJECT(e_client_get_source(client)), + source_client_quark(), + client, + g_object_unref); + + g_debug("client connected; calling rebuildSoon()"); + static_cast<Impl*>(gself)->rebuildSoon(); + } + } + + static void on_source_disabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself) + { + gpointer e_cal_client; + + // if this source has a connected ECalClient, remove it & notify clients + if ((e_cal_client = g_object_steal_qdata(G_OBJECT(source), source_client_quark()))) + { + g_object_unref(e_cal_client); + + g_debug("source disabled; calling rebuildSoon()"); + static_cast<Impl*>(gself)->rebuildSoon(); + } + } + + static void on_source_removed(ESourceRegistry* registry, ESource* source, gpointer gself) + { + auto self = static_cast<PlannerEds::Impl*>(gself); + + on_source_disabled(registry, source, gself); + + self->m_sources.erase(source); + g_object_unref(source); + } + + static void on_source_changed(ESourceRegistry* /*registry*/, ESource* /*source*/, gpointer gself) + { + g_debug("source changed; calling rebuildSoon()"); + static_cast<Impl*>(gself)->rebuildSoon(); + } + +private: + + typedef std::function<void(const std::vector<Appointment>&)> appointment_func; + + struct Task + { + Impl* p; + appointment_func func; + std::vector<Appointment> appointments; + Task(Impl* p_in, const appointment_func& func_in): p(p_in), func(func_in) {} + }; + + struct AppointmentSubtask + { + std::shared_ptr<Task> task; + ECalClient* client; + std::string color; + AppointmentSubtask(const std::shared_ptr<Task>& task_in, ECalClient* client_in, const char* color_in): + task(task_in), client(client_in), color(color_in) {} + }; + + void rebuildSoon() + { + const static guint ARBITRARY_INTERVAL_SECS = 2; + + if (m_rebuild_tag == 0) + m_rebuild_tag = g_timeout_add_seconds(ARBITRARY_INTERVAL_SECS, rebuildNowStatic, this); + } + + static gboolean rebuildNowStatic(gpointer gself) + { + auto self = static_cast<Impl*>(gself); + self->m_rebuild_tag = 0; + self->rebuildNow(); + return G_SOURCE_REMOVE; + } + + void rebuildNow() + { + auto calendar_date = m_owner.time.get().get(); + GDateTime* begin; + GDateTime* end; + int y, m, d; + + // get all the appointments in the calendar month + g_date_time_get_ymd(calendar_date, &y, &m, &d); + begin = g_date_time_new_local(y, m, 1, 0, 0, 0.1); + end = g_date_time_new_local(y, m, g_date_get_days_in_month(GDateMonth(m),GDateYear(y)), 23, 59, 59.9); + if (begin && end) + { + getAppointments(begin, end, [this](const std::vector<Appointment>& appointments) { + g_debug("got %d appointments in this calendar month", (int)appointments.size()); + m_owner.thisMonth.set(appointments); + }); + } + g_clear_pointer(&begin, g_date_time_unref); + g_clear_pointer(&end, g_date_time_unref); + + // get the upcoming appointments + begin = g_date_time_ref(calendar_date); + end = g_date_time_add_months(begin, 1); + if (begin && end) + { + getAppointments(begin, end, [this](const std::vector<Appointment>& appointments) { + g_debug("got %d upcoming appointments", (int)appointments.size()); + m_owner.upcoming.set(appointments); + }); + } + g_clear_pointer(&begin, g_date_time_unref); + g_clear_pointer(&end, g_date_time_unref); + g_clear_pointer(&calendar_date, g_date_time_unref); + } + + void getAppointments(GDateTime* begin_dt, GDateTime* end_dt, appointment_func func) + { + const auto begin = g_date_time_to_unix(begin_dt); + const auto end = g_date_time_to_unix(end_dt); + g_debug("getting all appointments from [%s ... %s]", g_date_time_format(begin_dt, "%F %T"), + g_date_time_format(end_dt, "%F %T")); + + /** + *** init the default timezone + **/ + + icaltimezone * default_timezone = nullptr; + + const auto tz = g_date_time_get_timezone_abbreviation(m_owner.time.get().get()); + g_debug("%s tz is %s", G_STRLOC, tz); + if (tz && *tz) + { + default_timezone = icaltimezone_get_builtin_timezone(tz); + + if (default_timezone == nullptr) // maybe str is a tzid? + default_timezone = icaltimezone_get_builtin_timezone_from_tzid(tz); + + g_debug("default_timezone is %p", (void*)default_timezone); + } + + /** + *** walk through the sources to build the appointment list + **/ + + std::shared_ptr<Task> main_task(new Task(this, func), [](Task* task){ + g_debug("time to delete task %p", (void*)task); + task->func(task->appointments); + }); + + for (auto& source : m_sources) + { + auto client = E_CAL_CLIENT(g_object_get_qdata(G_OBJECT(source), source_client_quark())); + if (client == nullptr) + continue; + + if (default_timezone != nullptr) + e_cal_client_set_default_timezone(client, default_timezone); + + // start a new subtask to enumerate all the components in this client. + auto extension = e_source_get_extension(source, E_SOURCE_EXTENSION_CALENDAR); + const auto color = e_source_selectable_get_color(E_SOURCE_SELECTABLE(extension)); + g_debug("calling e_cal_client_generate_instances for %p", (void*)client); + e_cal_client_generate_instances(client, + begin, + end, + m_cancellable, + my_get_appointments_foreach, + new AppointmentSubtask (main_task, client, color), + [](gpointer g){delete static_cast<AppointmentSubtask*>(g);}); + } + } + + struct UrlSubtask + { + std::shared_ptr<Task> task; + Appointment appointment; + UrlSubtask(const std::shared_ptr<Task>& task_in, const Appointment& appointment_in): + task(task_in), appointment(appointment_in) {} + }; + + static gboolean + my_get_appointments_foreach(ECalComponent* component, + time_t begin, + time_t end, + gpointer gsubtask) + { + const auto vtype = e_cal_component_get_vtype(component); + auto subtask = static_cast<AppointmentSubtask*>(gsubtask); + + if ((vtype == E_CAL_COMPONENT_EVENT) || (vtype == E_CAL_COMPONENT_TODO)) + { + const gchar* uid = nullptr; + e_cal_component_get_uid(component, &uid); + + auto status = ICAL_STATUS_NONE; + e_cal_component_get_status(component, &status); + + if ((uid != nullptr) && + (status != ICAL_STATUS_COMPLETED) && + (status != ICAL_STATUS_CANCELLED)) + { + Appointment appointment; + + /* Determine whether this is a recurring event. + NB: icalrecurrencetype supports complex recurrence patterns; + however, since design only allows daily recurrence, + that's all we support here. */ + GSList * recur_list; + e_cal_component_get_rrule_list(component, &recur_list); + for (auto l=recur_list; l!=nullptr; l=l->next) + { + const auto recur = static_cast<struct icalrecurrencetype*>(l->data); + appointment.is_daily |= ((recur->freq == ICAL_DAILY_RECURRENCE) + && (recur->interval == 1)); + } + e_cal_component_free_recur_list(recur_list); + + ECalComponentText text; + text.value = ""; + e_cal_component_get_summary(component, &text); + + appointment.begin = g_date_time_new_from_unix_local(begin); + appointment.end = g_date_time_new_from_unix_local(end); + appointment.color = subtask->color; + appointment.is_event = vtype == E_CAL_COMPONENT_EVENT; + appointment.summary = text.value; + appointment.uid = uid; + + GList * alarm_uids = e_cal_component_get_alarm_uids(component); + appointment.has_alarms = alarm_uids != nullptr; + cal_obj_uid_list_free(alarm_uids); + + e_cal_client_get_attachment_uris(subtask->client, + uid, + nullptr, + subtask->task->p->m_cancellable, + on_appointment_uris_ready, + new UrlSubtask(subtask->task, appointment)); + } + } + + return G_SOURCE_CONTINUE; + } + + static void on_appointment_uris_ready(GObject* client, GAsyncResult* res, gpointer gsubtask) + { + auto subtask = static_cast<UrlSubtask*>(gsubtask); + + GSList * uris = nullptr; + GError * error = nullptr; + e_cal_client_get_attachment_uris_finish(E_CAL_CLIENT(client), res, &uris, &error); + if (error != nullptr) + { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("Error getting appointment uris: %s", error->message); + + g_error_free(error); + } + else if (uris != nullptr) + { + subtask->appointment.url = (const char*) uris->data; // copy the first URL + g_debug("found url '%s' for appointment '%s'", subtask->appointment.url.c_str(), subtask->appointment.summary.c_str()); + e_client_util_free_string_slist(uris); + } + + g_debug("adding appointment '%s' '%s'", subtask->appointment.summary.c_str(), subtask->appointment.url.c_str()); + subtask->task->appointments.push_back(subtask->appointment); + delete subtask; + } + +private: + + PlannerEds& m_owner; + std::set<ESource*> m_sources; + GCancellable * m_cancellable = nullptr; + ESourceRegistry * m_source_registry = nullptr; + guint m_rebuild_tag = 0; +}; + +PlannerEds::PlannerEds(): p(new Impl(*this)) {} + +PlannerEds::~PlannerEds() =default; + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/planner-eds.h b/src/planner-eds.h deleted file mode 100644 index dea9371..0000000 --- a/src/planner-eds.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * Authors: - * Charles Kerr <charles.kerr@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/>. - */ - -#ifndef __INDICATOR_DATETIME_PLANNER_EDS__H__ -#define __INDICATOR_DATETIME_PLANNER_EDS__H__ - -#include "planner.h" /* parent class */ - -G_BEGIN_DECLS - -#define INDICATOR_TYPE_DATETIME_PLANNER_EDS (indicator_datetime_planner_eds_get_type()) -#define INDICATOR_DATETIME_PLANNER_EDS(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), INDICATOR_TYPE_DATETIME_PLANNER_EDS, IndicatorDatetimePlannerEds)) -#define INDICATOR_DATETIME_PLANNER_EDS_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), INDICATOR_TYPE_DATETIME_PLANNER_EDS, IndicatorDatetimePlannerEdsClass)) -#define INDICATOR_IS_DATETIME_PLANNER_EDS(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), INDICATOR_TYPE_DATETIME_PLANNER_EDS)) - -typedef struct _IndicatorDatetimePlannerEds IndicatorDatetimePlannerEds; -typedef struct _IndicatorDatetimePlannerEdsPriv IndicatorDatetimePlannerEdsPriv; -typedef struct _IndicatorDatetimePlannerEdsClass IndicatorDatetimePlannerEdsClass; - -GType indicator_datetime_planner_eds_get_type (void); - -/** - * An IndicatorDatetimePlanner which uses Evolution Data Server - * to get its list of appointments. - */ -struct _IndicatorDatetimePlannerEds -{ - /*< private >*/ - IndicatorDatetimePlanner parent; - IndicatorDatetimePlannerEdsPriv * priv; -}; - -struct _IndicatorDatetimePlannerEdsClass -{ - IndicatorDatetimePlannerClass parent_class; -}; - -IndicatorDatetimePlanner * indicator_datetime_planner_eds_new (void); - -G_END_DECLS - -#endif /* __INDICATOR_DATETIME_PLANNER_EDS__H__ */ diff --git a/src/planner.c b/src/planner.c deleted file mode 100644 index 9b9a77f..0000000 --- a/src/planner.c +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * Authors: - * Charles Kerr <charles.kerr@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 "planner.h" - -/** -*** Signals Boilerplate -**/ - -enum -{ - SIGNAL_APPTS_CHANGED, - SIGNAL_LAST -}; - -static guint signals[SIGNAL_LAST] = { 0 }; - -/** -*** Properties Boilerplate -**/ - -enum -{ - PROP_0, - PROP_TIMEZONE, - PROP_LAST -}; - -static GParamSpec * properties[PROP_LAST] = { 0 }; - -/** -*** GObject Boilerplate -**/ - -G_DEFINE_TYPE (IndicatorDatetimePlanner, - indicator_datetime_planner, - G_TYPE_OBJECT) - -struct _IndicatorDatetimePlannerPriv -{ - char * timezone; -}; - -/*** -**** GObjectClass virtual funcs -***/ - -static void -my_get_property (GObject * o, - guint property_id, - GValue * value, - GParamSpec * pspec) -{ - IndicatorDatetimePlanner * self = INDICATOR_DATETIME_PLANNER (o); - - switch (property_id) - { - case PROP_TIMEZONE: - g_value_set_string (value, self->priv->timezone); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec); - } -} - -static void -my_set_property (GObject * o, - guint property_id, - const GValue * value, - GParamSpec * pspec) -{ - IndicatorDatetimePlanner * self = INDICATOR_DATETIME_PLANNER (o); - - switch (property_id) - { - case PROP_TIMEZONE: - indicator_datetime_planner_set_timezone (self, g_value_get_string (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec); - } -} - -static void -my_finalize (GObject * o) -{ - IndicatorDatetimePlanner * self = INDICATOR_DATETIME_PLANNER(o); - - g_free (self->priv->timezone); - - G_OBJECT_CLASS (indicator_datetime_planner_parent_class)->finalize (o); -} - -/*** -**** Instantiation -***/ - -static void -indicator_datetime_planner_class_init (IndicatorDatetimePlannerClass * klass) -{ - GObjectClass * object_class; - const GParamFlags flags = G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS; - - g_type_class_add_private (klass, sizeof (IndicatorDatetimePlannerPriv)); - - object_class = G_OBJECT_CLASS (klass); - object_class->finalize = my_finalize; - object_class->get_property = my_get_property; - object_class->set_property = my_set_property; - - klass->get_appointments = NULL; - - signals[SIGNAL_APPTS_CHANGED] = g_signal_new ("appointments-changed", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (IndicatorDatetimePlannerClass, appointments_changed), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); - - /* install properties */ - - properties[PROP_0] = NULL; - - properties[PROP_TIMEZONE] = g_param_spec_string ("timezone", - "Timezone", - "Default timezone for the EDS appointments", - "", - flags); - - g_object_class_install_properties (object_class, PROP_LAST, properties); -} - -static void -indicator_datetime_planner_init (IndicatorDatetimePlanner * self) -{ - self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, - INDICATOR_TYPE_DATETIME_PLANNER, - IndicatorDatetimePlannerPriv); -} - -/*** -**** Public API -***/ - -void -indicator_datetime_planner_emit_appointments_changed (IndicatorDatetimePlanner * self) -{ - g_return_if_fail (INDICATOR_IS_DATETIME_PLANNER (self)); - - g_signal_emit (self, signals[SIGNAL_APPTS_CHANGED], 0, NULL); -} - -static gint -compare_appointments_by_start_time (gconstpointer ga, gconstpointer gb) -{ - const struct IndicatorDatetimeAppt * a = ga; - const struct IndicatorDatetimeAppt * b = gb; - - return g_date_time_compare (a->begin, b->begin); -} - -void -indicator_datetime_planner_get_appointments (IndicatorDatetimePlanner * self, - GDateTime * begin, - GDateTime * end, - GAsyncReadyCallback callback, - gpointer user_data) -{ - IndicatorDatetimePlannerClass * klass; - - g_return_if_fail (INDICATOR_IS_DATETIME_PLANNER (self)); - g_return_val_if_fail (begin != NULL, NULL); - g_return_val_if_fail (end != NULL, NULL); - - klass = INDICATOR_DATETIME_PLANNER_GET_CLASS (self); - g_return_if_fail (klass->get_appointments != NULL); - klass->get_appointments (self, begin, end, callback, user_data); -} - -GSList * -indicator_datetime_planner_get_appointments_finish (IndicatorDatetimePlanner * self, - GAsyncResult * res, - GError ** error) -{ - IndicatorDatetimePlannerClass * klass; - GSList * appointments; - - g_return_val_if_fail (INDICATOR_IS_DATETIME_PLANNER (self), NULL); - - klass = INDICATOR_DATETIME_PLANNER_GET_CLASS (self); - g_return_val_if_fail (klass->get_appointments_finish != NULL, NULL); - appointments = klass->get_appointments_finish (self, res, error); - return g_slist_sort (appointments, compare_appointments_by_start_time); -} - -void -indicator_datetime_planner_free_appointments (GSList * l) -{ - g_slist_free_full (l, (GDestroyNotify)indicator_datetime_appt_free); -} - -gboolean -indicator_datetime_planner_is_configured (IndicatorDatetimePlanner * self) -{ - g_return_val_if_fail (INDICATOR_IS_DATETIME_PLANNER (self), FALSE); - - return INDICATOR_DATETIME_PLANNER_GET_CLASS (self)->is_configured (self); -} - -void -indicator_datetime_planner_activate (IndicatorDatetimePlanner * self) -{ - g_return_if_fail (INDICATOR_IS_DATETIME_PLANNER (self)); - - INDICATOR_DATETIME_PLANNER_GET_CLASS (self)->activate (self); -} - -void -indicator_datetime_planner_activate_time (IndicatorDatetimePlanner * self, GDateTime * time) -{ - g_return_if_fail (INDICATOR_IS_DATETIME_PLANNER (self)); - - INDICATOR_DATETIME_PLANNER_GET_CLASS (self)->activate_time (self, time); -} - -void -indicator_datetime_planner_set_timezone (IndicatorDatetimePlanner * self, const char * timezone) -{ - g_return_if_fail (INDICATOR_IS_DATETIME_PLANNER (self)); - - g_free (self->priv->timezone); - self->priv->timezone = g_strdup (timezone); - g_object_notify_by_pspec (G_OBJECT(self), properties[PROP_TIMEZONE]); -} - -const char * -indicator_datetime_planner_get_timezone (IndicatorDatetimePlanner * self) -{ - g_return_val_if_fail (INDICATOR_IS_DATETIME_PLANNER (self), NULL); - - return self->priv->timezone; -} - -/*** -**** -***/ - -void -indicator_datetime_appt_free (struct IndicatorDatetimeAppt * appt) -{ - if (appt != NULL) - { - g_date_time_unref (appt->end); - g_date_time_unref (appt->begin); - g_free (appt->color); - g_free (appt->summary); - g_free (appt->url); - g_free (appt->uid); - g_slice_free (struct IndicatorDatetimeAppt, appt); - } -} - diff --git a/src/planner.h b/src/planner.h deleted file mode 100644 index ffe8937..0000000 --- a/src/planner.h +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * Authors: - * Charles Kerr <charles.kerr@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/>. - */ - -#ifndef __INDICATOR_DATETIME_PLANNER__H__ -#define __INDICATOR_DATETIME_PLANNER__H__ - -#include <glib.h> -#include <glib-object.h> /* parent class */ -#include <gio/gio.h> - -G_BEGIN_DECLS - -#define INDICATOR_TYPE_DATETIME_PLANNER (indicator_datetime_planner_get_type()) -#define INDICATOR_DATETIME_PLANNER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), INDICATOR_TYPE_DATETIME_PLANNER, IndicatorDatetimePlanner)) -#define INDICATOR_DATETIME_PLANNER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), INDICATOR_TYPE_DATETIME_PLANNER, IndicatorDatetimePlannerClass)) -#define INDICATOR_DATETIME_PLANNER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), INDICATOR_TYPE_DATETIME_PLANNER, IndicatorDatetimePlannerClass)) -#define INDICATOR_IS_DATETIME_PLANNER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), INDICATOR_TYPE_DATETIME_PLANNER)) - -typedef struct _IndicatorDatetimePlanner IndicatorDatetimePlanner; -typedef struct _IndicatorDatetimePlannerPriv IndicatorDatetimePlannerPriv; -typedef struct _IndicatorDatetimePlannerClass IndicatorDatetimePlannerClass; - -GType indicator_datetime_planner_get_type (void); - -struct IndicatorDatetimeAppt -{ - gchar * color; - gchar * summary; - gchar * url; - gchar * uid; - GDateTime * begin; - GDateTime * end; - gboolean is_event; - gboolean is_daily; - gboolean has_alarms; -}; - -/** - * Abstract Base Class for objects that provides appointments and events. - * - * These will be listed in the appointments section of indicator-datetime's menu. - */ -struct _IndicatorDatetimePlanner -{ - /*< private >*/ - GObject parent; - IndicatorDatetimePlannerPriv * priv; -}; - -struct _IndicatorDatetimePlannerClass -{ - GObjectClass parent_class; - - /* signals */ - - void (*appointments_changed) (IndicatorDatetimePlanner * self); - - /* virtual functions */ - - void (*get_appointments) (IndicatorDatetimePlanner * self, - GDateTime * begin, - GDateTime * end, - GAsyncReadyCallback callback, - gpointer user_data); - - GSList* (*get_appointments_finish) (IndicatorDatetimePlanner * self, - GAsyncResult * res, - GError ** error); - - - gboolean (*is_configured) (IndicatorDatetimePlanner * self); - void (*activate) (IndicatorDatetimePlanner * self); - void (*activate_time) (IndicatorDatetimePlanner * self, GDateTime *); -}; - -/*** -**** -***/ - -void indicator_datetime_appt_free (struct IndicatorDatetimeAppt * appt); - -/** - * Get a list of appointments, sorted by start time. - */ -void indicator_datetime_planner_get_appointments (IndicatorDatetimePlanner * self, - GDateTime * begin, - GDateTime * end, - GAsyncReadyCallback callback, - gpointer user_data); - -/** - * Finishes the async call begun with indicator_datetime_planner_get_appointments() - * - * To free the list properly, use indicator_datetime_planner_free_appointments() - * - * Return value: (element-type IndicatorDatetimeAppt) - * (transfer full): - * list of appointments - */ -GSList * indicator_datetime_planner_get_appointments_finish (IndicatorDatetimePlanner * self, - GAsyncResult * res, - GError ** error); - -/** - * Convenience function for freeing a GSList of IndicatorDatetimeAppt. - * - * Equivalent to g_slist_free_full (list, (GDestroyNotify)indicator_datetime_appt_free); - */ -void indicator_datetime_planner_free_appointments (GSList *); - - -/** - * Returns false if the planner's backend is not configured. - * - * This can be used on startup to determine whether or not to use this planner. - */ -gboolean indicator_datetime_planner_is_configured (IndicatorDatetimePlanner * self); - -/** - * Activate this planner. - * - * This is used to activate the planner's backend's event editor. - */ -void indicator_datetime_planner_activate (IndicatorDatetimePlanner * self); - -/** - * Activate this planner. - * - * This is used to activate the planner's backend's event editor, - * with an added hint of the specific time that the user would like to edit. - */ -void indicator_datetime_planner_activate_time (IndicatorDatetimePlanner * self, GDateTime * time); - -/** - * Set the timezone. - * - * This is used as a default timezone if the backend's events don't provide their own. - */ -void indicator_datetime_planner_set_timezone (IndicatorDatetimePlanner * self, const char * timezone); - -const char * indicator_datetime_planner_get_timezone (IndicatorDatetimePlanner * self); - - -/** - * Emits the "appointments-changed" signal. This should only be called by subclasses. - */ -void indicator_datetime_planner_emit_appointments_changed (IndicatorDatetimePlanner * self); - -G_END_DECLS - -#endif /* __INDICATOR_DATETIME_PLANNER__H__ */ diff --git a/src/service.h b/src/service.h deleted file mode 100644 index d38db72..0000000 --- a/src/service.h +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * Authors: - * Charles Kerr <charles.kerr@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/>. - */ - -#ifndef __INDICATOR_DATETIME_SERVICE_H__ -#define __INDICATOR_DATETIME_SERVICE_H__ - -#include <glib.h> -#include <glib-object.h> - -#include "clock.h" -#include "planner.h" - -G_BEGIN_DECLS - -/* standard GObject macros */ -#define INDICATOR_DATETIME_SERVICE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), INDICATOR_TYPE_DATETIME_SERVICE, IndicatorDatetimeService)) -#define INDICATOR_TYPE_DATETIME_SERVICE (indicator_datetime_service_get_type()) -#define INDICATOR_IS_DATETIME_SERVICE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), INDICATOR_TYPE_DATETIME_SERVICE)) - -typedef struct _IndicatorDatetimeService IndicatorDatetimeService; -typedef struct _IndicatorDatetimeServiceClass IndicatorDatetimeServiceClass; -typedef struct _IndicatorDatetimeServicePrivate IndicatorDatetimeServicePrivate; - -/* signal keys */ -#define INDICATOR_DATETIME_SERVICE_SIGNAL_NAME_LOST "name-lost" - -/** - * The Indicator Datetime Service. - */ -struct _IndicatorDatetimeService -{ - /*< private >*/ - GObject parent; - IndicatorDatetimeServicePrivate * priv; -}; - -struct _IndicatorDatetimeServiceClass -{ - GObjectClass parent_class; - - /* signals */ - - void (* name_lost)(IndicatorDatetimeService * self); -}; - -/*** -**** -***/ - -GType indicator_datetime_service_get_type (void); - -IndicatorDatetimeService * indicator_datetime_service_new (IndicatorDatetimeClock * clock, - IndicatorDatetimePlanner * planner); - -void indicator_datetime_service_set_calendar_date (IndicatorDatetimeService * self, - GDateTime * date); - -void indicator_datetime_service_set_planner (IndicatorDatetimeService * self, - IndicatorDatetimePlanner * planner); - - -void indicator_datetime_service_set_clock (IndicatorDatetimeService * self, - IndicatorDatetimeClock * clock); - - -G_END_DECLS - -#endif /* __INDICATOR_DATETIME_SERVICE_H__ */ diff --git a/src/settings-live.cpp b/src/settings-live.cpp new file mode 100644 index 0000000..2305c93 --- /dev/null +++ b/src/settings-live.cpp @@ -0,0 +1,257 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * 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/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <datetime/settings-live.h> + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +LiveSettings::~LiveSettings() +{ + g_clear_object(&m_settings); +} + +LiveSettings::LiveSettings(): + m_settings(g_settings_new(SETTINGS_INTERFACE)) +{ + g_signal_connect (m_settings, "changed", G_CALLBACK(on_changed), this); + + // init the Properties from the GSettings backend + update_custom_time_format(); + update_locations(); + update_show_calendar(); + update_show_clock(); + update_show_date(); + update_show_day(); + update_show_detected_locations(); + update_show_events(); + update_show_locations(); + update_show_seconds(); + update_show_week_numbers(); + update_show_year(); + update_time_format_mode(); + update_timezone_name(); + + // now listen for clients to change the properties s.t. we can sync update GSettings + + custom_time_format.changed().connect([this](const std::string& value){ + g_settings_set_string(m_settings, SETTINGS_CUSTOM_TIME_FORMAT_S, value.c_str()); + }); + + locations.changed().connect([this](const std::vector<std::string>& value){ + const int n = value.size(); + gchar** strv = g_new0(gchar*, n+1); + for(int i=0; i<n; i++) + strv[i] = const_cast<char*>(value[i].c_str()); + g_settings_set_strv(m_settings, SETTINGS_LOCATIONS_S, strv); + g_free(strv); + }); + + show_calendar.changed().connect([this](bool value){ + g_settings_set_boolean(m_settings, SETTINGS_SHOW_CALENDAR_S, value); + }); + + show_clock.changed().connect([this](bool value){ + g_settings_set_boolean(m_settings, SETTINGS_SHOW_CLOCK_S, value); + }); + + show_date.changed().connect([this](bool value){ + g_settings_set_boolean(m_settings, SETTINGS_SHOW_DATE_S, value); + }); + + show_day.changed().connect([this](bool value){ + g_settings_set_boolean(m_settings, SETTINGS_SHOW_DAY_S, value); + }); + + show_detected_location.changed().connect([this](bool value){ + g_settings_set_boolean(m_settings, SETTINGS_SHOW_DETECTED_S, value); + }); + + show_events.changed().connect([this](bool value){ + g_settings_set_boolean(m_settings, SETTINGS_SHOW_EVENTS_S, value); + }); + + show_locations.changed().connect([this](bool value){ + g_settings_set_boolean(m_settings, SETTINGS_SHOW_LOCATIONS_S, value); + }); + + show_seconds.changed().connect([this](bool value){ + g_settings_set_boolean(m_settings, SETTINGS_SHOW_SECONDS_S, value); + }); + + show_week_numbers.changed().connect([this](bool value){ + g_settings_set_boolean(m_settings, SETTINGS_SHOW_WEEK_NUMBERS_S, value); + }); + + show_year.changed().connect([this](bool value){ + g_settings_set_boolean(m_settings, SETTINGS_SHOW_YEAR_S, value); + }); + + time_format_mode.changed().connect([this](TimeFormatMode value){ + g_settings_set_enum(m_settings, SETTINGS_TIME_FORMAT_S, gint(value)); + }); + + timezone_name.changed().connect([this](const std::string& value){ + g_settings_set_string(m_settings, SETTINGS_TIMEZONE_NAME_S, value.c_str()); + }); +} + +/*** +**** +***/ + +void LiveSettings::update_custom_time_format() +{ + auto val = g_settings_get_string(m_settings, SETTINGS_CUSTOM_TIME_FORMAT_S); + custom_time_format.set(val); + g_free(val); +} + +void LiveSettings::update_locations() +{ + auto strv = g_settings_get_strv(m_settings, SETTINGS_LOCATIONS_S); + std::vector<std::string> l; + for(int i=0; strv && strv[i]; i++) + l.push_back(strv[i]); + g_strfreev(strv); + locations.set(l); +} + +void LiveSettings::update_show_calendar() +{ + const auto val = g_settings_get_boolean(m_settings, SETTINGS_SHOW_CALENDAR_S); + show_calendar.set(val); +} + +void LiveSettings::update_show_clock() +{ + show_clock.set(g_settings_get_boolean(m_settings, SETTINGS_SHOW_CLOCK_S)); +} + +void LiveSettings::update_show_date() +{ + show_date.set(g_settings_get_boolean(m_settings, SETTINGS_SHOW_DATE_S)); +} + +void LiveSettings::update_show_day() +{ + show_day.set(g_settings_get_boolean(m_settings, SETTINGS_SHOW_DAY_S)); +} + +void LiveSettings::update_show_detected_locations() +{ + const auto val = g_settings_get_boolean(m_settings, SETTINGS_SHOW_DETECTED_S); + show_detected_location.set(val); +} + +void LiveSettings::update_show_events() +{ + const auto val = g_settings_get_boolean(m_settings, SETTINGS_SHOW_EVENTS_S); + show_events.set(val); +} + +void LiveSettings::update_show_locations() +{ + const auto val = g_settings_get_boolean(m_settings, SETTINGS_SHOW_LOCATIONS_S); + show_locations.set(val); +} + +void LiveSettings::update_show_seconds() +{ + show_seconds.set(g_settings_get_boolean(m_settings, SETTINGS_SHOW_SECONDS_S)); +} + +void LiveSettings::update_show_week_numbers() +{ + const auto val = g_settings_get_boolean(m_settings, SETTINGS_SHOW_WEEK_NUMBERS_S); + show_week_numbers.set(val); +} + +void LiveSettings::update_show_year() +{ + show_year.set(g_settings_get_boolean(m_settings, SETTINGS_SHOW_YEAR_S)); +} + +void LiveSettings::update_time_format_mode() +{ + time_format_mode.set((TimeFormatMode)g_settings_get_enum(m_settings, SETTINGS_TIME_FORMAT_S)); +} + +void LiveSettings::update_timezone_name() +{ + auto val = g_settings_get_string(m_settings, SETTINGS_TIMEZONE_NAME_S); + timezone_name.set(val); + g_free(val); +} + +/*** +**** +***/ + +void LiveSettings::on_changed(GSettings* /*settings*/, + gchar* key, + gpointer gself) +{ + static_cast<LiveSettings*>(gself)->update_key(key); +} + +void LiveSettings::update_key(const std::string& key) +{ + if (key == SETTINGS_SHOW_CLOCK_S) + update_show_clock(); + else if (key == SETTINGS_LOCATIONS_S) + update_locations(); + else if (key == SETTINGS_TIME_FORMAT_S) + update_time_format_mode(); + else if (key == SETTINGS_SHOW_SECONDS_S) + update_show_seconds(); + else if (key == SETTINGS_SHOW_DAY_S) + update_show_day(); + else if (key == SETTINGS_SHOW_DATE_S) + update_show_date(); + else if (key == SETTINGS_SHOW_YEAR_S) + update_show_year(); + else if (key == SETTINGS_CUSTOM_TIME_FORMAT_S) + update_custom_time_format(); + else if (key == SETTINGS_SHOW_CALENDAR_S) + update_show_calendar(); + else if (key == SETTINGS_SHOW_WEEK_NUMBERS_S) + update_show_week_numbers(); + else if (key == SETTINGS_SHOW_EVENTS_S) + update_show_events(); + else if (key == SETTINGS_SHOW_LOCATIONS_S) + update_show_locations(); + else if (key == SETTINGS_SHOW_DETECTED_S) + update_show_detected_locations(); + else if (key == SETTINGS_TIMEZONE_NAME_S) + update_timezone_name(); +} + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/settings-shared.h b/src/settings-shared.h deleted file mode 100644 index 4615fe8..0000000 --- a/src/settings-shared.h +++ /dev/null @@ -1,50 +0,0 @@ -/* -An indicator to show date and time information. - -Copyright 2010 Canonical Ltd. - -Authors: - 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/>. -*/ - -#ifndef __DATETIME_SETTINGS_SHARED_H__ -#define __DATETIME_SETTINGS_SHARED_H__ - -typedef enum -{ - TIME_FORMAT_MODE_LOCALE_DEFAULT, - TIME_FORMAT_MODE_12_HOUR, - TIME_FORMAT_MODE_24_HOUR, - TIME_FORMAT_MODE_CUSTOM -} -TimeFormatMode; - -#define SETTINGS_INTERFACE "com.canonical.indicator.datetime" -#define SETTINGS_SHOW_CLOCK_S "show-clock" -#define SETTINGS_TIME_FORMAT_S "time-format" -#define SETTINGS_SHOW_SECONDS_S "show-seconds" -#define SETTINGS_SHOW_DAY_S "show-day" -#define SETTINGS_SHOW_DATE_S "show-date" -#define SETTINGS_SHOW_YEAR_S "show-year" -#define SETTINGS_CUSTOM_TIME_FORMAT_S "custom-time-format" -#define SETTINGS_SHOW_CALENDAR_S "show-calendar" -#define SETTINGS_SHOW_WEEK_NUMBERS_S "show-week-numbers" -#define SETTINGS_SHOW_EVENTS_S "show-events" -#define SETTINGS_SHOW_LOCATIONS_S "show-locations" -#define SETTINGS_SHOW_DETECTED_S "show-auto-detected-location" -#define SETTINGS_LOCATIONS_S "locations" -#define SETTINGS_TIMEZONE_NAME_S "timezone-name" - -#endif diff --git a/src/state-live.cpp b/src/state-live.cpp new file mode 100644 index 0000000..fe1e6cd --- /dev/null +++ b/src/state-live.cpp @@ -0,0 +1,56 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * 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/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <datetime/state-live.h> + +#include <datetime/clock.h> +#include <datetime/locations-settings.h> +#include <datetime/planner-eds.h> +#include <datetime/settings-live.h> +#include <datetime/state.h> +#include <datetime/timezones-live.h> + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +LiveState::LiveState() +{ + std::shared_ptr<Settings> live_settings(new LiveSettings); + std::shared_ptr<Timezones> live_timezones(new LiveTimezones(live_settings, TIMEZONE_FILE)); + std::shared_ptr<Clock> live_clock(new LiveClock(live_timezones)); + + settings = live_settings; + clock = live_clock; + locations.reset(new SettingsLocations(live_settings, live_timezones)); + planner.reset(new PlannerEds); + planner->time = clock->localtime(); +} + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/timezone-file.c b/src/timezone-file.c deleted file mode 100644 index ddc4256..0000000 --- a/src/timezone-file.c +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * Authors: - * Charles Kerr <charles.kerr@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 <gio/gio.h> /* GFile, GFileMonitor */ - -#include "timezone-file.h" - -enum -{ - PROP_0, - PROP_FILENAME, - PROP_LAST -}; - -static GParamSpec * properties[PROP_LAST] = { 0 }; - -struct _IndicatorDatetimeTimezoneFilePriv -{ - gchar * filename; - GFileMonitor * monitor; -}; - -typedef IndicatorDatetimeTimezoneFilePriv priv_t; - -G_DEFINE_TYPE (IndicatorDatetimeTimezoneFile, - indicator_datetime_timezone_file, - INDICATOR_TYPE_DATETIME_TIMEZONE) - -/*** -**** -***/ - -static void -reload (IndicatorDatetimeTimezoneFile * self) -{ - priv_t * p = self->priv; - - GError * err = NULL; - gchar * timezone = NULL; - - if (!g_file_get_contents (p->filename, &timezone, NULL, &err)) - { - g_warning ("%s Unable to read timezone file '%s': %s", G_STRLOC, p->filename, err->message); - g_error_free (err); - } - else - { - g_strstrip (timezone); - indicator_datetime_timezone_set_timezone (INDICATOR_DATETIME_TIMEZONE(self), timezone); - g_free (timezone); - } -} - -static void -set_filename (IndicatorDatetimeTimezoneFile * self, const char * filename) -{ - GError * err; - GFile * file; - priv_t * p = self->priv; - - g_clear_object (&p->monitor); - g_free (p->filename); - - p->filename = g_strdup (filename); - err = NULL; - file = g_file_new_for_path (p->filename); - p->monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &err); - g_object_unref (file); - if (err != NULL) - { - g_warning ("%s Unable to monitor timezone file '%s': %s", G_STRLOC, TIMEZONE_FILE, err->message); - g_error_free (err); - } - else - { - g_signal_connect_swapped (p->monitor, "changed", G_CALLBACK(reload), self); - g_debug ("%s Monitoring timezone file '%s'", G_STRLOC, p->filename); - } - - reload (self); -} - -/*** -**** GObjectClass funcs -***/ - -static void -my_get_property (GObject * o, - guint property_id, - GValue * value, - GParamSpec * pspec) -{ - IndicatorDatetimeTimezoneFile * self = INDICATOR_DATETIME_TIMEZONE_FILE (o); - - switch (property_id) - { - case PROP_FILENAME: - g_value_set_string (value, self->priv->filename); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec); - } -} - -static void -my_set_property (GObject * o, - guint property_id, - const GValue * value, - GParamSpec * pspec) -{ - IndicatorDatetimeTimezoneFile * self = INDICATOR_DATETIME_TIMEZONE_FILE (o); - - switch (property_id) - { - case PROP_FILENAME: - set_filename (self, g_value_get_string (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec); - } -} - -static void -my_dispose (GObject * o) -{ - IndicatorDatetimeTimezoneFile * self = INDICATOR_DATETIME_TIMEZONE_FILE (o); - priv_t * p = self->priv; - - g_clear_object (&p->monitor); - - G_OBJECT_CLASS (indicator_datetime_timezone_file_parent_class)->dispose (o); -} - -static void -my_finalize (GObject * o) -{ - IndicatorDatetimeTimezoneFile * self = INDICATOR_DATETIME_TIMEZONE_FILE (o); - priv_t * p = self->priv; - - g_free (p->filename); - - G_OBJECT_CLASS (indicator_datetime_timezone_file_parent_class)->finalize (o); -} - -/*** -**** -***/ - -static void -indicator_datetime_timezone_file_class_init (IndicatorDatetimeTimezoneFileClass * klass) -{ - GObjectClass * object_class; - const GParamFlags flags = G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS; - - object_class = G_OBJECT_CLASS (klass); - object_class->dispose = my_dispose; - object_class->finalize = my_finalize; - object_class->set_property = my_set_property; - object_class->get_property = my_get_property; - - g_type_class_add_private (klass, sizeof (IndicatorDatetimeTimezoneFilePriv)); - - /* install properties */ - - properties[PROP_0] = NULL; - - properties[PROP_FILENAME] = g_param_spec_string ("filename", - "Filename", - "Filename to monitor for TZ changes", - "", - flags); - - g_object_class_install_properties (object_class, PROP_LAST, properties); -} - -static void -indicator_datetime_timezone_file_init (IndicatorDatetimeTimezoneFile * self) -{ - self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, - INDICATOR_TYPE_DATETIME_TIMEZONE_FILE, - IndicatorDatetimeTimezoneFilePriv); -} - -/*** -**** Public -***/ - -IndicatorDatetimeTimezone * -indicator_datetime_timezone_file_new (const char * filename) -{ - gpointer o = g_object_new (INDICATOR_TYPE_DATETIME_TIMEZONE_FILE, "filename", filename, NULL); - - return INDICATOR_DATETIME_TIMEZONE (o); -} diff --git a/src/timezone-file.cpp b/src/timezone-file.cpp new file mode 100644 index 0000000..3a0c240 --- /dev/null +++ b/src/timezone-file.cpp @@ -0,0 +1,89 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * 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/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <datetime/timezone-file.h> + +namespace unity { +namespace indicator { +namespace datetime { + +void +FileTimezone::clear() +{ + if (m_monitor_handler_id) + g_signal_handler_disconnect(m_monitor, m_monitor_handler_id); + + g_clear_object (&m_monitor); + + m_filename.clear(); +} + +void +FileTimezone::setFilename(const std::string& filename) +{ + clear(); + + m_filename = filename; + + auto file = g_file_new_for_path(filename.c_str()); + GError * err = nullptr; + m_monitor = g_file_monitor_file(file, G_FILE_MONITOR_NONE, nullptr, &err); + g_object_unref(file); + if (err) + { + g_warning("%s Unable to monitor timezone file '%s': %s", G_STRLOC, TIMEZONE_FILE, err->message); + g_error_free(err); + } + else + { + m_monitor_handler_id = g_signal_connect_swapped(m_monitor, "changed", G_CALLBACK(onFileChanged), this); + g_debug("%s Monitoring timezone file '%s'", G_STRLOC, filename.c_str()); + } + + reload(); +} + +void +FileTimezone::onFileChanged(gpointer gself) +{ + static_cast<FileTimezone*>(gself)->reload(); +} + +void +FileTimezone::reload() +{ + GError * err = nullptr; + gchar * str = nullptr; + + if (!g_file_get_contents(m_filename.c_str(), &str, nullptr, &err)) + { + g_warning("%s Unable to read timezone file '%s': %s", G_STRLOC, m_filename.c_str(), err->message); + g_error_free(err); + } + else + { + g_strstrip(str); + timezone.set(str); + g_free(str); + } +} + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/timezone-file.h b/src/timezone-file.h deleted file mode 100644 index b02abe1..0000000 --- a/src/timezone-file.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * Authors: - * Charles Kerr <charles.kerr@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/>. - */ - -#ifndef __INDICATOR_DATETIME_TIMEZONE_FILE__H__ -#define __INDICATOR_DATETIME_TIMEZONE_FILE__H__ - -#include "timezone.h" /* parent class */ - -G_BEGIN_DECLS - -#define INDICATOR_TYPE_DATETIME_TIMEZONE_FILE (indicator_datetime_timezone_file_get_type()) -#define INDICATOR_DATETIME_TIMEZONE_FILE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), INDICATOR_TYPE_DATETIME_TIMEZONE_FILE, IndicatorDatetimeTimezoneFile)) -#define INDICATOR_DATETIME_TIMEZONE_FILE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), INDICATOR_TYPE_DATETIME_TIMEZONE_FILE, IndicatorDatetimeTimezoneFileClass)) -#define INDICATOR_IS_DATETIME_TIMEZONE_FILE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), INDICATOR_TYPE_DATETIME_TIMEZONE_FILE)) - -typedef struct _IndicatorDatetimeTimezoneFile IndicatorDatetimeTimezoneFile; -typedef struct _IndicatorDatetimeTimezoneFilePriv IndicatorDatetimeTimezoneFilePriv; -typedef struct _IndicatorDatetimeTimezoneFileClass IndicatorDatetimeTimezoneFileClass; - -GType indicator_datetime_timezone_file_get_type (void); - -/** - * An IndicatorDatetimeTimezone which uses a local file, - * such as /etc/timezone, to determine the timezone. - */ -struct _IndicatorDatetimeTimezoneFile -{ - /*< private >*/ - IndicatorDatetimeTimezone parent; - IndicatorDatetimeTimezoneFilePriv * priv; -}; - -struct _IndicatorDatetimeTimezoneFileClass -{ - IndicatorDatetimeTimezoneClass parent_class; -}; - -IndicatorDatetimeTimezone * indicator_datetime_timezone_file_new (const char * filename); - -G_END_DECLS - -#endif /* __INDICATOR_DATETIME_TIMEZONE_FILE__H__ */ diff --git a/src/timezone-geoclue.c b/src/timezone-geoclue.c deleted file mode 100644 index ac23b93..0000000 --- a/src/timezone-geoclue.c +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * Authors: - * Charles Kerr <charles.kerr@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 <geoclue/geoclue-master.h> -#include <geoclue/geoclue-master-client.h> - -#include "timezone-geoclue.h" - -struct _IndicatorDatetimeTimezoneGeocluePriv -{ - GeoclueMaster * master; - GeoclueMasterClient * client; - GeoclueAddress * address; -}; - -typedef IndicatorDatetimeTimezoneGeocluePriv priv_t; - -G_DEFINE_TYPE (IndicatorDatetimeTimezoneGeoclue, - indicator_datetime_timezone_geoclue, - INDICATOR_TYPE_DATETIME_TIMEZONE) - -static void geo_restart (IndicatorDatetimeTimezoneGeoclue * self); - -/*** -**** -***/ - -static void -on_address_changed (GeoclueAddress * address G_GNUC_UNUSED, - int timestamp G_GNUC_UNUSED, - GHashTable * addy_data, - GeoclueAccuracy * accuracy G_GNUC_UNUSED, - GError * error, - gpointer gself) -{ - if (error != NULL) - { - g_warning ("%s Unable to get timezone from GeoClue: %s", G_STRFUNC, error->message); - } - else - { - IndicatorDatetimeTimezoneGeoclue * self = INDICATOR_DATETIME_TIMEZONE_GEOCLUE (gself); - const char * timezone = g_hash_table_lookup (addy_data, "timezone"); - indicator_datetime_timezone_set_timezone (INDICATOR_DATETIME_TIMEZONE(self), timezone); - } -} - -/* The signal doesn't have the parameter for an error, so it ends up needing - a NULL inserted. */ -static void -on_address_changed_sig (GeoclueAddress * address G_GNUC_UNUSED, - int timestamp G_GNUC_UNUSED, - GHashTable * addy_data, - GeoclueAccuracy * accuracy G_GNUC_UNUSED, - gpointer gself) -{ - on_address_changed(address, timestamp, addy_data, accuracy, NULL, gself); -} - -static void -on_address_created (GeoclueMasterClient * master G_GNUC_UNUSED, - GeoclueAddress * address, - GError * error, - gpointer gself) -{ - if (error != NULL) - { - g_warning ("%s Unable to get timezone from GeoClue: %s", G_STRFUNC, error->message); - } - else - { - priv_t * p = INDICATOR_DATETIME_TIMEZONE_GEOCLUE(gself)->priv; - - g_assert (p->address == NULL); - p->address = g_object_ref (address); - - geoclue_address_get_address_async (address, on_address_changed, gself); - g_signal_connect (address, "address-changed", G_CALLBACK(on_address_changed_sig), gself); - } -} - -static void -on_requirements_set (GeoclueMasterClient * master G_GNUC_UNUSED, - GError * error, - gpointer user_data G_GNUC_UNUSED) -{ - if (error != NULL) - { - g_warning ("%s Unable to get timezone from GeoClue: %s", G_STRFUNC, error->message); - } -} - -static void -on_client_created (GeoclueMaster * master G_GNUC_UNUSED, - GeoclueMasterClient * client, - gchar * path, - GError * error, - gpointer gself) -{ - g_debug ("Created Geoclue client at: %s", path); - - if (error != NULL) - { - g_warning ("%s Unable to get timezone from GeoClue: %s", G_STRFUNC, error->message); - } - else - { - IndicatorDatetimeTimezoneGeoclue * self = INDICATOR_DATETIME_TIMEZONE_GEOCLUE (gself); - priv_t * p = self->priv; - - g_clear_object (&p->client); - p->client = g_object_ref (client); - g_signal_connect_swapped (p->client, "invalidated", G_CALLBACK(geo_restart), gself); - - geoclue_master_client_set_requirements_async (p->client, - GEOCLUE_ACCURACY_LEVEL_REGION, - 0, - FALSE, - GEOCLUE_RESOURCE_ALL, - on_requirements_set, - NULL); - - geoclue_master_client_create_address_async (p->client, on_address_created, gself); - } -} - -static void -geo_start (IndicatorDatetimeTimezoneGeoclue * self) -{ - priv_t * p = self->priv; - - g_assert (p->master == NULL); - p->master = geoclue_master_get_default (); - geoclue_master_create_client_async (p->master, on_client_created, self); -} - -static void -geo_stop (IndicatorDatetimeTimezoneGeoclue * self) -{ - priv_t * p = self->priv; - - if (p->address != NULL) - { - g_signal_handlers_disconnect_by_func (p->address, on_address_changed_sig, self); - g_clear_object (&p->address); - } - - if (p->client != NULL) - { - g_signal_handlers_disconnect_by_func (p->client, geo_restart, self); - g_clear_object (&p->client); - } - - g_clear_object (&p->master); -} - -static void -geo_restart (IndicatorDatetimeTimezoneGeoclue * self) -{ - geo_stop (self); - geo_start (self); -} - -/*** -**** -***/ - -static void -my_dispose (GObject * o) -{ - geo_stop (INDICATOR_DATETIME_TIMEZONE_GEOCLUE (o)); - - G_OBJECT_CLASS (indicator_datetime_timezone_geoclue_parent_class)->dispose (o); -} - -static void -indicator_datetime_timezone_geoclue_class_init (IndicatorDatetimeTimezoneGeoclueClass * klass) -{ - GObjectClass * object_class; - - object_class = G_OBJECT_CLASS (klass); - object_class->dispose = my_dispose; - - g_type_class_add_private (klass, sizeof (IndicatorDatetimeTimezoneGeocluePriv)); -} - -static void -indicator_datetime_timezone_geoclue_init (IndicatorDatetimeTimezoneGeoclue * self) -{ - priv_t * p; - - p = G_TYPE_INSTANCE_GET_PRIVATE (self, - INDICATOR_TYPE_DATETIME_TIMEZONE_GEOCLUE, - IndicatorDatetimeTimezoneGeocluePriv); - - self->priv = p; - - geo_start (self); -} - -/*** -**** Public -***/ - -IndicatorDatetimeTimezone * -indicator_datetime_timezone_geoclue_new (void) -{ - gpointer o = g_object_new (INDICATOR_TYPE_DATETIME_TIMEZONE_GEOCLUE, NULL); - - return INDICATOR_DATETIME_TIMEZONE (o); -} diff --git a/src/timezone-geoclue.cpp b/src/timezone-geoclue.cpp new file mode 100644 index 0000000..b8847a4 --- /dev/null +++ b/src/timezone-geoclue.cpp @@ -0,0 +1,250 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * Authors: + * Charles Kerr <charles.kerr@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 <datetime/timezone-geoclue.h> + +#define GEOCLUE_BUS_NAME "org.freedesktop.Geoclue.Master" + +namespace unity { +namespace indicator { +namespace datetime { + + +GeoclueTimezone::GeoclueTimezone(): + m_cancellable(g_cancellable_new()) +{ + g_bus_get(G_BUS_TYPE_SESSION, m_cancellable, on_bus_got, this); +} + +GeoclueTimezone::~GeoclueTimezone() +{ + g_cancellable_cancel(m_cancellable); + g_object_unref(m_cancellable); + + if (m_signal_subscription) + g_dbus_connection_signal_unsubscribe(m_connection, m_signal_subscription); + + g_object_unref(m_connection); +} + +/*** +**** +***/ + +void +GeoclueTimezone::on_bus_got(GObject* /*source*/, + GAsyncResult* res, + gpointer gself) +{ + GError * error; + GDBusConnection * connection; + + error = nullptr; + connection = g_bus_get_finish(res, &error); + if (error) + { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("Couldn't get bus: %s", error->message); + + g_error_free(error); + } + else + { + auto self = static_cast<GeoclueTimezone*>(gself); + + self->m_connection = connection; + + g_dbus_connection_call(self->m_connection, + GEOCLUE_BUS_NAME, + "/org/freedesktop/Geoclue/Master", + "org.freedesktop.Geoclue.Master", + "Create", + nullptr, // parameters + G_VARIANT_TYPE("(o)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + self->m_cancellable, + on_client_created, + self); + } +} + +void +GeoclueTimezone::on_client_created(GObject * source, GAsyncResult * res, gpointer gself) +{ + GVariant * result; + + if ((result = call_finish(source, res))) + { + auto self = static_cast<GeoclueTimezone*>(gself); + + GVariant * child = g_variant_get_child_value(result, 0); + self->m_client_object_path = g_variant_get_string(child, nullptr); + g_variant_unref(child); + g_variant_unref(result); + + self->m_signal_subscription = g_dbus_connection_signal_subscribe( + self->m_connection, + GEOCLUE_BUS_NAME, + "org.freedesktop.Geoclue.Address", // inteface + "AddressChanged", // signal name + self->m_client_object_path.c_str(), // object path + nullptr, // arg0 + G_DBUS_SIGNAL_FLAGS_NONE, + on_address_changed, + self, + nullptr); + + g_dbus_connection_call(self->m_connection, + GEOCLUE_BUS_NAME, + self->m_client_object_path.c_str(), + "org.freedesktop.Geoclue.MasterClient", + "SetRequirements", + g_variant_new("(iibi)", 2, 0, FALSE, 1023), + nullptr, + G_DBUS_CALL_FLAGS_NONE, + -1, + self->m_cancellable, + on_requirements_set, + self); + } +} + +void +GeoclueTimezone::on_address_changed(GDBusConnection* /*connection*/, + const gchar* /*sender_name*/, + const gchar* /*object_path*/, + const gchar* /*interface_name*/, + const gchar* /*signal_name*/, + GVariant* parameters, + gpointer gself) +{ + static_cast<GeoclueTimezone*>(gself)->setTimezoneFromAddressVariant(parameters); +} + +void +GeoclueTimezone::on_requirements_set(GObject* source, GAsyncResult* res, gpointer gself) +{ + GVariant * result; + + if ((result = call_finish(source, res))) + { + auto self = static_cast<GeoclueTimezone*>(gself); + + g_dbus_connection_call(self->m_connection, + GEOCLUE_BUS_NAME, + self->m_client_object_path.c_str(), + "org.freedesktop.Geoclue.MasterClient", + "AddressStart", + nullptr, + nullptr, + G_DBUS_CALL_FLAGS_NONE, + -1, + self->m_cancellable, + on_address_started, + self); + + g_variant_unref(result); + } +} + +void +GeoclueTimezone::on_address_started(GObject * source, GAsyncResult * res, gpointer gself) +{ + GVariant * result; + + if ((result = call_finish(source, res))) + { + auto self = static_cast<GeoclueTimezone*>(gself); + + g_dbus_connection_call(self->m_connection, + GEOCLUE_BUS_NAME, + self->m_client_object_path.c_str(), + "org.freedesktop.Geoclue.Address", + "GetAddress", + nullptr, + G_VARIANT_TYPE("(ia{ss}(idd))"), + G_DBUS_CALL_FLAGS_NONE, + -1, + self->m_cancellable, + on_address_got, + self); + + g_variant_unref(result); + } +} + +void +GeoclueTimezone::on_address_got(GObject * source, GAsyncResult * res, gpointer gself) +{ + GVariant * result; + + if ((result = call_finish(source, res))) + { + static_cast<GeoclueTimezone*>(gself)->setTimezoneFromAddressVariant(result); + g_variant_unref(result); + } +} + +void +GeoclueTimezone::setTimezoneFromAddressVariant(GVariant * variant) +{ + g_return_if_fail(g_variant_is_of_type(variant, G_VARIANT_TYPE("(ia{ss}(idd))"))); + + const gchar * timezone_string = nullptr; + GVariant * dict = g_variant_get_child_value(variant, 1); + if (dict) + { + if (g_variant_lookup(dict, "timezone", "&s", &timezone_string)) + timezone.set(timezone_string); + + g_variant_unref(dict); + } +} + +GVariant* +GeoclueTimezone::call_finish(GObject * source, GAsyncResult * res) +{ + GError * error; + GVariant * result; + + error = nullptr; + result = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error); + + if (error) + { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("AddressStart() failed: %s", error->message); + + g_error_free(error); + + g_clear_pointer(&result, g_variant_unref); + } + + return result; +} + +/**** +***** +****/ + +} // namespace datetime +} // namespace indicator +} // namespace unity + diff --git a/src/timezone-geoclue.h b/src/timezone-geoclue.h deleted file mode 100644 index 059bd81..0000000 --- a/src/timezone-geoclue.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * Authors: - * Charles Kerr <charles.kerr@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/>. - */ - -#ifndef __INDICATOR_DATETIME_TIMEZONE_GEOCLUE__H__ -#define __INDICATOR_DATETIME_TIMEZONE_GEOCLUE__H__ - -#include "timezone.h" /* parent class */ - -G_BEGIN_DECLS - -#define INDICATOR_TYPE_DATETIME_TIMEZONE_GEOCLUE (indicator_datetime_timezone_geoclue_get_type()) -#define INDICATOR_DATETIME_TIMEZONE_GEOCLUE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), INDICATOR_TYPE_DATETIME_TIMEZONE_GEOCLUE, IndicatorDatetimeTimezoneGeoclue)) -#define INDICATOR_DATETIME_TIMEZONE_GEOCLUE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), INDICATOR_TYPE_DATETIME_TIMEZONE_GEOCLUE, IndicatorDatetimeTimezoneGeoclueClass)) -#define INDICATOR_IS_DATETIME_TIMEZONE_GEOCLUE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), INDICATOR_TYPE_DATETIME_TIMEZONE_GEOCLUE)) - -typedef struct _IndicatorDatetimeTimezoneGeoclue IndicatorDatetimeTimezoneGeoclue; -typedef struct _IndicatorDatetimeTimezoneGeocluePriv IndicatorDatetimeTimezoneGeocluePriv; -typedef struct _IndicatorDatetimeTimezoneGeoclueClass IndicatorDatetimeTimezoneGeoclueClass; - -GType indicator_datetime_timezone_geoclue_get_type (void); - -/** - * An IndicatorDatetimeTimezone which uses GeoClue to determine the timezone. - */ -struct _IndicatorDatetimeTimezoneGeoclue -{ - /*< private >*/ - IndicatorDatetimeTimezone parent; - IndicatorDatetimeTimezoneGeocluePriv * priv; -}; - -struct _IndicatorDatetimeTimezoneGeoclueClass -{ - IndicatorDatetimeTimezoneClass parent_class; -}; - -IndicatorDatetimeTimezone * indicator_datetime_timezone_geoclue_new (void); - -G_END_DECLS - -#endif /* __INDICATOR_DATETIME_TIMEZONE_GEOCLUE__H__ */ diff --git a/src/timezone.c b/src/timezone.c deleted file mode 100644 index 4f8addc..0000000 --- a/src/timezone.c +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * Authors: - * Charles Kerr <charles.kerr@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 "timezone.h" - -G_DEFINE_TYPE (IndicatorDatetimeTimezone, - indicator_datetime_timezone, - G_TYPE_OBJECT) - -enum -{ - PROP_0, - PROP_TIMEZONE, - PROP_LAST -}; - -static GParamSpec * properties[PROP_LAST] = { 0, }; - -struct _IndicatorDatetimeTimezonePriv -{ - GString * timezone; -}; - -typedef struct _IndicatorDatetimeTimezonePriv priv_t; - -static void -my_get_property (GObject * o, - guint property_id, - GValue * value, - GParamSpec * pspec) -{ - IndicatorDatetimeTimezone * self = INDICATOR_DATETIME_TIMEZONE (o); - - switch (property_id) - { - case PROP_TIMEZONE: - g_value_set_string (value, indicator_datetime_timezone_get_timezone (self)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec); - } -} - -static void -my_finalize (GObject * o) -{ - priv_t * p = INDICATOR_DATETIME_TIMEZONE(o)->priv; - - g_string_free (p->timezone, TRUE); - - G_OBJECT_CLASS (indicator_datetime_timezone_parent_class)->finalize (o); -} - -static void -/* cppcheck-suppress unusedFunction */ -indicator_datetime_timezone_class_init (IndicatorDatetimeTimezoneClass * klass) -{ - GObjectClass * object_class; - const GParamFlags flags = G_PARAM_READABLE | G_PARAM_STATIC_STRINGS; - - g_type_class_add_private (klass, sizeof (IndicatorDatetimeTimezonePriv)); - - object_class = G_OBJECT_CLASS (klass); - object_class->get_property = my_get_property; - object_class->finalize = my_finalize; - - properties[PROP_TIMEZONE] = g_param_spec_string ("timezone", - "Timezone", - "Timezone", - "", - flags); - - g_object_class_install_properties (object_class, PROP_LAST, properties); -} - -static void -indicator_datetime_timezone_init (IndicatorDatetimeTimezone * self) -{ - priv_t * p; - - p = G_TYPE_INSTANCE_GET_PRIVATE (self, - INDICATOR_TYPE_DATETIME_TIMEZONE, - IndicatorDatetimeTimezonePriv); - - p->timezone = g_string_new (NULL); - - self->priv = p; -} - -/*** -**** -***/ - -const char * -indicator_datetime_timezone_get_timezone (IndicatorDatetimeTimezone * self) -{ - g_return_val_if_fail (INDICATOR_IS_DATETIME_TIMEZONE (self), NULL); - - return self->priv->timezone->str; -} - -void -indicator_datetime_timezone_set_timezone (IndicatorDatetimeTimezone * self, - const char * timezone) -{ - priv_t * p = self->priv; - - if (g_strcmp0 (p->timezone->str, timezone)) - { - if (timezone != NULL) - g_string_assign (p->timezone, timezone); - else - g_string_set_size (p->timezone, 0); - g_debug ("%s new timezone set: '%s'", G_STRLOC, p->timezone->str); - g_object_notify_by_pspec (G_OBJECT(self), properties[PROP_TIMEZONE]); - } -} diff --git a/src/timezone.h b/src/timezone.h deleted file mode 100644 index fa6593d..0000000 --- a/src/timezone.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * Authors: - * Charles Kerr <charles.kerr@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/>. - */ - -#ifndef __INDICATOR_DATETIME_TIMEZONE__H__ -#define __INDICATOR_DATETIME_TIMEZONE__H__ - -#include <glib.h> -#include <glib-object.h> /* parent class */ - -G_BEGIN_DECLS - -#define INDICATOR_TYPE_DATETIME_TIMEZONE (indicator_datetime_timezone_get_type()) -#define INDICATOR_DATETIME_TIMEZONE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), INDICATOR_TYPE_DATETIME_TIMEZONE, IndicatorDatetimeTimezone)) -#define INDICATOR_DATETIME_TIMEZONE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), INDICATOR_TYPE_DATETIME_TIMEZONE, IndicatorDatetimeTimezoneClass)) -#define INDICATOR_DATETIME_TIMEZONE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), INDICATOR_TYPE_DATETIME_TIMEZONE, IndicatorDatetimeTimezoneClass)) -#define INDICATOR_IS_DATETIME_TIMEZONE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), INDICATOR_TYPE_DATETIME_TIMEZONE)) - -typedef struct _IndicatorDatetimeTimezone IndicatorDatetimeTimezone; -typedef struct _IndicatorDatetimeTimezonePriv IndicatorDatetimeTimezonePriv; -typedef struct _IndicatorDatetimeTimezoneClass IndicatorDatetimeTimezoneClass; - -GType indicator_datetime_timezone_get_type (void); - -/** - * Abstract Base Class for objects that provide a timezone. - * - * We use this in datetime to determine the user's current timezone - * for display in the 'locations' section of the datetime indicator. - * - * This class has a 'timezone' property that clients can watch - * for change notifications. - */ -struct _IndicatorDatetimeTimezone -{ - /*< private >*/ - GObject parent; - IndicatorDatetimeTimezonePriv * priv; -}; - -struct _IndicatorDatetimeTimezoneClass -{ - GObjectClass parent_class; -}; - -/*** -**** -***/ - -const char * indicator_datetime_timezone_get_timezone (IndicatorDatetimeTimezone *); - -void indicator_datetime_timezone_set_timezone (IndicatorDatetimeTimezone *, - const char * new_timezone); - -G_END_DECLS - -#endif /* __INDICATOR_DATETIME_TIMEZONE__H__ */ diff --git a/src/timezones-live.cpp b/src/timezones-live.cpp new file mode 100644 index 0000000..baac05d --- /dev/null +++ b/src/timezones-live.cpp @@ -0,0 +1,71 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * 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/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <datetime/timezones-live.h> + +#include <glib.h> + +namespace unity { +namespace indicator { +namespace datetime { + +LiveTimezones::LiveTimezones(std::shared_ptr<Settings>& settings, const std::string& filename): + m_file(filename), + m_settings(settings) +{ + m_file.timezone.changed().connect([this](const std::string&){update_timezones();}); + + m_settings->show_detected_location.changed().connect([this](bool){update_geolocation();}); + update_geolocation(); + + update_timezones(); +} + +void LiveTimezones::update_geolocation() +{ + // clear the previous pointer, if any + m_geo.reset(); + + // if location detection is enabled, turn on GeoClue + if(m_settings->show_detected_location.get()) + { + auto geo = new GeoclueTimezone(); + geo->timezone.changed().connect([this](const std::string&){update_timezones();}); + m_geo.reset(geo); + } +} + +void LiveTimezones::update_timezones() +{ + const auto a = m_file.timezone.get(); + const auto b = m_geo ? m_geo->timezone.get() : ""; + + timezone.set(a.empty() ? b : a); + + std::set<std::string> zones; + if (!a.empty()) + zones.insert(a); + if (!b.empty()) + zones.insert(b); + timezones.set(zones); +} + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/utils.c b/src/utils.c deleted file mode 100644 index c90f2e7..0000000 --- a/src/utils.c +++ /dev/null @@ -1,466 +0,0 @@ -/* -*- Mode: C; coding: utf-8; indent-tabs-mode: nil; tab-width: 2 -*- - -A dialog for setting time and date preferences. - -Copyright 2010 Canonical Ltd. - -Authors: - Michael Terry <michael.terry@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 <glib/gi18n-lib.h> -#include <gio/gio.h> -#include <locale.h> -#include <langinfo.h> -#include <string.h> -#include "utils.h" -#include "settings-shared.h" - -/* Check the system locale setting to see if the format is 24-hour - time or 12-hour time */ -gboolean -is_locale_12h (void) -{ - static const char *formats_24h[] = {"%H", "%R", "%T", "%OH", "%k", NULL}; - const char *t_fmt = nl_langinfo (T_FMT); - int i; - - for (i = 0; formats_24h[i]; ++i) { - if (strstr (t_fmt, formats_24h[i])) { - return FALSE; - } - } - - return TRUE; -} - -void -split_settings_location (const gchar * location, gchar ** zone, gchar ** name) -{ - gchar * location_dup; - gchar * first; - - location_dup = g_strdup (location); - g_strstrip (location_dup); - - if ((first = strchr (location_dup, ' '))) - *first = '\0'; - - if (zone != NULL) - { - *zone = location_dup; - } - - if (name != NULL) - { - gchar * after = first ? g_strstrip (first + 1) : NULL; - - if (after && *after) - { - *name = g_strdup (after); - } - else /* make the name from zone */ - { - gchar * chr = strrchr (location_dup, '/'); - after = g_strdup (chr ? chr + 1 : location_dup); - - /* replace underscores with spaces */ - for (chr=after; chr && *chr; chr++) - if (*chr == '_') - *chr = ' '; - - *name = after; - } - } -} - -gchar * -get_current_zone_name (const gchar * location, GSettings * settings) -{ - gchar * new_zone, * new_name; - gchar * tz_name; - gchar * old_zone, * old_name; - gchar * rv; - - split_settings_location (location, &new_zone, &new_name); - - tz_name = g_settings_get_string (settings, SETTINGS_TIMEZONE_NAME_S); - split_settings_location (tz_name, &old_zone, &old_name); - g_free (tz_name); - - /* new_name is always just a sanitized version of a timezone. - old_name is potentially a saved "pretty" version of a timezone name from - geonames. So we prefer to use it if available and the zones match. */ - - if (g_strcmp0 (old_zone, new_zone) == 0) { - rv = old_name; - old_name = NULL; - } - else { - rv = new_name; - new_name = NULL; - } - - g_free (new_zone); - g_free (old_zone); - g_free (new_name); - g_free (old_name); - - return rv; -} - -/* Translate msg according to the locale specified by LC_TIME */ -static const char * -T_(const char *msg) -{ - /* General strategy here is to make sure LANGUAGE is empty (since that - trumps all LC_* vars) and then to temporarily swap LC_TIME and - LC_MESSAGES. Then have gettext translate msg. - - We strdup the strings because the setlocale & *env functions do not - guarantee anything about the storage used for the string, and thus - the string may not be portably safe after multiple calls. - - Note that while you might think g_dcgettext would do the trick here, - that actually looks in /usr/share/locale/XX/LC_TIME, not the - LC_MESSAGES directory, so we won't find any translation there. - */ - char *message_locale = g_strdup(setlocale(LC_MESSAGES, NULL)); - const char *time_locale = setlocale (LC_TIME, NULL); - char *language = g_strdup(g_getenv("LANGUAGE")); - const char *rv; - if (language) - g_unsetenv("LANGUAGE"); - setlocale(LC_MESSAGES, time_locale); - - /* Get the LC_TIME version */ - rv = _(msg); - - /* Put everything back the way it was */ - setlocale(LC_MESSAGES, message_locale); - if (language) - g_setenv("LANGUAGE", language, TRUE); - g_free(message_locale); - g_free(language); - return rv; -} - -gchar * -join_date_and_time_format_strings (const char * date_string, - const char * time_string) -{ - gchar * str; - - if (date_string && time_string) - { - /* TRANSLATORS: This is a format string passed to strftime to combine the - * date and the time. The value of "%s\xE2\x80\x82%s" will result in a - * string like this in US English 12-hour time: 'Fri Jul 16 11:50 AM'. - * The space in between date and time is a Unicode en space - * (E28082 in UTF-8 hex). */ - str = g_strdup_printf (T_("%s\xE2\x80\x82%s"), date_string, time_string); - } - else if (date_string) - { - str = g_strdup_printf (T_("%s"), date_string); - } - else /* time_string */ - { - str = g_strdup_printf (T_("%s"), time_string); - } - - return str; -} - -/*** -**** -***/ - -static const gchar * -get_default_header_time_format (gboolean twelvehour, gboolean show_seconds) -{ - const gchar * fmt; - - if (twelvehour && show_seconds) - /* TRANSLATORS: a strftime(3) format for 12hr time w/seconds */ - fmt = T_("%l:%M:%S %p"); - else if (twelvehour) - /* TRANSLATORS: a strftime(3) format for 12hr time */ - fmt = T_("%l:%M %p"); - else if (show_seconds) - /* TRANSLATORS: a strftime(3) format for 24hr time w/seconds */ - fmt = T_("%H:%M:%S"); - else - /* TRANSLATORS: a strftime(3) format for 24hr time */ - fmt = T_("%H:%M"); - - return fmt; -} - -/*** -**** -***/ - -typedef enum -{ - DATE_PROXIMITY_TODAY, - DATE_PROXIMITY_TOMORROW, - DATE_PROXIMITY_WEEK, - DATE_PROXIMITY_FAR -} -date_proximity_t; - -static date_proximity_t -get_date_proximity (GDateTime * now, GDateTime * time) -{ - date_proximity_t prox = DATE_PROXIMITY_FAR; - gint now_year, now_month, now_day; - gint time_year, time_month, time_day; - - /* does it happen today? */ - g_date_time_get_ymd (now, &now_year, &now_month, &now_day); - g_date_time_get_ymd (time, &time_year, &time_month, &time_day); - if ((now_year == time_year) && (now_month == time_month) && (now_day == time_day)) - prox = DATE_PROXIMITY_TODAY; - - /* does it happen tomorrow? */ - if (prox == DATE_PROXIMITY_FAR) - { - GDateTime * tomorrow; - gint tom_year, tom_month, tom_day; - - tomorrow = g_date_time_add_days (now, 1); - g_date_time_get_ymd (tomorrow, &tom_year, &tom_month, &tom_day); - if ((tom_year == time_year) && (tom_month == time_month) && (tom_day == time_day)) - prox = DATE_PROXIMITY_TOMORROW; - - g_date_time_unref (tomorrow); - } - - /* does it happen this week? */ - if (prox == DATE_PROXIMITY_FAR) - { - GDateTime * week; - GDateTime * week_bound; - - week = g_date_time_add_days (now, 6); - week_bound = g_date_time_new_local (g_date_time_get_year(week), - g_date_time_get_month (week), - g_date_time_get_day_of_month(week), - 23, 59, 59.9); - - if (g_date_time_compare (time, week_bound) <= 0) - prox = DATE_PROXIMITY_WEEK; - - g_date_time_unref (week_bound); - g_date_time_unref (week); - } - - return prox; -} - - -/* - * "Terse" time & date format strings - * - * Used on the phone menu where space is at a premium, these strings - * express the time and date in as brief a form as possible. - * - * Examples from spec: - * 1. "Daily 6:30 AM" - * 2. "5:07 PM" (note date is omitted; today's date is implicit) - * 3. "Daily 12 PM" (note minutes are omitted for on-the-hour times) - * 4. "Tomorrow 7 AM" (note "Tomorrow" is used instead of a day of week) - */ - -static const gchar * -get_terse_date_format_string (date_proximity_t proximity) -{ - const gchar * fmt; - - switch (proximity) - { - case DATE_PROXIMITY_TODAY: - /* 'Today' is implicit in the terse case, so no string needed */ - fmt = NULL; - break; - - case DATE_PROXIMITY_TOMORROW: - fmt = T_("Tomorrow"); - break; - - case DATE_PROXIMITY_WEEK: - /* a strftime(3) fmt string for abbreviated day of week */ - fmt = T_("%a"); - break; - - default: - /* a strftime(3) fmt string for day-of-month and abbreviated month */ - fmt = T_("%d %b"); - break; - } - - return fmt; -} - -const gchar* -get_terse_header_time_format_string (void) -{ - const gboolean twelvehour = is_locale_12h (); - const gboolean show_seconds = FALSE; - - return get_default_header_time_format (twelvehour, show_seconds); -} - -const gchar * -get_terse_time_format_string (GDateTime * time) -{ - const gchar * fmt; - - if (g_date_time_get_minute (time) != 0) - { - fmt = get_terse_header_time_format_string (); - } - else - { - /* a strftime(3) fmt string for a 12 hour on-the-hour time, eg "7 PM" */ - fmt = T_("%l %p"); - } - - return fmt; -} - -gchar * -generate_terse_format_string_at_time (GDateTime * now, GDateTime * time) -{ - const date_proximity_t prox = get_date_proximity (now, time); - const gchar * date_fmt = get_terse_date_format_string (prox); - const gchar * time_fmt = get_terse_time_format_string (time); - return join_date_and_time_format_strings (date_fmt, time_fmt); -} - -/*** -**** FULL -***/ - -static const gchar * -get_full_date_format_string (gboolean show_day, gboolean show_date, gboolean show_year) -{ - const char * fmt; - - if (show_day && show_date && show_year) - /* TRANSLATORS: a strftime(3) format showing the weekday, date, and year */ - fmt = T_("%a %b %e %Y"); - else if (show_day && show_date) - /* TRANSLATORS: a strftime(3) format showing the weekday and date */ - fmt = T_("%a %b %e"); - else if (show_day && show_year) - /* TRANSLATORS: a strftime(3) format showing the weekday and year. */ - fmt = T_("%a %Y"); - else if (show_day) - /* TRANSLATORS: a strftime(3) format showing the weekday. */ - fmt = T_("%a"); - else if (show_date && show_year) - /* TRANSLATORS: a strftime(3) format showing the date and year */ - fmt = T_("%b %e %Y"); - else if (show_date) - /* TRANSLATORS: a strftime(3) format showing the date */ - fmt = T_("%b %e"); - else if (show_year) - /* TRANSLATORS: a strftime(3) format showing the year */ - fmt = T_("%Y"); - else - fmt = NULL; - - return fmt; -} - - -/* - * "Full" time & date format strings - * - * These are used on the desktop menu & header and honors the - * GSettings entries for 12/24hr mode and whether or not to show seconds. - * - */ - -const gchar * -get_full_time_format_string (GSettings * settings) -{ - gboolean twelvehour; - gboolean show_seconds; - - g_return_val_if_fail (settings != NULL, NULL); - - show_seconds = g_settings_get_boolean (settings, SETTINGS_SHOW_SECONDS_S); - - switch (g_settings_get_enum (settings, SETTINGS_TIME_FORMAT_S)) - { - case TIME_FORMAT_MODE_LOCALE_DEFAULT: - twelvehour = is_locale_12h(); - break; - - case TIME_FORMAT_MODE_24_HOUR: - twelvehour = FALSE; - break; - - default: - twelvehour = TRUE; - break; - } - - return get_default_header_time_format (twelvehour, show_seconds); -} - -gchar * -generate_full_format_string (gboolean show_day, gboolean show_date, gboolean show_year, GSettings * settings) -{ - const gchar * date_fmt = get_full_date_format_string (show_day, show_date, show_year); - const gchar * time_fmt = get_full_time_format_string (settings); - return join_date_and_time_format_strings (date_fmt, time_fmt); -} - -gchar * -generate_full_format_string_at_time (GDateTime * now, GDateTime * time, GSettings * settings) -{ - gboolean show_day; - gboolean show_date; - - g_return_val_if_fail (now != NULL, NULL); - g_return_val_if_fail (time != NULL, NULL); - g_return_val_if_fail (settings != NULL, NULL); - - switch (get_date_proximity (now, time)) - { - case DATE_PROXIMITY_TODAY: - show_day = FALSE; - show_date = FALSE; - break; - - case DATE_PROXIMITY_TOMORROW: - case DATE_PROXIMITY_WEEK: - show_day = FALSE; - show_date = TRUE; - break; - - default: - show_day = TRUE; - show_date = TRUE; - break; - } - - return generate_full_format_string (show_day, show_date, FALSE, settings); -} - diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..e97b654 --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,154 @@ +/* -*- Mode: C; coding: utf-8; indent-tabs-mode: nil; tab-width: 2 -*- + +A dialog for setting time and date preferences. + +Copyright 2010 Canonical Ltd. + +Authors: + Michael Terry <michael.terry@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 <datetime/utils.h> + +#include <datetime/clock.h> +#include <datetime/clock-mock.h> +#include <datetime/formatter.h> +#include <datetime/settings-live.h> + +#include <glib.h> + +#include <locale.h> +#include <langinfo.h> +#include <string.h> + +/* Check the system locale setting to see if the format is 24-hour + time or 12-hour time */ +gboolean +is_locale_12h() +{ + static const char *formats_24h[] = {"%H", "%R", "%T", "%OH", "%k"}; + const auto t_fmt = nl_langinfo(T_FMT); + + for (const auto& needle : formats_24h) + if (strstr(t_fmt, needle) != nullptr) + return false; + + return true; +} + +void +split_settings_location(const gchar* location, gchar** zone, gchar** name) +{ + auto location_dup = g_strdup(location); + if(location_dup != nullptr) + g_strstrip(location_dup); + + gchar* first; + if(location_dup && (first = strchr(location_dup, ' '))) + *first = '\0'; + + if(zone) + *zone = location_dup; + + if(name != nullptr) + { + gchar* after = first ? g_strstrip(first + 1) : nullptr; + + if(after && *after) + { + *name = g_strdup(after); + } + else if (location_dup) // make the name from zone + { + gchar * chr = strrchr(location_dup, '/'); + after = g_strdup(chr ? chr + 1 : location_dup); + + // replace underscores with spaces + for(chr=after; chr && *chr; chr++) + if(*chr == '_') + *chr = ' '; + + *name = after; + } + else + { + *name = nullptr; + } + } +} + +/** + * Our Locations come from two places: (1) direct user input and (2) ones + * guessed by the system, such as from geoclue or timedate1. + * + * Since the latter only have a timezone (eg, "America/Chicago") and the + * former have a descriptive name provided by the end user (eg, + * "America/Chicago Oklahoma City"), this function tries to make a + * more human-readable name by using the user-provided name if the guessed + * timezone matches the last one the user manually clicked on. + * + * In the example above, this allows the menuitem for the system-guessed + * timezone ("America/Chicago") to read "Oklahoma City" after the user clicks + * on the "Oklahoma City" menuitem. + */ +gchar* +get_beautified_timezone_name(const char* timezone_, const char* saved_location) +{ + gchar* zone; + gchar* name; + split_settings_location(timezone_, &zone, &name); + + gchar* saved_zone; + gchar* saved_name; + split_settings_location(saved_location, &saved_zone, &saved_name); + + gchar* rv; + if (g_strcmp0(zone, saved_zone) == 0) + { + rv = saved_name; + saved_name = nullptr; + } + else + { + rv = name; + name = nullptr; + } + + g_free(zone); + g_free(name); + g_free(saved_zone); + g_free(saved_name); + return rv; +} + +gchar* +get_timezone_name(const gchar* timezone_, GSettings* settings) +{ + auto saved_location = g_settings_get_string(settings, SETTINGS_TIMEZONE_NAME_S); + auto rv = get_beautified_timezone_name(timezone_, saved_location); + g_free(saved_location); + return rv; +} + +using namespace unity::indicator::datetime; + +gchar* generate_full_format_string_at_time(GDateTime* now, GDateTime* then) +{ + std::shared_ptr<Clock> clock(new MockClock(DateTime(now))); + std::shared_ptr<Settings> settings(new LiveSettings); + DesktopFormatter formatter(clock, settings); + return g_strdup(formatter.getRelativeFormat(then).c_str()); +} + diff --git a/src/utils.h b/src/utils.h deleted file mode 100644 index 5eacce5..0000000 --- a/src/utils.h +++ /dev/null @@ -1,66 +0,0 @@ -/* -*- Mode: C; coding: utf-8; indent-tabs-mode: nil; tab-width: 2 -*- - -A dialog for setting time and date preferences. - -Copyright 2010 Canonical Ltd. - -Authors: - Michael Terry <michael.terry@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/>. -*/ - -#ifndef __DATETIME_UTILS_H__ -#define __DATETIME_UTILS_H__ - -#include <glib.h> -#include <gio/gio.h> /* GSettings */ - -G_BEGIN_DECLS - -gboolean is_locale_12h (void); - -void split_settings_location (const char * location, - char ** zone, - char ** name); - -gchar * get_current_zone_name (const char * location, - GSettings * settings); - -gchar* join_date_and_time_format_strings (const char * date_fmt, - const char * time_fmt); -/*** -**** -***/ - -const gchar * get_terse_time_format_string (GDateTime * time); - -const gchar * get_terse_header_time_format_string (void); - -const gchar * get_full_time_format_string (GSettings * settings); - -gchar * generate_terse_format_string_at_time (GDateTime * now, - GDateTime * time); - -gchar * generate_full_format_string (gboolean show_day, - gboolean show_date, - gboolean show_year, - GSettings * settings); - -gchar * generate_full_format_string_at_time (GDateTime * now, - GDateTime * time, - GSettings * settings); - -G_END_DECLS - -#endif |