diff options
102 files changed, 8579 insertions, 6231 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index fc8a639..53a3f7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,8 +40,9 @@ pkg_check_modules (SERVICE_DEPS REQUIRED libedataserver-1.2>=3.5 libnotify>=0.7.6 url-dispatcher-1>=1 + properties-cpp>=0.0.1 json-glib-1.0>=0.16.2) -include_directories (${SERVICE_INCLUDE_DIRS}) +include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS}) pkg_check_modules (PANEL_DEPS glib-2.0>=2.36 @@ -86,12 +87,14 @@ add_custom_target (cppcheck COMMAND cppcheck --enable=all -q --error-exitcode=2 ## set (CC_WARNING_ARGS " -Wall -Wshadow -Wextra -Wunused -Wformat=2 -Wno-missing-field-initializers") +set (CXX_WARNING_ARGS " -Wall -Wextra -pedantic -Wno-missing-field-initializers") -include_directories (${CMAKE_CURRENT_SOURCE_DIR}/src) -include_directories (${CMAKE_CURRENT_BINARY_DIR}/src) +include_directories (${CMAKE_CURRENT_SOURCE_DIR}/include) +include_directories (${CMAKE_CURRENT_BINARY_DIR}/include) # testing & coverage if (${enable_tests}) + pkg_check_modules (DBUSTEST REQUIRED dbustest-1>=14.04.0) set (GTEST_SOURCE_DIR /usr/src/gtest/src) set (GTEST_INCLUDE_DIR ${GTEST_SOURCE_DIR}/..) set (GTEST_LIBS -lpthread) @@ -102,16 +105,17 @@ if (${enable_tests}) endif () # actually build things -add_subdirectory (src) +add_subdirectory(include) +add_subdirectory(src) if (BUILD_PANEL) add_subdirectory (panel-gnome) endif () if (BUILD_UNITY_PANEL) add_subdirectory (panel-unity) endif () -add_subdirectory (data) -add_subdirectory (po) +add_subdirectory(data) +add_subdirectory(po) if (${enable_tests}) - add_subdirectory (tests) + add_subdirectory(tests) endif () @@ -68,3 +68,56 @@ CUSTOM MENUITEMS - x-canonical-time-format s strftime format string + +CODE +==== + +Model + + The app's model is represented by the "State" class, and "Menu" objects + are the corresponding views. "State" is a simple container for various + properties, and menus connect to those properties' changed() signals to + know when the view needs to be refreshed. + + As one can see in main.c, the app's very simple flow is to instantiate + a state and its properties, build menus that correspond to the state, + and export the menus on DBus. + + Because State is a simple aggregate of its components (such as a "Clock" + or "Planner" object to get the current time and upcoming appointments, + respectively), one can plug in live components for production and mock + components for unit tests. The entire backend can be mix-and-matched by + adding the desired test-or-production components. + + Start with: + include/datetime/state.h + include/datetime/clock.h + include/datetime/locations.h + include/datetime/planner.h + include/datetime/settings.h + include/datetime/timezones.h + + Implementations: + include/datetime/settings-live.h + include/datetime/locations-settings.h + include/datetime/planner-eds.h + include/datetime/timezones-live.h + +View + + Menu is a mostly-opaque class to wrap GMenu code. Its subclasses contain + the per-profile logic of which sections/menuitems to show and which to hide. + Menus are instantiated via the MenuFactory, which takes a state and profile. + + Actions is a mostly-opaque class to wrap our GActionGroup. Its subclasses + contain the code that actually executed when an action is triggered (ie, + LiveActions for production and MockActions for testing). + + Exporter exports the Actions and Menus onto the DBus, and also emits a + signal if/when the busname is lost so indicator-datetime-service knows + when to exit. + + include/datetime/menu.h + include/datetime/actions.h + include/datetime/exporter.h + diff --git a/cmake/GCov.cmake b/cmake/GCov.cmake index 8df10ee..81c0c40 100644 --- a/cmake/GCov.cmake +++ b/cmake/GCov.cmake @@ -29,7 +29,8 @@ if (CMAKE_BUILD_TYPE MATCHES coverage) WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMAND "${CMAKE_CTEST_COMMAND}" --force-new-ctest-process --verbose COMMAND "${LCOV_EXECUTABLE}" --directory ${CMAKE_BINARY_DIR} --capture | ${CMAKE_SOURCE_DIR}/trim-lcov.py > dconf-lcov.info - COMMAND LANG=C "${GENHTML_EXECUTABLE}" --prefix ${CMAKE_BINARY_DIR} --output-directory lcov-html --legend --show-details dconf-lcov.info + COMMAND "${LCOV_EXECUTABLE}" -r dconf-lcov.info /usr/include/\\* -o nosys-lcov.info + COMMAND LANG=C "${GENHTML_EXECUTABLE}" --prefix ${CMAKE_BINARY_DIR} --output-directory lcov-html --legend --show-details nosys-lcov.info COMMAND ${CMAKE_COMMAND} -E echo "" COMMAND ${CMAKE_COMMAND} -E echo "file://${CMAKE_BINARY_DIR}/lcov-html/index.html" COMMAND ${CMAKE_COMMAND} -E echo "") diff --git a/debian/changelog b/debian/changelog index d97de34..e3de384 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,21 @@ +indicator-datetime (13.10.0+14.04.20140131-0ubuntu1) trusty; urgency=low + + [ Charles Kerr ] + * Finally land this. Other, still open bugs will be fixed in + subsequent commits. (LP: #793450) + + [ Sebastien Bacher ] + * Use the correct location for datetime-dialog.ui. (LP: #1274046) + + [ Lars Uebernickel ] + * Set calendar to the current date when the menu is popped up This + makes use of the "submenu-action" attribute to get notified when the + menu is popped up. Charles, if your big refactoring-branch should + land before this, do you mind adapting this patch for that? Thanks . + (LP: #793450) + + -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 31 Jan 2014 11:46:16 +0000 + indicator-datetime (13.10.0+14.04.20140124-0ubuntu1) trusty; urgency=low [ Robert Ancell ] diff --git a/debian/control b/debian/control index 0b9d12b..175684f 100644 --- a/debian/control +++ b/debian/control @@ -5,6 +5,8 @@ Maintainer: Ubuntu Desktop Team <ubuntu-desktop@lists.ubuntu.com> # language-pack-en-base is for the unit tests s.t. we can test in 12h and 24h locales Build-Depends: cmake, dbus, + dbus-test-runner, + python3-dbusmock, debhelper (>= 9), dh-translations, intltool (>= 0.35.0), @@ -28,6 +30,8 @@ Build-Depends: cmake, libunity-control-center-dev, libtimezonemap1-dev, liburl-dispatcher1-dev, + libproperties-cpp-dev, + libdbustest1-dev, locales, Standards-Version: 3.9.3 Homepage: https://launchpad.net/indicator-datetime diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt new file mode 100644 index 0000000..486e9c7 --- /dev/null +++ b/include/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(datetime) diff --git a/include/datetime/CMakeLists.txt b/include/datetime/CMakeLists.txt new file mode 100644 index 0000000..139597f --- /dev/null +++ b/include/datetime/CMakeLists.txt @@ -0,0 +1,2 @@ + + diff --git a/include/datetime/actions-live.h b/include/datetime/actions-live.h new file mode 100644 index 0000000..3607836 --- /dev/null +++ b/include/datetime/actions-live.h @@ -0,0 +1,60 @@ +/* + * 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> + */ + +#ifndef INDICATOR_DATETIME_ACTIONS_LIVE_H +#define INDICATOR_DATETIME_ACTIONS_LIVE_H + +#include <datetime/actions.h> + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief Production implementation of the Actions interface. + * + * Delegates URLs, sets the timezone via org.freedesktop.timedate1, etc. + * + * @see MockActions + */ +class LiveActions: public Actions +{ +public: + LiveActions(const std::shared_ptr<State>& state_in); + ~LiveActions() =default; + + void open_desktop_settings(); + void open_phone_settings(); + void open_phone_clock_app(); + void open_planner(); + void open_planner_at(const DateTime&); + void open_appointment(const std::string& uid); + void set_location(const std::string& zone, const std::string& name); + void set_calendar_date(const DateTime&); + +protected: + virtual void execute_command(const std::string& command); + virtual void dispatch_url(const std::string& url); +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_ACTIONS_H diff --git a/include/datetime/actions.h b/include/datetime/actions.h new file mode 100644 index 0000000..99e78f5 --- /dev/null +++ b/include/datetime/actions.h @@ -0,0 +1,74 @@ +/* + * 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> + */ + +#ifndef INDICATOR_DATETIME_ACTIONS_H +#define INDICATOR_DATETIME_ACTIONS_H + +#include <datetime/date-time.h> +#include <datetime/state.h> + +#include <memory> // shared_ptr +#include <string> + +#include <gio/gio.h> // GSimpleActionGroup + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief Interface for all the actions that can be activated by users. + * + * This is a simple C++ wrapper around our GActionGroup that gets exported + * onto the bus. Subclasses implement the actual code that should be run + * when a particular action is triggered. + */ +class Actions +{ +public: + virtual void open_desktop_settings() =0; + virtual void open_phone_settings() =0; + virtual void open_phone_clock_app() =0; + virtual void open_planner() =0; + virtual void open_planner_at(const DateTime&) =0; + virtual void open_appointment(const std::string& uid) =0; + virtual void set_location(const std::string& zone, const std::string& name)=0; + void set_calendar_date(const DateTime&); + GActionGroup* action_group(); + const std::shared_ptr<State> state() const; + +protected: + Actions(const std::shared_ptr<State>& state); + virtual ~Actions(); + +private: + std::shared_ptr<State> m_state; + GSimpleActionGroup* m_actions = nullptr; + void update_calendar_state(); + + // we've got raw pointers in here, so disable copying + Actions(const Actions&) =delete; + Actions& operator=(const Actions&) =delete; +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_ACTIONS_H diff --git a/include/datetime/appointment.h b/include/datetime/appointment.h new file mode 100644 index 0000000..a5283c9 --- /dev/null +++ b/include/datetime/appointment.h @@ -0,0 +1,55 @@ +/* + * 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> + */ + +#ifndef INDICATOR_DATETIME_APPOINTMENT_H +#define INDICATOR_DATETIME_APPOINTMENT_H + +#include <datetime/date-time.h> +#include <string> + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief Plain Old Data Structure that represents a calendar appointment. + * + * @see Planner + */ +struct Appointment +{ +public: + std::string color; + std::string summary; + std::string url; + std::string uid; + bool is_event = false; + bool is_daily = false; + bool has_alarms = false; + DateTime begin; + DateTime end; + + bool operator== (const Appointment& that) const; +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_APPOINTMENT_H diff --git a/include/datetime/clock-mock.h b/include/datetime/clock-mock.h new file mode 100644 index 0000000..fb9b52f --- /dev/null +++ b/include/datetime/clock-mock.h @@ -0,0 +1,61 @@ +/* + * 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> + */ + +#ifndef INDICATOR_DATETIME_CLOCK_MOCK_H +#define INDICATOR_DATETIME_CLOCK_MOCK_H + +#include <datetime/clock.h> + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +/** + * \brief A clock that uses a client-provided time instead of the system time. + */ +class MockClock: public Clock +{ +public: + MockClock(const DateTime& dt): m_localtime(dt) {} + ~MockClock() =default; + + DateTime localtime() const { return m_localtime; } + + void set_localtime(const DateTime& dt) { + const auto old = m_localtime; + m_localtime = dt; + if (!DateTime::is_same_minute(old, m_localtime)) + minute_changed(); + if (!DateTime::is_same_day(old, m_localtime)) + date_changed(); + } + +private: + DateTime m_localtime; +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_CLOCK_MOCK_H diff --git a/include/datetime/clock.h b/include/datetime/clock.h new file mode 100644 index 0000000..1d488d1 --- /dev/null +++ b/include/datetime/clock.h @@ -0,0 +1,99 @@ +/* + * 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> + */ + +#ifndef INDICATOR_DATETIME_CLOCK_H +#define INDICATOR_DATETIME_CLOCK_H + +#include <datetime/date-time.h> + +#include <core/property.h> +#include <core/signal.h> + +#include <gio/gio.h> // GDBusConnection + +#include <memory> // std::shared_ptr, std::unique_ptr + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief A clock. + */ +class Clock +{ +public: + virtual ~Clock(); + virtual DateTime localtime() const =0; + + /** \brief A signal which fires when the clock's minute changes */ + core::Signal<> minute_changed; + + /** \brief A signal which fires when the clock's date changes */ + core::Signal<> date_changed; + +protected: + Clock(); + + /** \brief Compares old and new times, emits minute_changed() or date_changed() signals if appropriate */ + void maybe_emit (const DateTime& a, const DateTime& b); + +private: + static void on_system_bus_ready(GObject*, GAsyncResult*, gpointer); + static void on_prepare_for_sleep(GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar*, GVariant*, gpointer); + + GCancellable * m_cancellable = nullptr; + GDBusConnection * m_system_bus = nullptr; + unsigned int m_sleep_subscription_id = 0; + + // we've got raw pointers and GSignal tags in here, so disable copying + Clock(const Clock&) =delete; + Clock& operator=(const Clock&) =delete; +}; + +/*** +**** +***/ + +class Timezones; + +/** + * \brief A live #Clock that provides the actual system time. + */ +class LiveClock: public Clock +{ +public: + LiveClock (const std::shared_ptr<const Timezones>& zones); + virtual ~LiveClock(); + virtual DateTime localtime() const; + +private: + class Impl; + std::unique_ptr<Impl> p; +}; + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_CLOCK_H diff --git a/include/datetime/date-time.h b/include/datetime/date-time.h new file mode 100644 index 0000000..2ad7856 --- /dev/null +++ b/include/datetime/date-time.h @@ -0,0 +1,68 @@ +/* + * 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> + */ + +#ifndef INDICATOR_DATETIME_DATETIME_H +#define INDICATOR_DATETIME_DATETIME_H + +#include <glib.h> // GDateTime + +#include <ctime> // time_t +#include <memory> // std::shared_ptr + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief A simple C++ wrapper for GDateTime to simplify ownership/refcounts + */ +class DateTime +{ +public: + static DateTime NowLocal(); + explicit DateTime(time_t t); + explicit DateTime(GDateTime* in=nullptr); + DateTime& operator=(GDateTime* in); + DateTime& operator=(const DateTime& in); + DateTime to_timezone(const std::string& zone) const; + void reset(GDateTime* in=nullptr); + + GDateTime* get() const; + GDateTime* operator()() const {return get();} + + std::string format(const std::string& fmt) const; + int day_of_month() const; + int64_t to_unix() const; + + bool operator<(const DateTime& that) const; + bool operator!=(const DateTime& that) const; + bool operator==(const DateTime& that) const; + + static bool is_same_day(const DateTime& a, const DateTime& b); + static bool is_same_minute(const DateTime& a, const DateTime& b); + +private: + std::shared_ptr<GDateTime> m_dt; +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_DATETIME_H diff --git a/include/datetime/dbus-shared.h b/include/datetime/dbus-shared.h new file mode 100644 index 0000000..c5ff6ab --- /dev/null +++ b/include/datetime/dbus-shared.h @@ -0,0 +1,25 @@ +/* + * 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: + * Ted Gould <ted@canonical.com> + * Charles Kerr <charles.kerr@canonical.com> + */ + + +#define BUS_NAME "com.canonical.indicator.datetime" + +#define BUS_PATH "/com/canonical/indicator/datetime" + diff --git a/include/datetime/exporter.h b/include/datetime/exporter.h new file mode 100644 index 0000000..c228cc1 --- /dev/null +++ b/include/datetime/exporter.h @@ -0,0 +1,74 @@ +/* + * 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> + */ + +#ifndef INDICATOR_DATETIME_EXPORTER_H +#define INDICATOR_DATETIME_EXPORTER_H + +#include <datetime/actions.h> +#include <datetime/menu.h> + +#include <core/signal.h> + +#include <gio/gio.h> // GActionGroup + +#include <memory> // std::shared_ptr +#include <vector> + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief Exports actions and menus to DBus. + */ +class Exporter +{ +public: + Exporter() =default; + ~Exporter(); + + core::Signal<> name_lost; + + void publish(const std::shared_ptr<Actions>& actions, + const std::vector<std::shared_ptr<Menu>>& menus); + +private: + static void on_bus_acquired(GDBusConnection*, const gchar *name, gpointer gthis); + void on_bus_acquired(GDBusConnection*, const gchar *name); + + static void on_name_lost(GDBusConnection*, const gchar *name, gpointer gthis); + void on_name_lost(GDBusConnection*, const gchar *name); + + std::set<guint> m_exported_menu_ids; + guint m_own_id = 0; + guint m_exported_actions_id = 0; + GDBusConnection * m_dbus_connection = nullptr; + std::shared_ptr<Actions> m_actions; + std::vector<std::shared_ptr<Menu>> m_menus; + + // we've got raw pointers and gsignal tags in here, so disable copying + Exporter(const Exporter&) =delete; + Exporter& operator=(const Exporter&) =delete; +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_EXPORTER_H diff --git a/include/datetime/formatter.h b/include/datetime/formatter.h new file mode 100644 index 0000000..0d695e2 --- /dev/null +++ b/include/datetime/formatter.h @@ -0,0 +1,138 @@ +/* + * 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> + */ + +#ifndef INDICATOR_DATETIME_FORMATTER_H +#define INDICATOR_DATETIME_FORMATTER_H + +#include <core/property.h> +#include <core/signal.h> + +#include <datetime/clock.h> +#include <datetime/settings.h> +#include <datetime/utils.h> // is_locale_12h() + +#include <glib.h> + +#include <string> +#include <memory> + +namespace unity { +namespace indicator { +namespace datetime { + +class Clock; +class DateTime; + +/*** +**** +***/ + +/** + * \brief Provide the strftime() format strings + * + * This is a simple goal, but getting there has a lot of options and edge cases: + * + * - The default time format can change based on the locale. + * + * - The user's settings can change or completely override the format string. + * + * - The time formats are different on the Phone and Desktop profiles. + * + * - The time format string in the Locations' menuitems uses (mostly) + * the same time format as the header, except for some changes. + * + * - The 'current time' format string in the Locations' menuitems also + * prepends the string 'Yesterday' or 'Today' if it differs from the + * local time, so Formatter needs to have a Clock for its state. + * + * So the Formatter monitors system settings, the current timezone, etc. + * and upate its time format properties appropriately. + */ +class Formatter +{ +public: + + /** \brief The time format string for the menu header */ + core::Property<std::string> header_format; + + /** \brief The time string for the menu header. (eg, the header_format + the clock's time */ + core::Property<std::string> header; + + /** \brief Signal to denote when the relativeFormat has changed. + When this is emitted, clients will want to rebuild their + menuitems that contain relative time strings + (ie, the Appointments and Locations menuitems) */ + core::Signal<> relative_format_changed; + + /** \brief Generate a relative time format for some time (or time range) + from the current clock's value. For example, a full-day interval + starting at the end of the current clock's day yields "Tomorrow" */ + std::string relative_format(GDateTime* then, GDateTime* then_end=nullptr) const; + +protected: + Formatter(const std::shared_ptr<const Clock>&); + virtual ~Formatter(); + + static const char* default_header_time_format(bool twelvehour, bool show_seconds); + +private: + + Formatter(const Formatter&) =delete; + Formatter& operator=(const Formatter&) =delete; + + class Impl; + std::unique_ptr<Impl> p; +}; + + +/** + * \brief A Formatter for the Desktop and DesktopGreeter profiles. + */ +class DesktopFormatter: public Formatter +{ +public: + DesktopFormatter(const std::shared_ptr<const Clock>&, const std::shared_ptr<const Settings>&); + +private: + std::shared_ptr<const Settings> m_settings; + + void rebuildHeaderFormat(); + const gchar* getFullTimeFormatString() const; + std::string getHeaderLabelFormatString() const; + const gchar* getDateFormat(bool show_day, bool show_date, bool show_year) const; + +}; + + +/** + * \brief A Formatter for Phone and PhoneGreeter profiles. + */ +class PhoneFormatter: public Formatter +{ +public: + PhoneFormatter(const std::shared_ptr<const Clock>& clock): Formatter(clock) { + header_format.set(default_header_time_format(is_locale_12h(), false)); + } +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_CLOCK_H diff --git a/include/datetime/locations-settings.h b/include/datetime/locations-settings.h new file mode 100644 index 0000000..8757f43 --- /dev/null +++ b/include/datetime/locations-settings.h @@ -0,0 +1,55 @@ +/* + * 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> + */ + +#ifndef INDICATOR_DATETIME_SETTINGS_LOCATIONS_H +#define INDICATOR_DATETIME_SETTINGS_LOCATIONS_H + +#include <datetime/locations.h> // base class + +#include <datetime/settings.h> +#include <datetime/timezones.h> + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief #Locations implementation which builds its list from the #Settings. + */ +class SettingsLocations: public Locations +{ +public: + /** + * @param[in] settings the #Settings whose locations property is to be used + * @param[in] timezones the #Timezones to always show first in the list + */ + SettingsLocations (const std::shared_ptr<const Settings>& settings, + const std::shared_ptr<const Timezones>& timezones); + +private: + std::shared_ptr<const Settings> m_settings; + std::shared_ptr<const Timezones> m_timezones; + void reload(); +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_SETTINGS_LOCATIONS_H diff --git a/include/datetime/locations.h b/include/datetime/locations.h new file mode 100644 index 0000000..b840436 --- /dev/null +++ b/include/datetime/locations.h @@ -0,0 +1,79 @@ +/* + * 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> + */ + +#ifndef INDICATOR_DATETIME_LOCATIONS_H +#define INDICATOR_DATETIME_LOCATIONS_H + +#include <datetime/date-time.h> + +#include <core/property.h> + +#include <string> +#include <vector> + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief A physical place and its timezone; eg, "America/Chicago" + "Oklahoma City" + * + * @see Locations + */ +class Location +{ +public: + Location (const std::string& zone, const std::string& name); + const std::string& zone() const; + const std::string& name() const; + bool operator== (const Location& that) const; + +private: + + /** timezone; eg, "America/Chicago" */ + std::string m_zone; + + /* human-readable location name; eg, "Oklahoma City" */ + std::string m_name; + + /** offset from UTC in microseconds */ + int64_t m_offset = 0; +}; + +/** + * Container which holds an ordered list of Locations + * + * @see Location + * @see State + */ +class Locations +{ +public: + Locations() =default; + virtual ~Locations() =default; + + /** \brief an ordered list of Location items */ + core::Property<std::vector<Location>> locations; +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_LOCATIONS_H diff --git a/include/datetime/menu.h b/include/datetime/menu.h new file mode 100644 index 0000000..7b351c3 --- /dev/null +++ b/include/datetime/menu.h @@ -0,0 +1,85 @@ +/* + * 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> + */ + +#ifndef INDICATOR_DATETIME_MENU_H +#define INDICATOR_DATETIME_MENU_H + +#include <datetime/actions.h> +#include <datetime/state.h> + +#include <memory> // std::shared_ptr +#include <vector> + +#include <gio/gio.h> // GMenuModel + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief A menu for a specific profile; eg, Desktop or Phone. + * + * @see MenuFactory + * @see Exporter + */ +class Menu +{ +public: + enum Profile { Desktop, DesktopGreeter, Phone, PhoneGreeter, NUM_PROFILES }; + enum Section { Calendar, Appointments, Locations, Settings, NUM_SECTIONS }; + const std::string& name() const; + Profile profile() const; + GMenuModel* menu_model(); + +protected: + Menu (Profile profile_in, const std::string& name_in); + virtual ~Menu() =default; + GMenu* m_menu = nullptr; + +private: + const Profile m_profile; + const std::string m_name; + + // we've got raw pointers in here, so disable copying + Menu(const Menu&) =delete; + Menu& operator=(const Menu&) =delete; +}; + +/** + * \brief Builds a Menu for a given state and profile + * + * @see Menu + * @see Exporter + */ +class MenuFactory +{ +public: + MenuFactory (const std::shared_ptr<Actions>& actions, const std::shared_ptr<const State>& state); + std::shared_ptr<Menu> buildMenu(Menu::Profile profile); + +private: + std::shared_ptr<Actions> m_actions; + std::shared_ptr<const State> m_state; +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_MENU_H diff --git a/include/datetime/planner-eds.h b/include/datetime/planner-eds.h new file mode 100644 index 0000000..f3abce0 --- /dev/null +++ b/include/datetime/planner-eds.h @@ -0,0 +1,49 @@ +/* + * 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> + */ + +#ifndef INDICATOR_DATETIME_PLANNER_EDS_H +#define INDICATOR_DATETIME_PLANNER_EDS_H + +#include <datetime/planner.h> + +#include <memory> // unique_ptr + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief Planner which uses EDS as its backend + */ +class PlannerEds: public Planner +{ +public: + PlannerEds(); + virtual ~PlannerEds(); + +private: + class Impl; + std::unique_ptr<Impl> p; +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_PLANNER_EDS_H diff --git a/include/datetime/planner.h b/include/datetime/planner.h new file mode 100644 index 0000000..376a31f --- /dev/null +++ b/include/datetime/planner.h @@ -0,0 +1,76 @@ +/* + * 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> + */ + +#ifndef INDICATOR_DATETIME_PLANNER_H +#define INDICATOR_DATETIME_PLANNER_H + +#include <datetime/appointment.h> +#include <datetime/date-time.h> + +#include <core/property.h> + +#include <vector> + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief Simple appointment book + * + * @see EdsPlanner + * @see State + */ +class Planner +{ +public: + virtual ~Planner() =default; + + /** + * \brief Timestamp used to determine the appointments in the `upcoming' and `this_month' properties. + * Setting this value will cause the planner to re-query its backend and + * update the `upcoming' and `this_month' properties. + */ + core::Property<DateTime> time; + + /** + * \brief The next few appointments that follow the time specified in the time property. + */ + core::Property<std::vector<Appointment>> upcoming; + + /** + * \brief The appointments that occur in the same month as the time property + */ + core::Property<std::vector<Appointment>> this_month; + +protected: + Planner() =default; + +private: + + // disable copying + Planner(const Planner&) =delete; + Planner& operator=(const Planner&) =delete; +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_PLANNER_H diff --git a/include/datetime/settings-live.h b/include/datetime/settings-live.h new file mode 100644 index 0000000..202c998 --- /dev/null +++ b/include/datetime/settings-live.h @@ -0,0 +1,70 @@ +/* + * 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> + */ + +#ifndef INDICATOR_DATETIME_SETTINGS_LIVE_H +#define INDICATOR_DATETIME_SETTINGS_LIVE_H + +#include <datetime/settings.h> // parent class + +#include <gio/gio.h> // GSettings + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief #Settings implementation which uses GSettings. + */ +class LiveSettings: public Settings +{ +public: + LiveSettings(); + virtual ~LiveSettings(); + +private: + static void on_changed(GSettings*, gchar*, gpointer); + void update_key(const std::string& key); + + void update_custom_time_format(); + void update_locations(); + void update_show_calendar(); + void update_show_clock(); + void update_show_date(); + void update_show_day(); + void update_show_detected_locations(); + void update_show_events(); + void update_show_locations(); + void update_show_seconds(); + void update_show_week_numbers(); + void update_show_year(); + void update_time_format_mode(); + void update_timezone_name(); + + GSettings* m_settings; + + // we've got a raw pointer here, so disable copying + LiveSettings(const LiveSettings&) =delete; + LiveSettings& operator=(const LiveSettings&) =delete; +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_SETTINGS_LIVE_H diff --git a/src/settings-shared.h b/include/datetime/settings-shared.h index 4615fe8..17a8ef0 100644 --- a/src/settings-shared.h +++ b/include/datetime/settings-shared.h @@ -1,26 +1,25 @@ /* -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__ + * Copyright 2010 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: + * Ted Gould <ted@canonical.com> + * Charles Kerr <charles.kerr@canonical.com> + */ + +#ifndef INDICATOR_DATETIME_SETTINGS_SHARED +#define INDICATOR_DATETIME_SETTINGS_SHARED typedef enum { @@ -47,4 +46,4 @@ TimeFormatMode; #define SETTINGS_LOCATIONS_S "locations" #define SETTINGS_TIMEZONE_NAME_S "timezone-name" -#endif +#endif // INDICATOR_DATETIME_SETTINGS_SHARED diff --git a/include/datetime/settings.h b/include/datetime/settings.h new file mode 100644 index 0000000..ce234d9 --- /dev/null +++ b/include/datetime/settings.h @@ -0,0 +1,65 @@ +/* + * 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> + */ + +#ifndef INDICATOR_DATETIME_SETTINGS_H +#define INDICATOR_DATETIME_SETTINGS_H + +#include <datetime/settings-shared.h> + +#include <core/property.h> + +#include <vector> + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief Interface that represents user-configurable settings. + * + * See the descriptions in data/com.canonical.indicator.datetime.gschema.xml + * for more information on specific properties. + */ +class Settings +{ +public: + Settings() =default; + virtual ~Settings() =default; + + core::Property<std::string> custom_time_format; + core::Property<std::vector<std::string>> locations; + core::Property<bool> show_calendar; + core::Property<bool> show_clock; + core::Property<bool> show_date; + core::Property<bool> show_day; + core::Property<bool> show_detected_location; + core::Property<bool> show_events; + core::Property<bool> show_locations; + core::Property<bool> show_seconds; + core::Property<bool> show_week_numbers; + core::Property<bool> show_year; + core::Property<TimeFormatMode> time_format_mode; + core::Property<std::string> timezone_name; +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_SETTINGS_H diff --git a/include/datetime/state.h b/include/datetime/state.h new file mode 100644 index 0000000..414be32 --- /dev/null +++ b/include/datetime/state.h @@ -0,0 +1,75 @@ +/* + * 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> + */ + +#ifndef INDICATOR_DATETIME_STATE_H +#define INDICATOR_DATETIME_STATE_H + +#include <datetime/clock.h> +#include <datetime/locations.h> +#include <datetime/planner.h> +#include <datetime/settings.h> +#include <datetime/timezones.h> + +#include <core/property.h> + +#include <memory> // std::shared_ptr + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief Aggregates all the classes that represent the backend state. + * + * This is where the app comes together. It's a model that aggregates + * all of the backend appointments/alarms, locations, timezones, + * system time, and so on. The "view" code (ie, the Menus) need to + * respond to Signals from the State and update themselves accordingly. + * + * @see Menu + * @see MenuFactory + * @see Timezones + * @see Clock + * @see Planner + * @see Locations + * @see Settings + */ +struct State +{ + /** \brief The current time. Used by the header, by the date menuitem, + and by the locations for relative timestamp */ + std::shared_ptr<Clock> clock; + + /** \brief The locations to be displayed in the Locations + section of the #Menu */ + std::shared_ptr<Locations> locations; + + /** \brief The appointments to be displayed in the Calendar and + Appointments sections of the #Menu */ + std::shared_ptr<Planner> planner; + + /** \brief Configuration options that modify the view */ + std::shared_ptr<Settings> settings; +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_STATE_H diff --git a/include/datetime/timezone-file.h b/include/datetime/timezone-file.h new file mode 100644 index 0000000..d77aaae --- /dev/null +++ b/include/datetime/timezone-file.h @@ -0,0 +1,63 @@ +/* + * 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> + */ + +#ifndef INDICATOR_DATETIME_FILE_TIMEZONE_H +#define INDICATOR_DATETIME_FILE_TIMEZONE_H + +#include <datetime/timezone.h> // base class + +#include <string> // std::string + +#include <glib.h> +#include <gio/gio.h> + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief A #Timezone that gets its information from monitoring a file, such as /etc/timezone + */ +class FileTimezone: public Timezone +{ +public: + FileTimezone(); + FileTimezone(const std::string& filename); + ~FileTimezone(); + +private: + void setFilename(const std::string& filename); + static void onFileChanged(gpointer gself); + void clear(); + void reload(); + + std::string m_filename; + GFileMonitor * m_monitor = nullptr; + unsigned long m_monitor_handler_id = 0; + + // we have raw pointers and glib tags in here, so disable copying + FileTimezone(const FileTimezone&) =delete; + FileTimezone& operator=(const FileTimezone&) =delete; +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_FILE_TIMEZONE_H diff --git a/include/datetime/timezone-geoclue.h b/include/datetime/timezone-geoclue.h new file mode 100644 index 0000000..4a5b726 --- /dev/null +++ b/include/datetime/timezone-geoclue.h @@ -0,0 +1,69 @@ +/* + * 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> + */ + +#ifndef INDICATOR_DATETIME_GEOCLUE_TIMEZONE_H +#define INDICATOR_DATETIME_GEOCLUE_TIMEZONE_H + +#include <datetime/timezone.h> // base class + +#include <string> + +#include <glib.h> +#include <gio/gio.h> + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief A #Timezone that gets its information from asking GeoClue + */ +class GeoclueTimezone: public Timezone +{ +public: + GeoclueTimezone(); + ~GeoclueTimezone(); + +private: + static void on_bus_got (GObject*, GAsyncResult*, gpointer); + static void on_client_created (GObject*, GAsyncResult*, gpointer); + static void on_address_changed (GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar*, GVariant*, gpointer); + static void on_requirements_set (GObject*, GAsyncResult*, gpointer); + static void on_address_started (GObject*, GAsyncResult*, gpointer); + static void on_address_got (GObject*, GAsyncResult*, gpointer); + void setTimezoneFromAddressVariant (GVariant*); + static GVariant * call_finish (GObject*, GAsyncResult*); + + GCancellable * m_cancellable = nullptr; + GDBusConnection * m_connection = nullptr; + std::string m_client_object_path; + guint m_signal_subscription = 0; + + // we've got pointers and gsignal tags in here, so don't allow copying + GeoclueTimezone(const GeoclueTimezone&) =delete; + GeoclueTimezone& operator=(const GeoclueTimezone&) =delete; +}; + + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_GEOCLUE_TIMEZONE_H + diff --git a/include/datetime/timezone.h b/include/datetime/timezone.h new file mode 100644 index 0000000..7d2ace8 --- /dev/null +++ b/include/datetime/timezone.h @@ -0,0 +1,45 @@ +/* + * 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> + */ + +#ifndef INDICATOR_DATETIME_TIMEZONE_H +#define INDICATOR_DATETIME_TIMEZONE_H + +#include <core/property.h> + +#include <string> + +namespace unity { +namespace indicator { +namespace datetime { + +/** \brief Base a timezone, such as "America/Chicago". */ +class Timezone +{ +protected: + Timezone() =default; + +public: + core::Property<std::string> timezone; +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_TIMEZONE_H diff --git a/include/datetime/timezones-live.h b/include/datetime/timezones-live.h new file mode 100644 index 0000000..ca4ef31 --- /dev/null +++ b/include/datetime/timezones-live.h @@ -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> + */ + +#ifndef INDICATOR_DATETIME_LIVE_TIMEZONES_H +#define INDICATOR_DATETIME_LIVE_TIMEZONES_H + +#include <datetime/settings.h> +#include <datetime/timezones.h> +#include <datetime/timezone-file.h> +#include <datetime/timezone-geoclue.h> + +#include <memory> // shared_ptr<> + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief #Timezones object that uses a #FileTimezone and #GeoclueTimezone + * to detect what timezone we're in + */ +class LiveTimezones: public Timezones +{ +public: + LiveTimezones(const std::shared_ptr<const Settings>& settings, const std::string& filename); + +private: + void update_geolocation(); + void update_timezones(); + + FileTimezone m_file; + std::shared_ptr<const Settings> m_settings; + std::shared_ptr<GeoclueTimezone> m_geo; +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_LIVE_TIMEZONES_H diff --git a/include/datetime/timezones.h b/include/datetime/timezones.h new file mode 100644 index 0000000..d2842af --- /dev/null +++ b/include/datetime/timezones.h @@ -0,0 +1,59 @@ +/* + * 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> + */ + +#ifndef INDICATOR_DATETIME_TIMEZONES_H +#define INDICATOR_DATETIME_TIMEZONES_H + +#include <datetime/timezone.h> + +#include <core/property.h> + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief Helper class which aggregates one or more timezones + * + * @see LiveClock + * @see SettingsLocations + */ +class Timezones +{ +public: + Timezones() =default; + virtual ~Timezones() =default; + + /** + * \brief the current timezone + */ + core::Property<std::string> timezone; + + /** + * \brief all the detected timezones. + * The count is >1 iff the detection mechamisms disagree. + */ + core::Property<std::set<std::string> > timezones; +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_TIMEZONES_H diff --git a/include/datetime/utils.h b/include/datetime/utils.h new file mode 100644 index 0000000..7cac9fd --- /dev/null +++ b/include/datetime/utils.h @@ -0,0 +1,54 @@ +/* + * Copyright 2010, 2014 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: + * Michael Terry <michael.terry@canonical.com> + * Charles Kerr <charles.kerr@canonical.com> + */ + +#ifndef INDICATOR_DATETIME_UTILS_H +#define INDICATOR_DATETIME_UTILS_H + +#include <glib.h> +#include <gio/gio.h> /* GSettings */ + +G_BEGIN_DECLS + +/** \brief Returns true if the current locale prefers 12h display instead of 24h */ +gboolean is_locale_12h (void); + +void split_settings_location (const char * location, + char ** zone, + char ** name); + +gchar * get_timezone_name (const char * timezone, + GSettings * settings); + +gchar * get_beautified_timezone_name (const char * timezone, + const char * saved_location); + +gchar * generate_full_format_string_at_time (GDateTime * now, + GDateTime * then_begin, + GDateTime * then_end); + +/** \brief Translate the string based on LC_TIME instead of LC_MESSAGES. + The intent of this is to let users set LC_TIME to override + their other locale settings when generating time format string */ +const char* T_ (const char * msg); + + +G_END_DECLS + +#endif /* INDICATOR_DATETIME_UTILS_H */ diff --git a/panel-gnome/CMakeLists.txt b/panel-gnome/CMakeLists.txt index 9be4cf5..82c511d 100644 --- a/panel-gnome/CMakeLists.txt +++ b/panel-gnome/CMakeLists.txt @@ -2,16 +2,20 @@ set (PANEL_LIB "gnome-indicator-datetime") add_definitions (-DPKGDATADIR="${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME}") + +SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -g") +SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g ${CXX_WARNING_ARGS}") + add_library (${PANEL_LIB} SHARED ${CMAKE_SOURCE_DIR}/panel/datetime-prefs.c ${CMAKE_SOURCE_DIR}/panel/datetime-prefs-locations.c ${CMAKE_SOURCE_DIR}/panel/datetime-prefs-locations.h ${CMAKE_SOURCE_DIR}/src/utils.c - ${CMAKE_SOURCE_DIR}/src/utils.h - ${CMAKE_SOURCE_DIR}/src/settings-shared.h) + ${CMAKE_SOURCE_DIR}/include/datetime/utils.h + ${CMAKE_SOURCE_DIR}/include/datetime/settings-shared.h) set_property (TARGET ${PANEL_LIB} PROPERTY OUTPUT_NAME indicator-datetime) -include_directories (${PANEL_DEPS_INCLUDE_DIRS}) +include_directories (SYSTEM ${PANEL_DEPS_INCLUDE_DIRS}) link_directories (${PANEL_DEPS_LIBRARY_DIRS}) diff --git a/panel-unity/CMakeLists.txt b/panel-unity/CMakeLists.txt index f710006..c8ebde0 100644 --- a/panel-unity/CMakeLists.txt +++ b/panel-unity/CMakeLists.txt @@ -2,16 +2,19 @@ set (PANEL_LIB "unity-indicator-datetime") add_definitions (-DPKGDATADIR="${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME}") +SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -g") +SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g ${CXX_WARNING_ARGS}") + add_library (${PANEL_LIB} SHARED ${CMAKE_SOURCE_DIR}/panel/datetime-prefs.c ${CMAKE_SOURCE_DIR}/panel/datetime-prefs-locations.c ${CMAKE_SOURCE_DIR}/panel/datetime-prefs-locations.h ${CMAKE_SOURCE_DIR}/src/utils.c - ${CMAKE_SOURCE_DIR}/src/utils.h - ${CMAKE_SOURCE_DIR}/src/settings-shared.h) + ${CMAKE_SOURCE_DIR}/include/datetime/utils.h + ${CMAKE_SOURCE_DIR}/include/datetime/settings-shared.h) set_property (TARGET ${PANEL_LIB} PROPERTY OUTPUT_NAME indicator-datetime) -include_directories (${UNITY_PANEL_DEPS_INCLUDE_DIRS}) +include_directories (SYSTEM ${UNITY_PANEL_DEPS_INCLUDE_DIRS}) link_directories (${UNITY_PANEL_DEPS_LIBRARY_DIRS}) diff --git a/panel/datetime-prefs-locations.c b/panel/datetime-prefs-locations.c index 933adcf..8cd81fb 100644 --- a/panel/datetime-prefs-locations.c +++ b/panel/datetime-prefs-locations.c @@ -24,20 +24,23 @@ with this program. If not, see <http://www.gnu.org/licenses/>. #include "config.h" #endif -#include <stdlib.h> -#include <time.h> /* time_t */ +#include "datetime-prefs-locations.h" + +#include <datetime/settings-shared.h> +#include <datetime/utils.h> + +#include <timezonemap/timezone-completion.h> + #include <glib/gi18n-lib.h> #include <gtk/gtk.h> -#include <timezonemap/timezone-completion.h> -#include "datetime-prefs-locations.h" -#include "settings-shared.h" -#include "utils.h" +#include <stdlib.h> +#include <time.h> /* time_t */ #if USE_UNITY -#define DATETIME_DIALOG_UI_FILE PKGDATADIR "/unity-control-center/datetime-dialog.ui" + #define DATETIME_DIALOG_UI_FILE PKGDATADIR "/unity-control-center/datetime-dialog.ui" #else -#define DATETIME_DIALOG_UI_FILE PKGDATADIR "/gnome-control-center/datetime-dialog.ui" + #define DATETIME_DIALOG_UI_FILE PKGDATADIR "/gnome-control-center/datetime-dialog.ui" #endif #define COL_NAME 0 @@ -314,7 +317,7 @@ handle_edit (GtkCellRendererText * renderer G_GNUC_UNUSED, gtk_list_store_set (store, &iter, COL_VISIBLE_NAME, new_text, - COL_ICON, correct ? NULL : GTK_STOCK_DIALOG_ERROR, + COL_ICON, correct ? NULL : "dialog-error", -1); } } @@ -429,7 +432,6 @@ update_times (GtkWidget * dlg) g_signal_handlers_block_by_func (store, save_when_idle, dlg); - GSettings * settings = g_settings_new (SETTINGS_INTERFACE); GtkTreeIter iter; if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) { GDateTime * now = g_date_time_new_now_local (); @@ -441,7 +443,7 @@ update_times (GtkWidget * dlg) if (strzone && *strzone) { GTimeZone * tz = g_time_zone_new (strzone); GDateTime * now_tz = g_date_time_to_timezone (now, tz); - gchar * format = generate_full_format_string_at_time (now, now_tz, settings); + gchar * format = generate_full_format_string_at_time (now, now_tz, NULL); gchar * time_str = g_date_time_format (now_tz, format); gchar * old_time_str; @@ -460,8 +462,6 @@ update_times (GtkWidget * dlg) g_date_time_unref (now); } - g_object_unref (settings); - g_signal_handlers_unblock_by_func (store, save_when_idle, dlg); return TRUE; diff --git a/panel/datetime-prefs.c b/panel/datetime-prefs.c index 14e7e97..7808fa7 100644 --- a/panel/datetime-prefs.c +++ b/panel/datetime-prefs.c @@ -25,26 +25,30 @@ with this program. If not, see <http://www.gnu.org/licenses/>. #include "config.h" #endif -#include <stdlib.h> -#include <libintl.h> -#include <locale.h> -#include <langinfo.h> +#include "datetime-prefs-locations.h" + +#include <datetime/dbus-shared.h> +#include <datetime/settings-shared.h> +#include <datetime/utils.h> + +#include <polkit/polkit.h> +#include <timezonemap/cc-timezone-map.h> +#include <timezonemap/timezone-completion.h> + #include <glib/gi18n-lib.h> #include <gdk/gdkkeysyms.h> #include <gtk/gtk.h> -#include <polkit/polkit.h> -#if USE_UNITY -#include <libunity-control-center/cc-panel.h> + +#ifdef USE_UNITY + #include <libunity-control-center/cc-panel.h> #else -#include <libgnome-control-center/cc-panel.h> + #include <libgnome-control-center/cc-panel.h> #endif -#include <timezonemap/cc-timezone-map.h> -#include <timezonemap/timezone-completion.h> -#include "dbus-shared.h" -#include "settings-shared.h" -#include "utils.h" -#include "datetime-prefs-locations.h" +#include <stdlib.h> +#include <libintl.h> +#include <locale.h> +#include <langinfo.h> #if USE_UNITY #define DATETIME_DIALOG_UI_FILE PKGDATADIR "/unity-control-center/datetime-dialog.ui" @@ -225,12 +229,12 @@ toggle_ntp (GtkWidget * radio, GParamSpec * pspec G_GNUC_UNUSED, IndicatorDateti static void sync_entry (IndicatorDatetimePanel * self, const gchar * location) { - gchar * name = get_current_zone_name (location, self->priv->settings); + gchar * name = get_timezone_name (location, self->priv->settings); gtk_entry_set_text (GTK_ENTRY (self->priv->tz_entry), name); g_free (name); - gtk_entry_set_icon_from_stock (GTK_ENTRY (self->priv->tz_entry), - GTK_ENTRY_ICON_SECONDARY, NULL); + gtk_entry_set_icon_from_icon_name (GTK_ENTRY (self->priv->tz_entry), + GTK_ENTRY_ICON_SECONDARY, NULL); } static void @@ -295,10 +299,10 @@ proxy_ready (GObject *object G_GNUC_UNUSED, { if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) { - const gchar *timezone = g_variant_get_string (value, NULL); + const gchar * str = g_variant_get_string (value, NULL); - cc_timezone_map_set_timezone (priv->tzmap, timezone); - sync_entry (self, timezone); + cc_timezone_map_set_timezone (priv->tzmap, str); + sync_entry (self, str); g_signal_connect (priv->tzmap, "location-changed", G_CALLBACK (tz_changed), self); } g_variant_unref (value); @@ -640,13 +644,13 @@ entry_focus_out (GtkEntry * entry, gchar * zone; g_object_get (location, "zone", &zone, NULL); - gchar * name = get_current_zone_name (zone, self->priv->settings); + gchar * name = get_timezone_name (zone, self->priv->settings); gboolean correct = (g_strcmp0 (gtk_entry_get_text (entry), name) == 0); g_free (name); g_free (zone); - gtk_entry_set_icon_from_stock (entry, GTK_ENTRY_ICON_SECONDARY, - correct ? NULL : GTK_STOCK_DIALOG_ERROR); + gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_SECONDARY, + correct ? NULL : "dialog-error"); gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_SECONDARY, _("You need to choose a location to change the time zone.")); gtk_entry_set_icon_activatable (entry, GTK_ENTRY_ICON_SECONDARY, FALSE); diff --git a/po/POTFILES.in b/po/POTFILES.in index acc8916..8ee157e 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -1,6 +1,6 @@ -src/service.c -src/settings-shared.h -src/utils.c +src/formatter.cpp +src/formatter-desktop.cpp +src/menu.cpp panel/datetime-prefs.c panel/datetime-prefs-locations.c [type: gettext/glade]data/datetime-dialog.ui diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 15f29ea..810e299 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,9 @@ set (SERVICE_LIB "indicatordatetimeservice") set (SERVICE_EXEC "indicator-datetime-service") +SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -g ${CXX_WARNING_ARGS} ${GCOV_FLAGS}") +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 +13,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 + appointment.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 + timezone-file.cpp + timezone-geoclue.cpp + timezones-live.cpp + utils.c) +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..c0fd8ff --- /dev/null +++ b/src/actions-live.cpp @@ -0,0 +1,217 @@ +/* + * 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 { + +/*** +**** +***/ + +LiveActions::LiveActions(const std::shared_ptr<State>& state_in): + Actions(state_in) +{ +} + +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..d6fa698 --- /dev/null +++ b/src/actions.cpp @@ -0,0 +1,266 @@ +/* + * 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 (const 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); +} + +void on_calendar_active_changed(GSimpleAction * /*action*/, + GVariant * state, + gpointer gself) +{ + // reset the date when the menu is shown + if (g_variant_get_boolean(state)) + { + auto self = static_cast<Actions*>(gself); + + self->set_calendar_date(self->state()->clock->localtime()); + } +} + +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->this_month.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 }, + { "calendar-active", nullptr, nullptr, "false", on_calendar_active_changed }, + { "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->this_month.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); +} + +GActionGroup* Actions::action_group() +{ + return G_ACTION_GROUP(m_actions); +} + +const std::shared_ptr<State> Actions::state() const +{ + return m_state; +} + + + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/appointment.cpp b/src/appointment.cpp new file mode 100644 index 0000000..6e742c3 --- /dev/null +++ b/src/appointment.cpp @@ -0,0 +1,48 @@ +/* + * 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/appointment.h> + +namespace unity { +namespace indicator { +namespace datetime { + +/**** +***** +****/ + +bool Appointment::operator==(const Appointment& that) const +{ + return (color==that.color) + && (summary==that.summary) + && (url==that.url) + && (uid==that.uid) + && (is_event==that.is_event) + && (has_alarms==that.has_alarms) + && (begin==that.begin) + && (end==that.end); +} + +/**** +***** +****/ + +} // 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..21a18a3 --- /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<const 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.minute_changed(); + } + + /*** + **** + ***/ + + 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.minute_changed(); + if (!DateTime::is_same_day(m_prev_datetime, now)) + m_owner.date_changed(); + + // 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<const Timezones> m_timezones; + + DateTime m_prev_datetime; + unsigned int m_timer = 0; +}; + +LiveClock::LiveClock(const std::shared_ptr<const 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..f41a0cc --- /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, on_system_bus_ready, 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::on_system_bus_ready(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, + on_prepare_for_sleep, + self, + nullptr); + } +} + +void +Clock::on_prepare_for_sleep(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)->minute_changed(); +} + +/*** +**** +***/ + +} // 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..a634c5e --- /dev/null +++ b/src/date-time.cpp @@ -0,0 +1,159 @@ +/* + * 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(GDateTime* gdt) +{ + reset(gdt); +} + +DateTime& DateTime::operator=(GDateTime* gdt) +{ + reset(gdt); + return *this; +} + +DateTime& DateTime::operator=(const DateTime& that) +{ + m_dt = that.m_dt; + return *this; +} + +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..ccd6e5c --- /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(const std::shared_ptr<Actions>& actions, + const 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..336d2d3 --- /dev/null +++ b/src/formatter-desktop.cpp @@ -0,0 +1,169 @@ +/* + * 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/utils.h> // T_() + +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<const Clock>& clock_in, + const std::shared_ptr<const 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() +{ + header_format.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 default_header_time_format(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..9aa9bbb --- /dev/null +++ b/src/formatter.cpp @@ -0,0 +1,267 @@ +/* + * 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> // T_() + +#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<const Clock>& clock): + m_owner(owner), + m_clock(clock) + { + m_owner->header_format.changed().connect([this](const std::string& /*fmt*/){update_header();}); + m_clock->minute_changed.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->header_format.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->relative_format_changed(); + 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<const Clock> m_clock; +}; + +/*** +**** +***/ + +Formatter::Formatter(const std::shared_ptr<const Clock>& clock): + p(new Formatter::Impl(this, clock)) +{ +} + +Formatter::~Formatter() +{ +} + +const char* +Formatter::default_header_time_format(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; +} + +/*** +**** +***/ + +std::string +Formatter::relative_format(GDateTime* then_begin, GDateTime* then_end) const +{ + auto cstr = generate_full_format_string_at_time (p->m_clock->localtime().get(), then_begin, then_end); + const std::string ret = cstr; + g_free (cstr); + 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..ef3085f --- /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<const Settings>& settings, + const std::shared_ptr<const 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..0690acd --- /dev/null +++ b/src/locations.cpp @@ -0,0 +1,59 @@ +/* + * 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 { + +const std::string& Location::zone() const +{ + return m_zone; +} + +const std::string& Location::name() const +{ + return m_name; +} + +bool Location::operator== (const Location& that) const +{ + return (m_name == that.m_name) + && (m_zone == that.m_zone) + && (m_offset == that.m_offset); +} + + +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); +} + +} // 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..1534777 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,86 @@ +/* + * 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/clock.h> +#include <datetime/exporter.h> +#include <datetime/locations-settings.h> +#include <datetime/menu.h> +#include <datetime/planner-eds.h> +#include <datetime/settings-live.h> +#include <datetime/state.h> +#include <datetime/timezones-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, actions, and menufactory + std::shared_ptr<State> state(new State); + 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)); + state->settings = live_settings; + state->clock = live_clock; + state->locations.reset(new SettingsLocations(live_settings, live_timezones)); + state->planner.reset(new PlannerEds); + state->planner->time = live_clock->localtime(); + 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..91f7dd2 --- /dev/null +++ b/src/menu.cpp @@ -0,0 +1,598 @@ +/* + * 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 { + +/**** +***** +****/ + +Menu::Menu (Profile profile_in, const std::string& name_in): + m_profile(profile_in), + m_name(name_in) +{ +} + +const std::string& Menu::name() const +{ + return m_name; +} + +Menu::Profile Menu::profile() const +{ + return m_profile; +} + +GMenuModel* Menu::menu_model() +{ + return G_MENU_MODEL(m_menu); +} + + +/**** +***** +****/ + + +#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<const State>& state, + std::shared_ptr<Actions>& actions, + std::shared_ptr<const 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->header_format.changed().connect([this](const std::string&){ + update_section(Locations); // need to update x-canonical-time-format + }); + m_formatter->relative_format_changed.connect([this](){ + update_section(Appointments); // uses formatter.relative_format() + update_section(Locations); // uses formatter.relative_format() + }); + 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_calendar.changed().connect([this](bool){ + update_section(Calendar); + }); + 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->date_changed.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<const State> m_state; + std::shared_ptr<Actions> m_actions; + std::shared_ptr<const 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_attribute(header, "submenu-action", "s", + "indicator.calendar-active"); + 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 = m_state->settings->show_calendar.get() && + ((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->relative_format(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==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); + } + else if (profile==Phone) + { + auto menu_item = g_menu_item_new (_("Clock"), "indicator.activate-phone-clock-app"); + g_menu_item_set_attribute_value (menu_item, G_MENU_ATTRIBUTE_ICON, get_serialized_alarm_icon()); + g_menu_append_item (menu, menu_item); + g_object_unref (menu_item); + + add_appointments (menu, profile); + } + + 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->relative_format(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: model = nullptr; 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<const State>& state_, + std::shared_ptr<Actions>& actions_): + MenuImpl(profile_, name_, state_, actions_, + std::shared_ptr<const 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<const State>& state_, std::shared_ptr<Actions>& actions_): + DesktopBaseMenu(Desktop,"desktop", state_, actions_) {} +}; + +class DesktopGreeterMenu: public DesktopBaseMenu +{ +public: + DesktopGreeterMenu(std::shared_ptr<const 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<const 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<const State>& state_, + std::shared_ptr<Actions>& actions_): + PhoneBaseMenu(Phone, "phone", state_, actions_) {} +}; + +class PhoneGreeterMenu: public PhoneBaseMenu +{ +public: + PhoneGreeterMenu(std::shared_ptr<const State>& state_, + std::shared_ptr<Actions>& actions_): + PhoneBaseMenu(PhoneGreeter, "phone_greeter", state_, actions_) {} +}; + +/**** +***** +****/ + +MenuFactory::MenuFactory(const std::shared_ptr<Actions>& actions_, + const std::shared_ptr<const 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..cb42d6e --- /dev/null +++ b/src/planner-eds.cpp @@ -0,0 +1,425 @@ +/* + * 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() + { + const 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.this_month.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); + } + + 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); + + auto begin_str = g_date_time_format(begin_dt, "%F %T"); + auto end_str = g_date_time_format(end_dt, "%F %T"); + g_debug("getting all appointments from [%s ... %s]", begin_str, end_str); + g_free(begin_str); + g_free(end_str); + + /** + *** 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); + delete task; + }); + + 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 = DateTime(begin); + appointment.end = DateTime(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.c b/src/service.c deleted file mode 100644 index 7176ef1..0000000 --- a/src/service.c +++ /dev/null @@ -1,2432 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * Authors: - * Charles Kerr <charles.kerr@canonical.com> - * Ted Gould <ted@canonical.com> - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 3, as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <string.h> /* strstr() */ - -#include <glib/gi18n.h> -#include <gio/gio.h> -#include <libnotify/notify.h> -#include <json-glib/json-glib.h> -#include <url-dispatcher.h> - -#include "dbus-shared.h" -#include "service.h" -#include "settings-shared.h" -#include "utils.h" - -#define SKEW_CHECK_INTERVAL_SEC 10 -#define SKEW_DIFF_THRESHOLD_USEC ((SKEW_CHECK_INTERVAL_SEC+5) * G_USEC_PER_SEC) -#define ALARM_CLOCK_ICON_NAME "alarm-clock" - -G_DEFINE_TYPE (IndicatorDatetimeService, - indicator_datetime_service, - G_TYPE_OBJECT) - -enum -{ - SIGNAL_NAME_LOST, - LAST_SIGNAL -}; - -static guint signals[LAST_SIGNAL] = { 0 }; - -enum -{ - PROP_0, - PROP_CLOCK, - PROP_PLANNER, - PROP_LAST -}; - -static GParamSpec * properties[PROP_LAST] = { 0 }; - -enum -{ - SECTION_HEADER = (1<<0), - SECTION_CALENDAR = (1<<1), - SECTION_APPOINTMENTS = (1<<2), - SECTION_LOCATIONS = (1<<3), - SECTION_SETTINGS = (1<<4), -}; - -enum -{ - PROFILE_PHONE, - PROFILE_DESKTOP, - PROFILE_GREETER, - N_PROFILES -}; - -static const char * const menu_names[N_PROFILES] = -{ - "phone", - "desktop", - "desktop_greeter" -}; - -struct ProfileMenuInfo -{ - /* the root level -- the header is the only child of this */ - GMenu * menu; - - /* parent of the sections. This is the header's submenu */ - GMenu * submenu; - - guint export_id; -}; - -struct _IndicatorDatetimeServicePrivate -{ - GCancellable * cancellable; - - GSettings * settings; - - IndicatorDatetimeClock * clock; - IndicatorDatetimePlanner * planner; - - gchar * header_label_format_string; - - guint own_id; - guint actions_export_id; - GDBusConnection * conn; - - guint rebuild_id; - int rebuild_flags; - struct ProfileMenuInfo menus[N_PROFILES]; - - GDateTime * skew_time; - guint skew_timer; - - guint header_timer; - guint timezone_timer; - guint alarm_timer; - - /* Which year/month to show in the calendar, - and which day should get the cursor. - This value is reflected in the calendar action's state */ - GDateTime * calendar_date; - - GSimpleActionGroup * actions; - GSimpleAction * phone_header_action; - GSimpleAction * desktop_header_action; - GSimpleAction * calendar_action; - - GDBusProxy * login1_manager; - - /* all the appointments in the selected calendar_date's month. - Used when populating the 'appointment-days' entry in - create_calendar_state() */ - GSList * calendar_appointments; - - /* appointments over the next few weeks. - Used when building SECTION_APPOINTMENTS */ - GSList * upcoming_appointments; - - /* serialized icon cache */ - GVariant * alarm_icon_serialized; - GVariant * calendar_icon_serialized; - GVariant * clock_app_icon_serialized; -}; - -typedef IndicatorDatetimeServicePrivate priv_t; - -/*** -**** -***/ - -static void -indicator_clear_timer (guint * tag) -{ - if (*tag) - { - g_source_remove (*tag); - *tag = 0; - } -} - -static inline GDateTime * -indicator_datetime_service_get_localtime (IndicatorDatetimeService * self) -{ - return indicator_datetime_clock_get_localtime (self->priv->clock); -} - -/*** -**** -***/ - -static void rebuild_now (IndicatorDatetimeService * self, int section); -static void rebuild_soon (IndicatorDatetimeService * self, int section); - -static inline void -rebuild_header_soon (IndicatorDatetimeService * self) -{ - g_clear_pointer (&self->priv->header_label_format_string, g_free); - - rebuild_soon (self, SECTION_HEADER); -} - -static inline void -rebuild_calendar_section_soon (IndicatorDatetimeService * self) -{ - rebuild_soon (self, SECTION_CALENDAR); -} - -static inline void -rebuild_appointments_section_soon (IndicatorDatetimeService * self) -{ - rebuild_soon (self, SECTION_APPOINTMENTS); -} - -static inline void -rebuild_locations_section_soon (IndicatorDatetimeService * self) -{ - rebuild_soon (self, SECTION_LOCATIONS); -} - -static inline void -rebuild_settings_section_soon (IndicatorDatetimeService * self) -{ - rebuild_soon (self, SECTION_SETTINGS); -} - -/*** -**** TIMEZONE TIMER -***/ - -/* - * Periodically rebuild the sections that have time format strings - * that are dependent on the current time: - * - * 1. appointment menuitems' time format strings depend on the - * current time; for example, they don't show the day of week - * if the appointment is today. - * - * 2. location menuitems' time format strings depend on the - * current time; for example, they don't show the day of the week - * if the local date and location date are the same. - * - * 3. the "local date" menuitem in the calendar section is, - * obviously, dependent on the local time. - * - * In short, we want to update whenever the number of days between two zone - * might have changed. We do that by updating when either zone's day changes. - * - * Since not all UTC offsets are evenly divisible by hours - * (examples: Newfoundland UTC-03:30, Nepal UTC+05:45), refreshing on the hour - * is not enough. We need to refresh at HH:00, HH:15, HH:30, and HH:45. - */ - -static guint -calculate_seconds_until_next_fifteen_minutes (GDateTime * now) -{ - char * str; - gint minute; - guint seconds; - GTimeSpan diff; - GDateTime * next; - GDateTime * start_of_next; - - minute = g_date_time_get_minute (now); - minute = 15 - (minute % 15); - next = g_date_time_add_minutes (now, minute); - start_of_next = g_date_time_new_local (g_date_time_get_year (next), - g_date_time_get_month (next), - g_date_time_get_day_of_month (next), - g_date_time_get_hour (next), - g_date_time_get_minute (next), - 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; -} - -static void start_timezone_timer (IndicatorDatetimeService * self); - -static gboolean -on_timezone_timer (gpointer gself) -{ - IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (gself); - - rebuild_soon (self, SECTION_CALENDAR | - SECTION_APPOINTMENTS | - SECTION_LOCATIONS); - - /* Restarting the timer to recalculate the interval. This helps us to hit - our marks despite clock skew, suspend+resume, leap seconds, etc */ - start_timezone_timer (self); - return G_SOURCE_REMOVE; -} - -static void -start_timezone_timer (IndicatorDatetimeService * self) -{ - GDateTime * now; - guint seconds; - priv_t * p = self->priv; - - indicator_clear_timer (&p->timezone_timer); - - now = indicator_datetime_service_get_localtime (self); - seconds = calculate_seconds_until_next_fifteen_minutes (now); - p->timezone_timer = g_timeout_add_seconds (seconds, on_timezone_timer, self); - g_date_time_unref (now); -} - -/*** -**** HEADER TIMER -***/ - -/* - * This is to periodically rebuild the header's action's state. - * - * If the label shows seconds, update when we reach the next second. - * Otherwise, update when we reach the next minute. - */ - -static guint -calculate_milliseconds_until_next_minute (GDateTime * now) -{ - GDateTime * next; - GDateTime * start_of_next; - GTimeSpan interval_usec; - guint interval_msec; - - next = g_date_time_add_minutes (now, 1); - start_of_next = g_date_time_new_local (g_date_time_get_year (next), - g_date_time_get_month (next), - g_date_time_get_day_of_month (next), - g_date_time_get_hour (next), - g_date_time_get_minute (next), - 0.1); - - interval_usec = g_date_time_difference (start_of_next, now); - interval_msec = (interval_usec + 999) / 1000; - - g_date_time_unref (start_of_next); - g_date_time_unref (next); - - return interval_msec; -} - -static gint -calculate_milliseconds_until_next_second (GDateTime * now) -{ - gint interval_usec; - guint interval_msec; - - interval_usec = G_USEC_PER_SEC - g_date_time_get_microsecond (now); - interval_msec = (interval_usec + 999) / 1000; - - return interval_msec; -} - -static void start_header_timer (IndicatorDatetimeService * self); - -static gboolean -on_header_timer (gpointer gself) -{ - IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (gself); - - rebuild_now (self, SECTION_HEADER); - - /* Restarting the timer to recalculate the interval. This helps us to hit - our marks despite clock skew, suspend+resume, leap seconds, etc */ - start_header_timer (self); - return G_SOURCE_REMOVE; -} - -static const char * get_header_label_format_string (IndicatorDatetimeService *); - -static void -start_header_timer (IndicatorDatetimeService * self) -{ - guint interval_msec; - gboolean header_shows_seconds = FALSE; - priv_t * p = self->priv; - GDateTime * now = indicator_datetime_service_get_localtime (self); - - indicator_clear_timer (&p->header_timer); - - if (g_settings_get_boolean (self->priv->settings, SETTINGS_SHOW_CLOCK_S)) - { - const char * fmt = get_header_label_format_string (self); - header_shows_seconds = fmt && (strstr(fmt,"%s") || strstr(fmt,"%S") || - strstr(fmt,"%T") || strstr(fmt,"%X") || - strstr(fmt,"%c")); - } - - if (header_shows_seconds) - interval_msec = calculate_milliseconds_until_next_second (now); - else - interval_msec = calculate_milliseconds_until_next_minute (now); - - interval_msec += 50; /* add a small margin to ensure the callback - fires /after/ next is reached */ - - p->header_timer = g_timeout_add_full (G_PRIORITY_HIGH, - interval_msec, - on_header_timer, - self, - NULL); - - g_date_time_unref (now); -} - -/*** -**** ALARMS -***/ - -static void set_alarm_timer (IndicatorDatetimeService * self); - -static gboolean -appointment_has_alarm_url (const struct IndicatorDatetimeAppt * appt) -{ - return (appt->has_alarms) && - (appt->url != NULL) && - (g_str_has_prefix (appt->url, "alarm:///")); -} - -static gboolean -datetimes_have_the_same_minute (GDateTime * a G_GNUC_UNUSED, GDateTime * b G_GNUC_UNUSED) -{ - int ay, am, ad; - int by, bm, bd; - - g_date_time_get_ymd (a, &ay, &am, &ad); - g_date_time_get_ymd (b, &by, &bm, &bd); - - return (ay == by) && - (am == bm) && - (ad == bd) && - (g_date_time_get_hour (a) == g_date_time_get_hour (b)) && - (g_date_time_get_minute (a) == g_date_time_get_minute (b)); -} - -static void -dispatch_alarm_url (const struct IndicatorDatetimeAppt * appt) -{ - gchar * str; - - g_return_if_fail (appt != NULL); - g_return_if_fail (appointment_has_alarm_url (appt)); - - str = g_date_time_format (appt->begin, "%F %T"); - g_debug ("dispatching url \"%s\" for appointment \"%s\", which begins at %s", - appt->url, appt->summary, str); - g_free (str); - - url_dispatch_send (appt->url, NULL, NULL); -} - -static void -on_snap_decided (NotifyNotification * notification G_GNUC_UNUSED, - char * action, - gpointer gurl) -{ - g_debug ("%s: %s", G_STRFUNC, action); - - if (!g_strcmp0 (action, "show")) - { - const gchar * url = gurl; - g_debug ("dispatching url '%s'", url); - url_dispatch_send (url, NULL, NULL); - } -} - -static void -show_snap_decision_for_alarm (const struct IndicatorDatetimeAppt * appt) -{ - gchar * title; - const gchar * body; - const gchar * icon_name; - NotifyNotification * nn; - GError * error; - - title = g_date_time_format (appt->begin, - get_terse_time_format_string (appt->begin)); - body = appt->summary; - icon_name = ALARM_CLOCK_ICON_NAME; - g_debug ("creating a snap decision with title '%s', body '%s', icon '%s'", - title, body, icon_name); - - nn = notify_notification_new (title, body, icon_name); - notify_notification_set_hint_string (nn, - "x-canonical-snap-decisions", - "true"); - notify_notification_set_hint_string (nn, - "x-canonical-private-button-tint", - "true"); - notify_notification_add_action (nn, "show", _("Show"), - on_snap_decided, g_strdup(appt->url), g_free); - notify_notification_add_action (nn, "dismiss", _("Dismiss"), - on_snap_decided, NULL, NULL); - - error = NULL; - notify_notification_show (nn, &error); - if (error != NULL) - { - g_warning ("Unable to show alarm '%s' popup: %s", body, error->message); - g_error_free (error); - dispatch_alarm_url (appt); - } - - g_free (title); -} - -static void update_appointment_lists (IndicatorDatetimeService * self); - -static gboolean -on_alarm_timer (gpointer gself) -{ - IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (gself); - GDateTime * now; - GSList * l; - - /* If there are any alarms at the current time, show a snap decision */ - now = indicator_datetime_service_get_localtime (self); - for (l=self->priv->upcoming_appointments; l!=NULL; l=l->next) - { - const struct IndicatorDatetimeAppt * appt = l->data; - - if (appointment_has_alarm_url (appt)) - if (datetimes_have_the_same_minute (now, appt->begin)) - show_snap_decision_for_alarm (appt); - } - g_date_time_unref (now); - - /* rebuild the alarm list asynchronously. - set_upcoming_appointments() will update the alarm timer when this - async call is done, so no need to restart the timer here... */ - update_appointment_lists (self); - - return G_SOURCE_REMOVE; -} - -/* if there are upcoming alarms, set the alarm timer to the nearest one. - otherwise, unset the alarm timer. */ -static void -set_alarm_timer (IndicatorDatetimeService * self) -{ - priv_t * p; - GDateTime * now; - GDateTime * alarm_time; - GSList * l; - - p = self->priv; - indicator_clear_timer (&p->alarm_timer); - - now = indicator_datetime_service_get_localtime (self); - - /* find the time of the next alarm on our calendar */ - alarm_time = NULL; - for (l=p->upcoming_appointments; l!=NULL; l=l->next) - { - const struct IndicatorDatetimeAppt * appt = l->data; - - if (appointment_has_alarm_url (appt)) - if (g_date_time_compare (appt->begin, now) > 0) - if (!alarm_time || g_date_time_compare (alarm_time, appt->begin) > 0) - alarm_time = appt->begin; - } - - /* if there's an upcoming alarm, set a timer to wake up at that time */ - if (alarm_time != NULL) - { - GTimeSpan interval_msec; - gchar * str; - GDateTime * then; - - interval_msec = g_date_time_difference (alarm_time, now); - interval_msec += G_USEC_PER_SEC; /* fire a moment after alarm_time */ - interval_msec /= 1000; /* convert from usec to msec */ - - str = g_date_time_format (alarm_time, "%F %T"); - g_debug ("%s is the next alarm time", str); - g_free (str); - then = g_date_time_add_seconds (now, interval_msec/1000); - str = g_date_time_format (then, "%F %T"); - g_debug ("%s is when we'll wake up for it", str); - g_free (str); - g_date_time_unref (then); - - p->alarm_timer = g_timeout_add_full (G_PRIORITY_HIGH, - (guint) interval_msec, - on_alarm_timer, - self, - NULL); - } - - g_date_time_unref (now); -} - -/*** -**** -***/ - -/** - * General purpose handler for rebuilding sections and restarting their timers - * when time jumps for whatever reason: - * - * - clock skew - * - laptop suspend + resume - * - geoclue detects that we've changed timezones - * - Unity is running inside a TARDIS - */ -static void -on_local_time_jumped (IndicatorDatetimeService * self) -{ - g_debug ("%s %s", G_STRLOC, G_STRFUNC); - - /* these calls accomplish two things: - 1. rebuild the necessary states / menuitems when time jumps - 2. restart the timers so their new wait interval is correct */ - - on_header_timer (self); - on_timezone_timer (self); -} - -static gboolean -skew_timer_func (gpointer gself) -{ - IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (gself); - GDateTime * now = indicator_datetime_service_get_localtime (self); - priv_t * p = self->priv; - - /* check for clock skew: has too much time passed since the last check? */ - if (p->skew_time != NULL) - { - const GTimeSpan diff = g_date_time_difference (now, p->skew_time); - - if (diff > SKEW_DIFF_THRESHOLD_USEC) - on_local_time_jumped (self); - } - - g_clear_pointer (&p->skew_time, g_date_time_unref); - p->skew_time = now; - return G_SOURCE_CONTINUE; -} - -/*** -**** -**** HEADER SECTION -**** -***/ - -static const gchar * -get_header_label_format_string (IndicatorDatetimeService * self) -{ - priv_t * p = self->priv; - - if (p->header_label_format_string == NULL) - { - char * fmt; - GSettings * s = p->settings; - const TimeFormatMode mode = g_settings_get_enum (s, SETTINGS_TIME_FORMAT_S); - - if (mode == TIME_FORMAT_MODE_CUSTOM) - { - fmt = g_settings_get_string (s, SETTINGS_CUSTOM_TIME_FORMAT_S); - } - else - { - gboolean show_day = g_settings_get_boolean (s, SETTINGS_SHOW_DAY_S); - gboolean show_date = g_settings_get_boolean (s, SETTINGS_SHOW_DATE_S); - gboolean show_year = show_date && g_settings_get_boolean (s, SETTINGS_SHOW_YEAR_S); - fmt = generate_full_format_string (show_day, show_date, show_year, s); - } - - p->header_label_format_string = fmt; - } - - return p->header_label_format_string; -} - -static GVariant * -create_desktop_header_state (IndicatorDatetimeService * self) -{ - priv_t * p = self->priv; - GVariantBuilder b; - const gchar * fmt; - gchar * str; - gboolean visible; - GDateTime * now; - GVariant * label_variant; - - visible = g_settings_get_boolean (p->settings, SETTINGS_SHOW_CLOCK_S); - - /* build the time string for the label & a11y */ - fmt = get_header_label_format_string (self); - now = indicator_datetime_service_get_localtime (self); - str = g_date_time_format (now, fmt); - if (str == NULL) - { - str = g_strdup_printf (_("Unsupported date format “%s”"), fmt); - g_warning ("%s", str); - } - - label_variant = g_variant_new_take_string (str); - g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT); - g_variant_builder_add (&b, "{sv}", "accessible-desc", label_variant); - g_variant_builder_add (&b, "{sv}", "label", label_variant); - g_variant_builder_add (&b, "{sv}", "title", g_variant_new_string (_("Date and Time"))); - g_variant_builder_add (&b, "{sv}", "visible", g_variant_new_boolean (visible)); - - /* cleanup */ - g_date_time_unref (now); - return g_variant_builder_end (&b); -} - -static gboolean -service_has_alarms (IndicatorDatetimeService * self); - -static GVariant * -create_phone_header_state (IndicatorDatetimeService * self) -{ - priv_t * p = self->priv; - const gboolean has_alarms = service_has_alarms (self); - GVariantBuilder b; - GDateTime * now; - const gchar * fmt; - - 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) - g_variant_builder_add (&b, "{sv}", "icon", p->alarm_icon_serialized); - - /* label, a11y */ - now = indicator_datetime_service_get_localtime (self); - fmt = get_terse_header_time_format_string (); - if (has_alarms) - { - gchar * label = g_date_time_format (now, fmt); - gchar * a11y = g_strdup_printf (_("%s (has alarms)"), label); - g_variant_builder_add (&b, "{sv}", "label", g_variant_new_take_string (label)); - g_variant_builder_add (&b, "{sv}", "accessible-desc", g_variant_new_take_string (a11y)); - } - else - { - GVariant * v = g_variant_new_take_string (g_date_time_format (now, fmt)); - g_variant_builder_add (&b, "{sv}", "label", v); - g_variant_builder_add (&b, "{sv}", "accessible-desc", v); - } - - g_date_time_unref (now); - return g_variant_builder_end (&b); -} - - -/*** -**** -**** CALENDAR SECTION -**** -***/ - -static GDateTime * -get_calendar_date (IndicatorDatetimeService * self) -{ - GDateTime * date; - priv_t * p = self->priv; - - if (p->calendar_date) - date = g_date_time_ref (p->calendar_date); - else - date = indicator_datetime_service_get_localtime (self); - - return date; -} - -static GVariant * -create_calendar_state (IndicatorDatetimeService * self) -{ - guint i; - const char * key; - gboolean days[32] = { 0 }; - GVariantBuilder dict_builder; - GVariantBuilder day_builder; - GDateTime * date; - GSList * l; - gboolean b; - priv_t * p = self->priv; - - g_variant_builder_init (&dict_builder, G_VARIANT_TYPE_DICTIONARY); - - key = "appointment-days"; - for (l=p->calendar_appointments; l!=NULL; l=l->next) - { - const struct IndicatorDatetimeAppt * appt = l->data; - days[g_date_time_get_day_of_month (appt->begin)] = TRUE; - } - g_variant_builder_init (&day_builder, G_VARIANT_TYPE("ai")); - for (i=0; i<G_N_ELEMENTS(days); i++) - if (days[i]) - g_variant_builder_add (&day_builder, "i", i); - g_variant_builder_add (&dict_builder, "{sv}", key, - g_variant_builder_end (&day_builder)); - - key = "calendar-day"; - date = get_calendar_date (self); - g_variant_builder_add (&dict_builder, "{sv}", key, - g_variant_new_int64 (g_date_time_to_unix (date))); - g_date_time_unref (date); - - key = "show-week-numbers"; - b = g_settings_get_boolean (p->settings, SETTINGS_SHOW_WEEK_NUMBERS_S); - g_variant_builder_add (&dict_builder, "{sv}", key, g_variant_new_boolean (b)); - - return g_variant_builder_end (&dict_builder); -} - -static void -update_calendar_action_state (IndicatorDatetimeService * self) -{ - g_simple_action_set_state (self->priv->calendar_action, - create_calendar_state (self)); -} - -static GMenuModel * -create_calendar_section (IndicatorDatetimeService * self, int profile) -{ - const gboolean allow_activation = (profile == PROFILE_PHONE) || (profile == PROFILE_DESKTOP); - GMenu * menu; - GDateTime * now; - char * label; - GMenuItem * item; - - menu = g_menu_new (); - - /* add a menuitem that shows the current date & time */ - now = indicator_datetime_service_get_localtime (self); - label = g_date_time_format (now, _("%A, %e %B %Y")); - item = g_menu_item_new (label, NULL); - g_menu_item_set_attribute_value (item, G_MENU_ATTRIBUTE_ICON, self->priv->calendar_icon_serialized); - if (allow_activation) - g_menu_item_set_action_and_target_value (item, "indicator.activate-planner", g_variant_new_int64(0)); - g_menu_append_item (menu, item); - g_object_unref (item); - g_free (label); - g_date_time_unref (now); - - /* add a menuitem that shows the current date & time */ - if ((profile == PROFILE_DESKTOP) || (profile == PROFILE_GREETER)) - { - item = g_menu_item_new ("[calendar]", NULL); - g_menu_item_set_action_and_target_value (item, "indicator.calendar", g_variant_new_int64(0)); - 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); -} - -/*** -**** -**** APPOINTMENTS SECTION -**** -***/ - -static gboolean -service_has_alarms (IndicatorDatetimeService * self) -{ - gboolean has_alarms = FALSE; - GSList * appts; - GSList * l; - - appts = self->priv->upcoming_appointments; - for (l=appts; l!=NULL; l=l->next) - { - struct IndicatorDatetimeAppt * appt = l->data; - if ((has_alarms = appt->has_alarms)) - break; - } - - return has_alarms; -} - -static char * -get_appointment_time_format (struct IndicatorDatetimeAppt * appt, - GDateTime * now, - GSettings * settings, - gboolean terse) -{ - char * fmt; - gboolean full_day = g_date_time_difference (appt->end, appt->begin) == G_TIME_SPAN_DAY; - - if (appt->is_daily) - { - const char * time_fmt = terse ? get_terse_time_format_string (appt->begin) - : get_full_time_format_string (settings); - fmt = join_date_and_time_format_strings (_("Daily"), time_fmt); - } - else if (full_day) - { - /* TRANSLATORS: a strftime(3) format showing full day events. - * "%A" means a full text day (Wednesday), "%a" means abbreviated (Wed). */ - fmt = g_strdup (_("%A")); - } - else - { - fmt = terse ? generate_terse_format_string_at_time (now, appt->begin) - : generate_full_format_string_at_time (now, appt->begin, settings); - } - - return fmt; -} - -static void -add_appointments (IndicatorDatetimeService * self, GMenu * menu, gboolean phone) -{ - const int MAX_APPTS = 5; - GDateTime * now; - GHashTable * added; - GSList * appts; - GSList * l; - int i; - - now = indicator_datetime_service_get_localtime (self); - - added = g_hash_table_new (g_str_hash, g_str_equal); - - /* build appointment menuitems */ - appts = self->priv->upcoming_appointments; - for (l=appts, i=0; l!=NULL && i<MAX_APPTS; l=l->next, i++) - { - struct IndicatorDatetimeAppt * appt = l->data; - char * fmt; - gint64 unix_time; - GMenuItem * menu_item; - - if (g_hash_table_contains (added, appt->uid)) - continue; - - g_hash_table_add (added, appt->uid); - - fmt = get_appointment_time_format (appt, now, self->priv->settings, phone); - unix_time = g_date_time_to_unix (appt->begin); - - menu_item = g_menu_item_new (appt->summary, NULL); - - if (appt->has_alarms) - g_menu_item_set_attribute_value (menu_item, G_MENU_ATTRIBUTE_ICON, - self->priv->alarm_icon_serialized); - else if (appt->color != NULL) - g_menu_item_set_attribute (menu_item, "x-canonical-color", - "s", appt->color); - - g_menu_item_set_attribute (menu_item, "x-canonical-time", - "x", unix_time); - g_menu_item_set_attribute (menu_item, "x-canonical-time-format", - "s", fmt); - g_menu_item_set_attribute (menu_item, "x-canonical-type", - "s", appt->has_alarms ? "com.canonical.indicator.alarm" - : "com.canonical.indicator.appointment"); - - if (phone) - g_menu_item_set_action_and_target_value (menu_item, - "indicator.activate-appointment", - g_variant_new_string (appt->uid)); - 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); - g_free (fmt); - } - - /* cleanup */ - g_hash_table_unref (added); - g_date_time_unref (now); -} - - -/* try to extract the clock app's filename from click. (/$pkgdir/$icon) */ -static GVariant * -get_clock_app_icon (void) -{ - GVariant * serialized = NULL; - gchar * icon_filename = NULL; - gchar * pkgdir; - - pkgdir = NULL; - g_spawn_command_line_sync ("click pkgdir com.ubuntu.clock", &pkgdir, NULL, NULL, NULL); - if (pkgdir != NULL) - { - gchar * manifest = NULL; - g_strstrip (pkgdir); - g_spawn_command_line_sync ("click info com.ubuntu.clock", &manifest, NULL, NULL, NULL); - if (manifest != NULL) - { - JsonParser * parser = json_parser_new (); - if (json_parser_load_from_data (parser, manifest, -1, NULL)) - { - JsonNode * root = json_parser_get_root (parser); /* transfer-none */ - if ((root != NULL) && (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 != NULL) - icon_filename = g_build_filename (pkgdir, icon_name, NULL); - } - } - g_object_unref (parser); - g_free (manifest); - } - g_free (pkgdir); - } - - if (icon_filename != NULL) - { - 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); - } - - return serialized; -} - -static GMenuModel * -create_phone_appointments_section (IndicatorDatetimeService * self) -{ - priv_t * p = self->priv; - GMenu * menu = g_menu_new (); - GMenuItem * menu_item; - - menu_item = g_menu_item_new (_("Clock"), "indicator.activate-phone-clock-app"); - if (p->clock_app_icon_serialized != NULL) - g_menu_item_set_attribute_value (menu_item, G_MENU_ATTRIBUTE_ICON, p->clock_app_icon_serialized); - g_menu_append_item (menu, menu_item); - g_object_unref (menu_item); - - add_appointments (self, menu, TRUE); - - return G_MENU_MODEL (menu); -} - -static GMenuModel * -create_desktop_appointments_section (IndicatorDatetimeService * self) -{ - GMenu * menu = g_menu_new (); - - if (g_settings_get_boolean (self->priv->settings, SETTINGS_SHOW_EVENTS_S)) - { - GMenuItem * menu_item; - - add_appointments (self, menu, FALSE); - - /* add the 'Add Event…' menuitem */ - menu_item = g_menu_item_new (_("Add Event…"), NULL); - g_menu_item_set_action_and_target_value (menu_item, - "indicator.activate-planner", - g_variant_new_int64 (0)); - g_menu_append_item (menu, menu_item); - g_object_unref (menu_item); - } - - return G_MENU_MODEL (menu); -} - -/*** -**** -**** LOCATIONS SECTION -**** -***/ - - -/* A temp struct used by create_locations_section() - for pruning duplicates and sorting. */ -struct TimeLocation -{ - GTimeSpan offset; - gchar * zone; - gchar * name; - gboolean visible; - GDateTime * local_time; -}; - -static void -time_location_free (struct TimeLocation * loc) -{ - g_date_time_unref (loc->local_time); - g_free (loc->name); - g_free (loc->zone); - g_slice_free (struct TimeLocation, loc); -} - -static struct TimeLocation* -time_location_new (const char * zone, - const char * name, - gboolean visible) -{ - struct TimeLocation * loc = g_slice_new (struct TimeLocation); - GTimeZone * tz = g_time_zone_new (zone); - loc->zone = g_strdup (zone); - loc->name = g_strdup (name); - loc->visible = visible; - loc->local_time = g_date_time_new_now (tz); - loc->offset = g_date_time_get_utc_offset (loc->local_time); - g_time_zone_unref (tz); - return loc; -} - -static int -time_location_compare (const struct TimeLocation * a, - const struct TimeLocation * b) -{ - int ret = 0; - - if (!ret && (a->offset != b->offset)) /* primary key */ - ret = (a->offset < b->offset) ? -1 : 1; - - if (!ret) - ret = g_strcmp0 (a->name, b->name); /* secondary key */ - - if (!ret) - ret = a->visible - b->visible; /* tertiary key */ - - return ret; -} - -static GSList* -locations_add (GSList * locations, - const char * zone, - const char * name, - gboolean visible) -{ - struct TimeLocation * loc = time_location_new (zone, name, visible); - - if (g_slist_find_custom (locations, loc, (GCompareFunc)time_location_compare)) - { - g_debug("%s Skipping duplicate zone '%s' name '%s'", G_STRLOC, zone, name); - time_location_free (loc); - } - else - { - g_debug ("%s Adding zone '%s', name '%s'", G_STRLOC, zone, name); - locations = g_slist_append (locations, loc); - } - - return locations; -} - -static GMenuModel * -create_locations_section (IndicatorDatetimeService * self) -{ - guint i; - GMenu * menu; - GSList * l; - GSList * locations = NULL; - gchar ** user_locations; - const gchar ** detected_timezones; - priv_t * p = self->priv; - GDateTime * now = indicator_datetime_service_get_localtime (self); - - menu = g_menu_new (); - - /*** - **** Build a list of locations to add, omitting duplicates - ***/ - - detected_timezones = indicator_datetime_clock_get_timezones (p->clock); - for (i=0; detected_timezones && detected_timezones[i]; i++) - { - const char * tz = detected_timezones[i]; - gchar * name = get_current_zone_name (tz, p->settings); - locations = locations_add (locations, tz, name, TRUE); - g_free (name); - } - - /* maybe add the user-specified locations */ - user_locations = g_settings_get_strv (p->settings, SETTINGS_LOCATIONS_S); - if (user_locations != NULL) - { - const gboolean visible = g_settings_get_boolean (p->settings, SETTINGS_SHOW_LOCATIONS_S); - - for (i=0; user_locations[i] != NULL; i++) - { - gchar * zone; - gchar * name; - split_settings_location (user_locations[i], &zone, &name); - locations = locations_add (locations, zone, name, visible); - g_free (name); - g_free (zone); - } - - g_strfreev (user_locations); - user_locations = NULL; - } - - /* now build menuitems for all the locations */ - for (l=locations; l!=NULL; l=l->next) - { - struct TimeLocation * loc = l->data; - if (loc->visible) - { - char * detailed_action; - char * fmt; - GMenuItem * menu_item; - - detailed_action = g_strdup_printf ("indicator.set-location::%s %s", - loc->zone, - loc->name); - fmt = generate_full_format_string_at_time (now, loc->local_time, p->settings); - - menu_item = g_menu_item_new (loc->name, detailed_action); - g_menu_item_set_attribute (menu_item, "x-canonical-type", - "s", "com.canonical.indicator.location"); - g_menu_item_set_attribute (menu_item, "x-canonical-timezone", - "s", loc->zone); - g_menu_item_set_attribute (menu_item, "x-canonical-time-format", - "s", fmt); - g_menu_append_item (menu, menu_item); - - g_object_unref (menu_item); - g_free (fmt); - g_free (detailed_action); - } - } - - g_date_time_unref (now); - g_slist_free_full (locations, (GDestroyNotify)time_location_free); - return G_MENU_MODEL (menu); -} - -/*** -**** SET LOCATION -***/ - -struct setlocation_data -{ - IndicatorDatetimeService * service; - char * timezone_id; - char * name; -}; - -static void -setlocation_data_free (struct setlocation_data * data) -{ - g_free (data->timezone_id); - g_free (data->name); - g_slice_free (struct setlocation_data, data); -} - -static void -on_datetime1_set_timezone_response (GObject * object, - GAsyncResult * res, - gpointer gdata) -{ - GError * err; - GVariant * answers; - struct setlocation_data * data = gdata; - - err = NULL; - answers = g_dbus_proxy_call_finish (G_DBUS_PROXY(object), res, &err); - if (err != NULL) - { - if (!g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - g_warning ("Could not set new timezone: %s", err->message); - - g_error_free (err); - } - else - { - char * timezone_name = g_strdup_printf ("%s %s", - data->timezone_id, - data->name); - - g_settings_set_string (data->service->priv->settings, - SETTINGS_TIMEZONE_NAME_S, - timezone_name); - - g_free (timezone_name); - g_variant_unref (answers); - } - - setlocation_data_free (data); -} - -static void -on_datetime1_proxy_ready (GObject * object G_GNUC_UNUSED, - GAsyncResult * res, - gpointer gdata) -{ - GError * err; - GDBusProxy * proxy; - struct setlocation_data * data = gdata; - - err = NULL; - proxy = g_dbus_proxy_new_for_bus_finish (res, &err); - if (err != NULL) - { - if (!g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - g_warning ("Could not grab DBus proxy for timedated: %s", err->message); - - g_error_free (err); - setlocation_data_free (data); - } - else - { - g_dbus_proxy_call (proxy, - "SetTimezone", - g_variant_new ("(sb)", data->timezone_id, TRUE), - G_DBUS_CALL_FLAGS_NONE, - -1, - data->service->priv->cancellable, - on_datetime1_set_timezone_response, - data); - - g_object_unref (proxy); - } -} - -static void -indicator_datetime_service_set_location (IndicatorDatetimeService * self, - const char * timezone_id, - const char * name) -{ - priv_t * p = self->priv; - struct setlocation_data * data; - - g_return_if_fail (INDICATOR_IS_DATETIME_SERVICE (self)); - g_return_if_fail (name && *name); - g_return_if_fail (timezone_id && *timezone_id); - - data = g_slice_new0 (struct setlocation_data); - data->timezone_id = g_strdup (timezone_id); - data->name = g_strdup (name); - data->service = self; - - g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, - G_DBUS_PROXY_FLAGS_NONE, - NULL, - "org.freedesktop.timedate1", - "/org/freedesktop/timedate1", - "org.freedesktop.timedate1", - p->cancellable, - on_datetime1_proxy_ready, - data); -} - -static void -on_set_location (GSimpleAction * a G_GNUC_UNUSED, - GVariant * param, - gpointer gself) -{ - char * zone; - char * name; - IndicatorDatetimeService * self; - - self = INDICATOR_DATETIME_SERVICE (gself); - split_settings_location (g_variant_get_string (param, NULL), &zone, &name); - indicator_datetime_service_set_location (self, zone, name); - - g_free (name); - g_free (zone); -} - -static void -on_calendar_active_changed (GSimpleAction *action G_GNUC_UNUSED, - GVariant *state, - gpointer user_data) -{ - IndicatorDatetimeService *self = user_data; - - /* reset the date when the menu is shown */ - if (g_variant_get_boolean (state) && self->priv->calendar_date) - { - g_clear_pointer (&self->priv->calendar_date, g_date_time_unref); - update_calendar_action_state (self); - } -} - -/*** -**** -***/ - -static GMenuModel * -create_desktop_settings_section (IndicatorDatetimeService * self G_GNUC_UNUSED) -{ - GMenu * menu = g_menu_new (); - g_menu_append (menu, _("Date & Time Settings…"), "indicator.activate-desktop-settings"); - return G_MENU_MODEL (menu); -} - -static GMenuModel * -create_phone_settings_section (IndicatorDatetimeService * self G_GNUC_UNUSED) -{ - GMenu * menu = g_menu_new (); - g_menu_append (menu, _("Time & Date settings…"), "indicator.activate-phone-settings"); - return G_MENU_MODEL (menu); -} - -static void -create_menu (IndicatorDatetimeService * self, int profile) -{ - GMenu * menu; - GMenu * submenu; - GMenuItem * header; - GMenuModel * sections[16]; - const gchar * header_action; - int i; - int n = 0; - - g_assert (0<=profile && profile<N_PROFILES); - g_assert (self->priv->menus[profile].menu == NULL); - - switch (profile) - { - case PROFILE_PHONE: - sections[n++] = create_calendar_section (self, profile); - sections[n++] = create_phone_appointments_section (self); - sections[n++] = create_phone_settings_section (self); - header_action = "indicator.phone-header"; - break; - - case PROFILE_DESKTOP: - sections[n++] = create_calendar_section (self, profile); - sections[n++] = create_desktop_appointments_section (self); - sections[n++] = create_locations_section (self); - sections[n++] = create_desktop_settings_section (self); - header_action = "indicator.desktop-header"; - break; - - case PROFILE_GREETER: - sections[n++] = create_calendar_section (self, profile); - header_action = "indicator.desktop-header"; - break; - } - - /* add sections to the submenu */ - - submenu = g_menu_new (); - - for (i=0; i<n; ++i) - { - g_menu_append_section (submenu, NULL, sections[i]); - g_object_unref (sections[i]); - } - - /* add submenu to the header */ - header = g_menu_item_new (NULL, header_action); - g_menu_item_set_attribute (header, "x-canonical-type", - "s", "com.canonical.indicator.root"); - g_menu_item_set_submenu (header, G_MENU_MODEL (submenu)); - g_menu_item_set_attribute (header, "submenu-action", "s", "indicator.calendar-active"); - g_object_unref (submenu); - - /* add header to the menu */ - menu = g_menu_new (); - g_menu_append_item (menu, header); - g_object_unref (header); - - self->priv->menus[profile].menu = menu; - self->priv->menus[profile].submenu = submenu; -} - -/*** -**** GActions -***/ - -/* Run a particular program based on an activation */ -static void -execute_command (const gchar * cmd) -{ - GError * err = NULL; - - g_debug ("Issuing command '%s'", cmd); - - if (!g_spawn_command_line_async (cmd, &err)) - { - g_warning ("Unable to start \"%s\": %s", cmd, err->message); - g_error_free (err); - } -} - -static void -on_desktop_settings_activated (GSimpleAction * a G_GNUC_UNUSED, - GVariant * param G_GNUC_UNUSED, - gpointer gself G_GNUC_UNUSED) -{ - gchar *path; - - path = g_find_program_in_path ("unity-control-center"); - if (path != NULL && 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); -} - -static void -on_phone_settings_activated (GSimpleAction * a G_GNUC_UNUSED, - GVariant * param G_GNUC_UNUSED, - gpointer gself G_GNUC_UNUSED) -{ - url_dispatch_send ("settings:///system/time-date", NULL, NULL); -} - -static void -on_activate_appointment (GSimpleAction * a G_GNUC_UNUSED, - GVariant * param, - gpointer gself) -{ - priv_t * p = INDICATOR_DATETIME_SERVICE(gself)->priv; - const gchar * uid = g_variant_get_string (param, NULL); - - if (uid != NULL) - { - const struct IndicatorDatetimeAppt * appt; - GSList * l; - - /* find the appointment that matches that uid */ - for (l=p->upcoming_appointments, appt=NULL; l && !appt; l=l->next) - { - const struct IndicatorDatetimeAppt * tmp = l->data; - if (!g_strcmp0 (uid, tmp->uid)) - appt = tmp; - } - - /* if that appointment's an alarm, dispatch its url */ - g_debug ("%s: uri '%s'; matching appt is %p", G_STRFUNC, uid, (void*)appt); - if (appt && appointment_has_alarm_url (appt)) - dispatch_alarm_url (appt); - } -} - -static void -on_phone_clock_activated (GSimpleAction * a G_GNUC_UNUSED, - GVariant * param G_GNUC_UNUSED, - gpointer gself G_GNUC_UNUSED) -{ - const char * url = "appid://com.ubuntu.clock/clock/current-user-version"; - url_dispatch_send (url, NULL, NULL); -} - -static void -on_activate_planner (GSimpleAction * a G_GNUC_UNUSED, - GVariant * param, - gpointer gself) -{ - priv_t * p = INDICATOR_DATETIME_SERVICE(gself)->priv; - - if (p->planner != NULL) - { - const gint64 t = g_variant_get_int64 (param); - if (t) - { - GDateTime * date_time = g_date_time_new_from_unix_local (t); - indicator_datetime_planner_activate_time (p->planner, date_time); - g_date_time_unref (date_time); - } - else /* no time specified... */ - { - indicator_datetime_planner_activate (p->planner); - } - } -} - -static void -on_calendar_action_activated (GSimpleAction * action G_GNUC_UNUSED, - GVariant * state, - gpointer gself) -{ - gint64 unix_time; - IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (gself); - - if ((unix_time = g_variant_get_int64 (state))) - { - GDateTime * date = g_date_time_new_from_unix_local (unix_time); - indicator_datetime_service_set_calendar_date (self, date); - g_date_time_unref (date); - } - else /* unset */ - { - indicator_datetime_service_set_calendar_date (self, NULL); - } -} - - -static void -init_gactions (IndicatorDatetimeService * self) -{ - GSimpleAction * a; - priv_t * p = self->priv; - - GActionEntry entries[] = { - { "activate-desktop-settings", on_desktop_settings_activated }, - { "activate-phone-settings", on_phone_settings_activated }, - { "activate-phone-clock-app", on_phone_clock_activated }, - { "activate-planner", on_activate_planner, "x", NULL }, - { "activate-appointment", on_activate_appointment, "s", NULL }, - { "set-location", on_set_location, "s" }, - { "calendar-active", NULL, NULL, "false", on_calendar_active_changed } - }; - - p->actions = g_simple_action_group_new (); - - g_action_map_add_action_entries (G_ACTION_MAP(p->actions), - entries, - G_N_ELEMENTS(entries), - self); - - /* add the header actions */ - - a = g_simple_action_new_stateful ("desktop-header", NULL, - create_desktop_header_state (self)); - g_action_map_add_action (G_ACTION_MAP(p->actions), G_ACTION(a)); - p->desktop_header_action = a; - - a = g_simple_action_new_stateful ("phone-header", NULL, - create_phone_header_state (self)); - g_action_map_add_action (G_ACTION_MAP(p->actions), G_ACTION(a)); - p->phone_header_action = a; - - /* add the calendar action */ - a = g_simple_action_new_stateful ("calendar", - G_VARIANT_TYPE_INT64, - create_calendar_state (self)); - g_action_map_add_action (G_ACTION_MAP(p->actions), G_ACTION(a)); - g_signal_connect (a, "activate", - G_CALLBACK(on_calendar_action_activated), self); - p->calendar_action = a; - - rebuild_now (self, SECTION_HEADER); -} - -/*** -**** -***/ - -/** - * A small helper function for rebuild_now(). - * - removes the previous section - * - adds and unrefs the new section - */ -static void -rebuild_section (GMenu * parent, int pos, GMenuModel * new_section) -{ - g_menu_remove (parent, pos); - g_menu_insert_section (parent, pos, NULL, new_section); - g_object_unref (new_section); -} - -static void -rebuild_now (IndicatorDatetimeService * self, int sections) -{ - priv_t * p = self->priv; - struct ProfileMenuInfo * phone = &p->menus[PROFILE_PHONE]; - struct ProfileMenuInfo * desktop = &p->menus[PROFILE_DESKTOP]; - struct ProfileMenuInfo * greeter = &p->menus[PROFILE_GREETER]; - - if (p->actions == NULL) - return; - - if (sections & SECTION_HEADER) - { - g_simple_action_set_state (p->desktop_header_action, - create_desktop_header_state (self)); - g_simple_action_set_state (p->phone_header_action, - create_phone_header_state (self)); - } - - if (sections & SECTION_CALENDAR) - { - rebuild_section (phone->submenu, 0, create_calendar_section(self, PROFILE_PHONE)); - rebuild_section (desktop->submenu, 0, create_calendar_section(self, PROFILE_DESKTOP)); - rebuild_section (greeter->submenu, 0, create_calendar_section(self, PROFILE_GREETER)); - } - - if (sections & SECTION_APPOINTMENTS) - { - rebuild_section (phone->submenu, 1, create_phone_appointments_section (self)); - rebuild_section (desktop->submenu, 1, create_desktop_appointments_section (self)); - } - - if (sections & SECTION_LOCATIONS) - { - rebuild_section (desktop->submenu, 2, create_locations_section (self)); - } - - if (sections & SECTION_SETTINGS) - { - rebuild_section (phone->submenu, 2, create_phone_settings_section (self)); - rebuild_section (desktop->submenu, 3, create_desktop_settings_section (self)); - } -} - -static int -rebuild_timeout_func (IndicatorDatetimeService * self) -{ - priv_t * p = self->priv; - rebuild_now (self, p->rebuild_flags); - p->rebuild_flags = 0; - p->rebuild_id = 0; - return G_SOURCE_REMOVE; -} - -static void -rebuild_soon (IndicatorDatetimeService * self, int section) -{ - priv_t * p = self->priv; - - p->rebuild_flags |= section; - - if (p->rebuild_id == 0) - { - /* Change events seem to come over the bus in small bursts. This msec - value is an arbitrary number that tries to be large enough to fold - multiple events into a single rebuild, but small enough that the - user won't notice any lag. */ - static const int REBUILD_INTERVAL_MSEC = 500; - - p->rebuild_id = g_timeout_add (REBUILD_INTERVAL_MSEC, - (GSourceFunc)rebuild_timeout_func, - self); - } -} - -/*** -**** org.freedesktop.login1.Manager -***/ - -static void -on_login1_manager_signal (GDBusProxy * proxy G_GNUC_UNUSED, - gchar * sender_name G_GNUC_UNUSED, - gchar * signal_name, - GVariant * parameters, - gpointer gself) -{ - if (!g_strcmp0 (signal_name, "PrepareForSleep")) - { - gboolean sleeping = FALSE; - g_variant_get (parameters, "(b)", &sleeping); - if (!sleeping) - on_local_time_jumped (INDICATOR_DATETIME_SERVICE (gself)); - } -} - -static void -on_login1_manager_proxy_ready (GObject * object G_GNUC_UNUSED, - GAsyncResult * res, - gpointer gself) -{ - GError * err; - GDBusProxy * proxy; - - err = NULL; - proxy = g_dbus_proxy_new_for_bus_finish (res, &err); - - if (err != NULL) - { - if (!g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - g_warning ("Could not grab DBus proxy for logind: %s", err->message); - - g_error_free (err); - } - else - { - IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (gself); - self->priv->login1_manager = proxy; - g_signal_connect (proxy, "g-signal", - G_CALLBACK(on_login1_manager_signal), self); - } -} - -/*** -**** Appointments -***/ - -static void -set_calendar_appointments (IndicatorDatetimeService * self, - GSList * appointments) -{ - priv_t * p = self->priv; - - /* repopulate the list */ - indicator_datetime_planner_free_appointments (p->calendar_appointments); - p->calendar_appointments = appointments; - - /* sync the menus/actions */ - update_calendar_action_state (self); - rebuild_calendar_section_soon (self); -} - -static void -on_calendar_appointments_ready (GObject * source, - GAsyncResult * res, - gpointer self) -{ - IndicatorDatetimePlanner * planner; - GError * error; - GSList * appointments; - - planner = INDICATOR_DATETIME_PLANNER (source); - error = NULL; - appointments = indicator_datetime_planner_get_appointments_finish (planner, - res, - &error); - - if (error != NULL) - { - if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - g_warning ("can't get this month's appointments: %s", error->message); - - g_error_free (error); - } - else - { - set_calendar_appointments (INDICATOR_DATETIME_SERVICE (self), - appointments); - } -} - -static void -set_upcoming_appointments (IndicatorDatetimeService * self, - GSList * appointments) -{ - priv_t * p = self->priv; - - /* repopulate the list */ - indicator_datetime_planner_free_appointments (p->upcoming_appointments); - p->upcoming_appointments = appointments; - - /* sync the menus/actions */ - rebuild_appointments_section_soon (self); - - /* alarm timer is keyed off of the next alarm time, - so it needs to be rebuilt when the appointment list changes */ - set_alarm_timer (self); -} - -static void -on_upcoming_appointments_ready (GObject * source, - GAsyncResult * res, - gpointer self) -{ - IndicatorDatetimePlanner * planner; - GError * error; - GSList * appointments; - - planner = INDICATOR_DATETIME_PLANNER (source); - error = NULL; - appointments = indicator_datetime_planner_get_appointments_finish (planner, - res, - &error); - - if (error != NULL) - { - if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - g_warning ("can't get upcoming appointments: %s", error->message); - - g_error_free (error); - } - else - { - set_upcoming_appointments (INDICATOR_DATETIME_SERVICE (self), - appointments); - } -} - -static void -update_appointment_lists (IndicatorDatetimeService * self) -{ - IndicatorDatetimePlanner * planner; - GDateTime * calendar_date; - GDateTime * begin; - GDateTime * end; - int y, m, d; - - planner = self->priv->planner; - calendar_date = get_calendar_date (self); - - /* 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(m,y), 23, 59, 59.9); - if (begin && end) - indicator_datetime_planner_get_appointments (planner, begin, end, - on_calendar_appointments_ready, - self); - 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) - indicator_datetime_planner_get_appointments (planner, begin, end, - on_upcoming_appointments_ready, - self); - 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); -} - - -/*** -**** GDBus -***/ - -static void -on_bus_acquired (GDBusConnection * connection, - const gchar * name, - gpointer gself) -{ - int i; - guint id; - GError * err = NULL; - IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE(gself); - priv_t * p = self->priv; - - g_debug ("bus acquired: %s", name); - - p->conn = g_object_ref (G_OBJECT (connection)); - - /* export the actions */ - if ((id = g_dbus_connection_export_action_group (connection, - BUS_PATH, - G_ACTION_GROUP (p->actions), - &err))) - { - p->actions_export_id = id; - } - else - { - g_warning ("cannot export action group: %s", err->message); - g_clear_error (&err); - } - - /* export the menus */ - for (i=0; i<N_PROFILES; ++i) - { - char * path = g_strdup_printf ("%s/%s", BUS_PATH, menu_names[i]); - struct ProfileMenuInfo * menu = &p->menus[i]; - - if ((id = g_dbus_connection_export_menu_model (connection, - path, - G_MENU_MODEL (menu->menu), - &err))) - { - menu->export_id = id; - } - else - { - g_warning ("cannot export %s menu: %s", menu_names[i], err->message); - g_clear_error (&err); - } - - g_free (path); - } -} - -static void -unexport (IndicatorDatetimeService * self) -{ - int i; - priv_t * p = self->priv; - - /* unexport the menus */ - for (i=0; i<N_PROFILES; ++i) - { - guint * id = &self->priv->menus[i].export_id; - - if (*id) - { - g_dbus_connection_unexport_menu_model (p->conn, *id); - *id = 0; - } - } - - /* unexport the actions */ - if (p->actions_export_id) - { - g_dbus_connection_unexport_action_group (p->conn, p->actions_export_id); - p->actions_export_id = 0; - } -} - -static void -on_name_lost (GDBusConnection * connection G_GNUC_UNUSED, - const gchar * name, - gpointer gself) -{ - IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (gself); - - g_debug ("%s %s name lost %s", G_STRLOC, G_STRFUNC, name); - - unexport (self); - - g_signal_emit (self, signals[SIGNAL_NAME_LOST], 0, NULL); -} - - -/*** -**** GObject virtual functions -***/ - -static void -my_get_property (GObject * o, - guint property_id, - GValue * value, - GParamSpec * pspec) -{ - IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (o); - - switch (property_id) - { - case PROP_CLOCK: - g_value_set_object (value, self->priv->clock); - break; - - case PROP_PLANNER: - g_value_set_object (value, self->priv->planner); - 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) -{ - IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (o); - - switch (property_id) - { - case PROP_CLOCK: - indicator_datetime_service_set_clock (self, g_value_get_object (value)); - break; - - case PROP_PLANNER: - indicator_datetime_service_set_planner (self, g_value_get_object (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec); - } -} - - -static void -my_dispose (GObject * o) -{ - int i; - IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE(o); - priv_t * p = self->priv; - - if (p->own_id) - { - g_bus_unown_name (p->own_id); - p->own_id = 0; - } - - unexport (self); - - if (p->cancellable != NULL) - { - g_cancellable_cancel (p->cancellable); - g_clear_object (&p->cancellable); - } - - indicator_datetime_service_set_clock (self, NULL); - indicator_datetime_service_set_planner (self, NULL); - - if (p->login1_manager != NULL) - { - g_signal_handlers_disconnect_by_data (p->login1_manager, self); - g_clear_object (&p->login1_manager); - } - - indicator_clear_timer (&p->skew_timer); - indicator_clear_timer (&p->rebuild_id); - indicator_clear_timer (&p->timezone_timer); - indicator_clear_timer (&p->header_timer); - indicator_clear_timer (&p->alarm_timer); - - if (p->settings != NULL) - { - g_signal_handlers_disconnect_by_data (p->settings, self); - g_clear_object (&p->settings); - } - - g_clear_object (&p->actions); - - for (i=0; i<N_PROFILES; ++i) - g_clear_object (&p->menus[i].menu); - - g_clear_object (&p->calendar_action); - g_clear_object (&p->desktop_header_action); - g_clear_object (&p->phone_header_action); - g_clear_object (&p->conn); - - /* clear the serialized icon cache */ - g_clear_pointer (&p->alarm_icon_serialized, g_variant_unref); - g_clear_pointer (&p->calendar_icon_serialized, g_variant_unref); - g_clear_pointer (&p->clock_app_icon_serialized, g_variant_unref); - - G_OBJECT_CLASS (indicator_datetime_service_parent_class)->dispose (o); -} - -static void -my_finalize (GObject * o) -{ - IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE(o); - priv_t * p = self->priv; - - g_free (p->header_label_format_string); - g_clear_pointer (&p->skew_time, g_date_time_unref); - g_clear_pointer (&p->calendar_date, g_date_time_unref); - - G_OBJECT_CLASS (indicator_datetime_service_parent_class)->finalize (o); -} - -/*** -**** Instantiation -***/ - -static void -indicator_datetime_service_init (IndicatorDatetimeService * self) -{ - GIcon * icon; - priv_t * p; - - /* init the priv pointer */ - - p = G_TYPE_INSTANCE_GET_PRIVATE (self, - INDICATOR_TYPE_DATETIME_SERVICE, - IndicatorDatetimeServicePrivate); - self->priv = p; - - p->cancellable = g_cancellable_new (); - - p->settings = g_settings_new (SETTINGS_INTERFACE); - - /* build the serialized icon cache */ - - icon = g_themed_icon_new_with_default_fallbacks (ALARM_CLOCK_ICON_NAME); - p->alarm_icon_serialized = g_icon_serialize (icon); - g_object_unref (icon); - - icon = g_themed_icon_new_with_default_fallbacks ("calendar"); - p->calendar_icon_serialized = g_icon_serialize (icon); - g_object_unref (icon); - - p->clock_app_icon_serialized = get_clock_app_icon (); -} - -static void -my_constructed (GObject * gself) -{ - IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (gself); - priv_t * p = self->priv; - GString * gstr = g_string_new (NULL); - guint i, n; - - /* these are the settings that affect the - contents of the respective sections */ - const char * const header_settings[] = { - SETTINGS_SHOW_CLOCK_S, - SETTINGS_TIME_FORMAT_S, - SETTINGS_SHOW_SECONDS_S, - SETTINGS_SHOW_DAY_S, - SETTINGS_SHOW_DATE_S, - SETTINGS_SHOW_YEAR_S, - SETTINGS_CUSTOM_TIME_FORMAT_S - }; - const char * const calendar_settings[] = { - SETTINGS_SHOW_CALENDAR_S, - SETTINGS_SHOW_WEEK_NUMBERS_S - }; - const char * const appointment_settings[] = { - SETTINGS_SHOW_EVENTS_S, - SETTINGS_TIME_FORMAT_S, - SETTINGS_SHOW_SECONDS_S - }; - const char * const location_settings[] = { - SETTINGS_TIME_FORMAT_S, - SETTINGS_SHOW_SECONDS_S, - SETTINGS_CUSTOM_TIME_FORMAT_S, - SETTINGS_SHOW_LOCATIONS_S, - SETTINGS_LOCATIONS_S, - SETTINGS_SHOW_DETECTED_S, - SETTINGS_TIMEZONE_NAME_S - }; - const char * const time_format_string_settings[] = { - SETTINGS_TIME_FORMAT_S, - SETTINGS_SHOW_SECONDS_S, - SETTINGS_CUSTOM_TIME_FORMAT_S - }; - - - /*** - **** Listen for settings changes - ***/ - - for (i=0, n=G_N_ELEMENTS(header_settings); i<n; i++) - { - g_string_printf (gstr, "changed::%s", header_settings[i]); - g_signal_connect_swapped (p->settings, gstr->str, - G_CALLBACK(rebuild_header_soon), self); - } - - for (i=0, n=G_N_ELEMENTS(calendar_settings); i<n; i++) - { - g_string_printf (gstr, "changed::%s", calendar_settings[i]); - g_signal_connect_swapped (p->settings, gstr->str, - G_CALLBACK(rebuild_calendar_section_soon), self); - } - - for (i=0, n=G_N_ELEMENTS(appointment_settings); i<n; i++) - { - g_string_printf (gstr, "changed::%s", appointment_settings[i]); - g_signal_connect_swapped (p->settings, gstr->str, - G_CALLBACK(rebuild_appointments_section_soon), self); - } - - for (i=0, n=G_N_ELEMENTS(location_settings); i<n; i++) - { - g_string_printf (gstr, "changed::%s", location_settings[i]); - g_signal_connect_swapped (p->settings, gstr->str, - G_CALLBACK(rebuild_locations_section_soon), self); - } - - /* The keys in time_format_string_settings affect the time format strings we build. - When these change, we need to rebuild everything that has a time format string. */ - for (i=0, n=G_N_ELEMENTS(time_format_string_settings); i<n; i++) - { - g_string_printf (gstr, "changed::%s", time_format_string_settings[i]); - g_signal_connect_swapped (p->settings, gstr->str, - G_CALLBACK(on_local_time_jumped), self); - } - - init_gactions (self); - - g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, - G_DBUS_PROXY_FLAGS_NONE, - NULL, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - p->cancellable, - on_login1_manager_proxy_ready, - self); - - p->skew_timer = g_timeout_add_seconds (SKEW_CHECK_INTERVAL_SEC, - skew_timer_func, - self); - - p->own_id = g_bus_own_name (G_BUS_TYPE_SESSION, - BUS_NAME, - G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT, - on_bus_acquired, - NULL, - on_name_lost, - self, - NULL); - - on_local_time_jumped (self); - - set_alarm_timer (self); - - for (i=0; i<N_PROFILES; ++i) - create_menu (self, i); - - g_string_free (gstr, TRUE); -} - -static void -indicator_datetime_service_class_init (IndicatorDatetimeServiceClass * klass) -{ - GObjectClass * object_class = G_OBJECT_CLASS (klass); - const GParamFlags flags = G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS; - - object_class->dispose = my_dispose; - object_class->finalize = my_finalize; - object_class->constructed = my_constructed; - object_class->get_property = my_get_property; - object_class->set_property = my_set_property; - - g_type_class_add_private (klass, sizeof (IndicatorDatetimeServicePrivate)); - - signals[SIGNAL_NAME_LOST] = g_signal_new ( - INDICATOR_DATETIME_SERVICE_SIGNAL_NAME_LOST, - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (IndicatorDatetimeServiceClass, name_lost), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); - - /* install properties */ - - properties[PROP_0] = NULL; - - properties[PROP_CLOCK] = g_param_spec_object ("clock", - "Clock", - "The clock", - INDICATOR_TYPE_DATETIME_CLOCK, - flags); - - properties[PROP_PLANNER] = g_param_spec_object ("planner", - "Planner", - "The appointment provider", - INDICATOR_TYPE_DATETIME_PLANNER, - flags); - - g_object_class_install_properties (object_class, PROP_LAST, properties); -} - -/*** -**** Public API -***/ - -IndicatorDatetimeService * -indicator_datetime_service_new (IndicatorDatetimeClock * clock, - IndicatorDatetimePlanner * planner) -{ - GObject * o = g_object_new (INDICATOR_TYPE_DATETIME_SERVICE, - "clock", clock, - "planner", planner, - NULL); - - return INDICATOR_DATETIME_SERVICE (o); -} - -void -indicator_datetime_service_set_calendar_date (IndicatorDatetimeService * self, - GDateTime * date) -{ - gboolean dirty; - priv_t * p = self->priv; - - dirty = !date || !p->calendar_date || g_date_time_compare (date, p->calendar_date); - - /* update calendar_date */ - g_clear_pointer (&p->calendar_date, g_date_time_unref); - if (date != NULL) - p->calendar_date = g_date_time_ref (date); - - /* sync the menuitems and action states */ - if (dirty) - update_appointment_lists (self); -} - -static void -on_clock_changed (IndicatorDatetimeService * self) -{ - on_local_time_jumped (self); -} - -void -indicator_datetime_service_set_clock (IndicatorDatetimeService * self, - IndicatorDatetimeClock * clock) -{ - priv_t * p; - - g_return_if_fail (INDICATOR_IS_DATETIME_SERVICE (self)); - g_return_if_fail ((clock == NULL) || INDICATOR_IS_DATETIME_CLOCK (clock)); - - p = self->priv; - - /* clear the old clock */ - - if (p->clock != NULL) - { - g_signal_handlers_disconnect_by_data (p->clock, self); - g_clear_object (&p->clock); - } - - /* set the new clock */ - - if (clock != NULL) - { - p->clock = g_object_ref (clock); - - g_signal_connect_swapped (p->clock, "changed", - G_CALLBACK(on_clock_changed), self); - on_clock_changed (self); - } -} - -void -indicator_datetime_service_set_planner (IndicatorDatetimeService * self, - IndicatorDatetimePlanner * planner) -{ - priv_t * p; - - g_return_if_fail (INDICATOR_IS_DATETIME_SERVICE (self)); - g_return_if_fail ((planner == NULL) || INDICATOR_IS_DATETIME_PLANNER (planner)); - - p = self->priv; - - /* clear the old planner & appointments */ - - if (p->planner != NULL) - { - g_signal_handlers_disconnect_by_data (p->planner, self); - g_clear_object (&p->planner); - } - - g_clear_pointer (&p->upcoming_appointments, indicator_datetime_planner_free_appointments); - g_clear_pointer (&p->calendar_appointments, indicator_datetime_planner_free_appointments); - - /* set the new planner & begin fetching appointments from it */ - - if (planner != NULL) - { - p->planner = g_object_ref (planner); - - g_signal_connect_swapped (p->planner, "appointments-changed", - G_CALLBACK(update_appointment_lists), self); - - update_appointment_lists (self); - } -} 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/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..76737b4 --- /dev/null +++ b/src/timezone-file.cpp @@ -0,0 +1,103 @@ +/* + * 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 { + +FileTimezone::FileTimezone() +{ +} + +FileTimezone::FileTimezone(const std::string& filename) +{ + setFilename(filename); +} + +FileTimezone::~FileTimezone() +{ + clear(); +} + +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..4902b76 --- /dev/null +++ b/src/timezones-live.cpp @@ -0,0 +1,72 @@ +/* + * 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(const std::shared_ptr<const 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 index c90f2e7..f4eb53f 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1,212 +1,142 @@ -/* -*- 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> +/* + * Copyright 2010, 2014 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: + * Michael Terry <michael.terry@canonical.com> + * 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. +#include <datetime/utils.h> +#include <datetime/settings-shared.h> -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 <glib/gi18n.h> -#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) +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; + int i; + static const char *formats_24h[] = {"%H", "%R", "%T", "%OH", "%k", NULL}; + const char* t_fmt = nl_langinfo(T_FMT); - for (i = 0; formats_24h[i]; ++i) { - if (strstr (t_fmt, formats_24h[i])) { - return FALSE; - } - } + for (i=0; formats_24h[i]!=NULL; i++) + if (strstr(t_fmt, formats_24h[i]) != NULL) + return FALSE; - return TRUE; + return TRUE; } void -split_settings_location (const gchar * location, gchar ** zone, gchar ** name) +split_settings_location(const gchar* location, gchar** zone, gchar** name) { - gchar * location_dup; - gchar * first; + gchar* location_dup = g_strdup(location); + if(location_dup != NULL) + g_strstrip(location_dup); - location_dup = g_strdup (location); - g_strstrip (location_dup); + gchar* first; + if(location_dup && (first = strchr(location_dup, ' '))) + *first = '\0'; - if ((first = strchr (location_dup, ' '))) - *first = '\0'; - - if (zone != NULL) - { - *zone = location_dup; - } + if(zone) + *zone = location_dup; - if (name != NULL) + if(name != NULL) { - gchar * after = first ? g_strstrip (first + 1) : NULL; + gchar* after = first ? g_strstrip(first + 1) : NULL; - if (after && *after) + if(after && *after) { - *name = g_strdup (after); + *name = g_strdup(after); } - else /* make the name from zone */ + else if (location_dup) // make the name from zone { - gchar * chr = strrchr (location_dup, '/'); - after = g_strdup (chr ? chr + 1 : location_dup); + 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 = ' '; + // replace underscores with spaces + for(chr=after; chr && *chr; chr++) + if(*chr == '_') + *chr = ' '; - *name = after; + *name = after; + } + else + { + *name = NULL; } } } -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) +/** + * 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) { - /* 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* zone; + gchar* name; + split_settings_location(timezone_, &zone, &name); -gchar * -join_date_and_time_format_strings (const char * date_string, - const char * time_string) -{ - gchar * str; + gchar* saved_zone; + gchar* saved_name; + split_settings_location(saved_location, &saved_zone, &saved_name); - 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) + gchar* rv; + if (g_strcmp0(zone, saved_zone) == 0) { - str = g_strdup_printf (T_("%s"), date_string); + rv = saved_name; + saved_name = NULL; } - else /* time_string */ + else { - str = g_strdup_printf (T_("%s"), time_string); + rv = name; + name = NULL; } - return str; + g_free(zone); + g_free(name); + g_free(saved_zone); + g_free(saved_name); + return rv; } -/*** -**** -***/ - -static const gchar * -get_default_header_time_format (gboolean twelvehour, gboolean show_seconds) +gchar* +get_timezone_name(const gchar* timezone_, GSettings* settings) { - 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; + gchar* saved_location = g_settings_get_string(settings, SETTINGS_TIMEZONE_NAME_S); + gchar* rv = get_beautified_timezone_name(timezone_, saved_location); + g_free(saved_location); + return rv; } /*** @@ -215,252 +145,163 @@ get_default_header_time_format (gboolean twelvehour, gboolean show_seconds) typedef enum { - DATE_PROXIMITY_TODAY, - DATE_PROXIMITY_TOMORROW, - DATE_PROXIMITY_WEEK, - DATE_PROXIMITY_FAR + 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) +getDateProximity(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) + 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 * 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); + GDateTime* tomorrow = g_date_time_add_days(now, 1); - if (g_date_time_compare (time, week_bound) <= 0) - prox = DATE_PROXIMITY_WEEK; + 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 (week_bound); - g_date_time_unref (week); + g_date_time_unref(tomorrow); } - 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) + // does it happen this week? + if (prox == DATE_PROXIMITY_FAR) { - 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; -} + GDateTime* week = g_date_time_add_days(now, 6); + GDateTime* 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); -const gchar* -get_terse_header_time_format_string (void) -{ - const gboolean twelvehour = is_locale_12h (); - const gboolean show_seconds = FALSE; + if (g_date_time_compare(time, week_bound) <= 0) + prox = DATE_PROXIMITY_WEEK; - 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"); + g_date_time_unref(week_bound); + g_date_time_unref(week); } - 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); + return prox; } -/*** -**** FULL -***/ - -static const gchar * -get_full_date_format_string (gboolean show_day, gboolean show_date, gboolean show_year) +const char* +T_(const char *msg) { - 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; + /* 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. + */ + + gchar* message_locale = g_strdup(setlocale(LC_MESSAGES, NULL)); + const char* time_locale = setlocale(LC_TIME, NULL); + gchar* language = g_strdup(g_getenv("LANGUAGE")); + + if (language) + g_unsetenv("LANGUAGE"); + setlocale(LC_MESSAGES, time_locale); + + /* Get the LC_TIME version */ + const char* 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; } -/* - * "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. - * +/** + * _ 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”. */ - -const gchar * -get_full_time_format_string (GSettings * settings) +char* generate_full_format_string_at_time (GDateTime* now, + GDateTime* then, + GDateTime* then_end) { - gboolean twelvehour; - gboolean show_seconds; - - g_return_val_if_fail (settings != NULL, NULL); - - show_seconds = g_settings_get_boolean (settings, SETTINGS_SHOW_SECONDS_S); + GString* ret = g_string_new (NULL); - switch (g_settings_get_enum (settings, SETTINGS_TIME_FORMAT_S)) + if (then != NULL) { - 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; + const gboolean full_day = then_end && (g_date_time_difference(then_end, then) >= G_TIME_SPAN_DAY); + const date_proximity_t prox = getDateProximity(now, then); - g_return_val_if_fail (now != NULL, NULL); - g_return_val_if_fail (time != NULL, NULL); - g_return_val_if_fail (settings != NULL, NULL); + if (full_day) + { + switch (prox) + { + case DATE_PROXIMITY_TODAY: g_string_assign (ret, T_("Today")); break; + case DATE_PROXIMITY_TOMORROW: g_string_assign (ret, T_("Tomorrow")); break; + case DATE_PROXIMITY_WEEK: g_string_assign (ret, T_("%A")); break; + case DATE_PROXIMITY_FAR: g_string_assign (ret, T_("%a %d %b")); break; + } + } + else if (is_locale_12h()) + { + switch (prox) + { + case DATE_PROXIMITY_TODAY: g_string_assign (ret, T_("%l:%M %p")); break; + case DATE_PROXIMITY_TOMORROW: g_string_assign (ret, T_("Tomorrow\u2003%l:%M %p")); break; + case DATE_PROXIMITY_WEEK: g_string_assign (ret, T_("%a\u2003%l:%M %p")); break; + case DATE_PROXIMITY_FAR: g_string_assign (ret, T_("%a %d %b\u2003%l:%M %p")); break; + } + } + else + { + switch (prox) + { + case DATE_PROXIMITY_TODAY: g_string_assign (ret, T_("%H:%M")); break; + case DATE_PROXIMITY_TOMORROW: g_string_assign (ret, T_("Tomorrow\u2003%H:%M")); break; + case DATE_PROXIMITY_WEEK: g_string_assign (ret, T_("%a\u2003%H:%M")); break; + case DATE_PROXIMITY_FAR: g_string_assign (ret, T_("%a %d %b\u2003%H:%M")); break; + } + } - 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; + /* 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 != NULL) && + (!full_day) && + ((g_date_time_get_utc_offset(now) != g_date_time_get_utc_offset(then)))) + { + g_string_append_printf (ret, " %s", g_date_time_get_timezone_abbreviation(then)); + } } - return generate_full_format_string (show_day, show_date, FALSE, settings); + return g_string_free (ret, FALSE); } - 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 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6564a25..3dcd151 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -28,12 +28,40 @@ add_custom_command (OUTPUT gschemas.compiled # look for headers in our src dir, and also in the directories where we autogenerate files... include_directories (${CMAKE_SOURCE_DIR}/src) include_directories (${CMAKE_CURRENT_BINARY_DIR}) -include_directories (${SERVICE_DEPS_INCLUDE_DIRS}) +include_directories (${DBUSTEST_INCLUDE_DIRS}) -# test-formatter -set (TEST_NAME test-formatter) -add_executable (${TEST_NAME} test-formatter.cc) -add_test (${TEST_NAME} ${TEST_NAME}) -add_dependencies (${TEST_NAME} libindicatordatetimeservice) -target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS}) +add_definitions (-DSANDBOX="${CMAKE_CURRENT_BINARY_DIR}") + +function(add_test_by_name name) + set (TEST_NAME ${name}) + add_executable (${TEST_NAME} ${TEST_NAME}.cpp gschemas.compiled) + add_test (${TEST_NAME} ${TEST_NAME}) + add_dependencies (${TEST_NAME} libindicatordatetimeservice) + target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS}) +endfunction() +add_test_by_name(test-actions) +add_test_by_name(test-clock) +add_test_by_name(test-exporter) +add_test_by_name(test-formatter) +add_test_by_name(test-live-actions) +add_test_by_name(test-locations) +add_test_by_name(test-menus) +add_test_by_name(test-planner) +add_test_by_name(test-settings) +add_test_by_name(test-timezone-file) +add_test_by_name(test-utils) + + +# disabling the timezone unit tests because they require +# https://code.launchpad.net/~ted/dbus-test-runner/multi-interface-test/+merge/199724 +# which hasn't landed yet. These can be re-enabled as soon as that lands. +#function(add_dbusmock_test_by_name name) +# set (TEST_NAME ${name}) +# add_executable (${TEST_NAME} ${TEST_NAME}.cpp gschemas.compiled) +# add_test (${TEST_NAME} ${TEST_NAME}) +# add_dependencies (${TEST_NAME} libindicatordatetimeservice) +# target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${SERVICE_DEPS_LIBRARIES} ${DBUSTEST_LIBRARIES} ${GTEST_LIBS}) +#endfunction() +#add_dbusmock_test_by_name(test-timezone-geoclue) +#add_dbusmock_test_by_name(test-timezones) diff --git a/tests/Makefile.am.strings b/tests/Makefile.am.strings deleted file mode 100644 index 4a89e8f..0000000 --- a/tests/Makefile.am.strings +++ /dev/null @@ -1,38 +0,0 @@ -TESTS += \ - test-ellipsis \ - test-space-ellipsis \ - test-ascii-quotes - -##### -# Tests for there being proper ellipsis instead of three periods in a row -##### -test-ellipsis: $(top_srcdir)/po - @echo "#!/bin/bash" > $@ - @echo "(cd $(top_srcdir)/po && make $(GETTEXT_PACKAGE).pot)" >> $@ - @echo "grep -c -e \"^msgid.*\.\.\.\\\"\" $(top_srcdir)/po/$(GETTEXT_PACKAGE).pot > /dev/null && echo \"Ellipsis found in user visible strings\" >&2 && exit 1" >> $@ - @echo "exit 0" >> $@ - @chmod +x $@ - -##### -# Tests for there being a space before an ellipsis -##### -test-space-ellipsis: $(top_srcdir)/po - @echo "#!/bin/bash" > $@ - @echo "(cd $(top_srcdir)/po && make $(GETTEXT_PACKAGE).pot)" >> $@ - @echo "grep -c -e \"^msgid.* …\\\"\" $(top_srcdir)/po/$(GETTEXT_PACKAGE).pot > /dev/null && echo \"Space before ellipsis found in user visible strings\" >&2 && exit 1" >> $@ - @echo "exit 0" >> $@ - @chmod +x $@ - -##### -# Tests for ASCII quote types -##### -test-ascii-quotes: $(top_srcdir)/po - @echo "#!/bin/bash" > $@ - @echo "(cd $(top_srcdir)/po && make $(GETTEXT_PACKAGE).pot)" >> $@ - @echo "grep -c -e \"^msgid \\\".*'.*\\\"\" $(top_srcdir)/po/$(GETTEXT_PACKAGE).pot > /dev/null && echo \"ASCII apostrophe found in user visible strings\" >&2 && exit 1" >> $@ - @echo "grep -c -e \"^msgid \\\".*\\\".*\\\"\" $(top_srcdir)/po/$(GETTEXT_PACKAGE).pot > /dev/null && echo \"ASCII quote found in user visible strings\" >&2 && exit 1" >> $@ - @echo "grep -c -e \"^msgid \\\".*\\\`.*\\\"\" $(top_srcdir)/po/$(GETTEXT_PACKAGE).pot > /dev/null && echo \"ASCII backtick found in user visible strings\" >&2 && exit 1" >> $@ - @echo "exit 0" >> $@ - @chmod +x $@ - -CLEANFILES += $(TESTS) diff --git a/tests/actions-mock.h b/tests/actions-mock.h new file mode 100644 index 0000000..da93cb9 --- /dev/null +++ b/tests/actions-mock.h @@ -0,0 +1,82 @@ +/* + * 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> + */ + +#ifndef INDICATOR_DATETIME_ACTIONS_MOCK_H +#define INDICATOR_DATETIME_ACTIONS_MOCK_H + +#include <datetime/actions.h> + +#include <set> + +namespace unity { +namespace indicator { +namespace datetime { + +class MockActions: public Actions +{ +public: + MockActions(std::shared_ptr<State>& state_in): Actions(state_in) {} + ~MockActions() =default; + + enum Action { OpenDesktopSettings, OpenPhoneSettings, OpenPhoneClockApp, + OpenPlanner, OpenPlannerAt, OpenAppointment, SetLocation }; + const std::vector<Action>& history() const { return m_history; } + const DateTime& date_time() const { return m_date_time; } + const std::string& zone() const { return m_zone; } + const std::string& name() const { return m_name; } + const std::string& url() const { return m_url; } + void clear() { m_history.clear(); m_zone.clear(); m_name.clear(); } + + void open_desktop_settings() { m_history.push_back(OpenDesktopSettings); } + + void open_phone_settings() { m_history.push_back(OpenPhoneSettings); } + + void open_phone_clock_app() { m_history.push_back(OpenPhoneClockApp); } + + void open_planner() { m_history.push_back(OpenPlanner); } + + void open_planner_at(const DateTime& date_time_) { + m_history.push_back(OpenPlannerAt); + m_date_time = date_time_; + } + + void set_location(const std::string& zone_, const std::string& name_) { + m_history.push_back(SetLocation); + m_zone = zone_; + m_name = name_; + } + + void open_appointment(const std::string& url_) { + m_history.push_back(OpenAppointment); + m_url = url_; + } + +private: + std::string m_url; + std::string m_zone; + std::string m_name; + DateTime m_date_time; + std::vector<Action> m_history; +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_ACTIONS_MOCK_H diff --git a/tests/geoclue-fixture.h b/tests/geoclue-fixture.h new file mode 100644 index 0000000..7e29018 --- /dev/null +++ b/tests/geoclue-fixture.h @@ -0,0 +1,150 @@ +/* + * 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-fixture.h" + +#include <libdbustest/dbus-test.h> + +class GeoclueFixture : public GlibFixture +{ + private: + + typedef GlibFixture super; + + GDBusConnection * bus = nullptr; + + protected: + + DbusTestService * service = nullptr; + DbusTestDbusMock * mock = nullptr; + DbusTestDbusMockObject * obj_geo = nullptr; + DbusTestDbusMockObject * obj_geo_m = nullptr; + DbusTestDbusMockObject * obj_geo_mc = nullptr; + DbusTestDbusMockObject * obj_geo_addr = nullptr; + const std::string timezone_1 = "America/Denver"; + + void SetUp () + { + super::SetUp(); + + GError * error = nullptr; + const auto master_path = "/org/freedesktop/Geoclue/Master"; + const auto client_path = "/org/freedesktop/Geoclue/Master/client0"; + GString * gstr = g_string_new (nullptr); + + service = dbus_test_service_new (nullptr); + mock = dbus_test_dbus_mock_new ("org.freedesktop.Geoclue.Master"); + + auto interface = "org.freedesktop.Geoclue.Master"; + obj_geo_m = dbus_test_dbus_mock_get_object (mock, master_path, interface, nullptr); + g_string_printf (gstr, "ret = '%s'", client_path); + dbus_test_dbus_mock_object_add_method (mock, obj_geo_m, "Create", nullptr, G_VARIANT_TYPE_OBJECT_PATH, gstr->str, &error); + + interface = "org.freedesktop.Geoclue.MasterClient"; + obj_geo_mc = dbus_test_dbus_mock_get_object (mock, client_path, interface, nullptr); + dbus_test_dbus_mock_object_add_method (mock, obj_geo_mc, "SetRequirements", G_VARIANT_TYPE("(iibi)"), nullptr, "", &error); + dbus_test_dbus_mock_object_add_method (mock, obj_geo_mc, "AddressStart", nullptr, nullptr, "", &error); + + interface = "org.freedesktop.Geoclue"; + obj_geo = dbus_test_dbus_mock_get_object (mock, client_path, interface, nullptr); + dbus_test_dbus_mock_object_add_method (mock, obj_geo, "AddReference", nullptr, nullptr, "", &error); + g_string_printf (gstr, "ret = (1385238033, {'timezone': '%s'}, (3, 0.0, 0.0))", timezone_1.c_str()); + + interface = "org.freedesktop.Geoclue.Address"; + obj_geo_addr = dbus_test_dbus_mock_get_object (mock, client_path, interface, nullptr); + dbus_test_dbus_mock_object_add_method (mock, obj_geo_addr, "GetAddress", nullptr, G_VARIANT_TYPE("(ia{ss}(idd))"), gstr->str, &error); + + dbus_test_service_add_task(service, DBUS_TEST_TASK(mock)); + dbus_test_service_start_tasks(service); + + bus = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, nullptr); + g_dbus_connection_set_exit_on_close (bus, FALSE); + g_object_add_weak_pointer (G_OBJECT(bus), (gpointer*)&bus); + + g_string_free (gstr, TRUE); + } + + virtual void TearDown () + { + g_clear_object (&mock); + g_clear_object (&service); + g_object_unref (bus); + + unsigned int cleartry = 0; + while (bus != nullptr && cleartry < 10) + { + wait_msec (100); + cleartry++; + } + + // I've looked and can't find where this extra ref is coming from. + // is there an unbalanced ref to the bus in the test harness?! + while (bus != NULL) + { + g_object_unref (bus); + wait_msec (1000); + } + + super::TearDown (); + } + +private: + + struct EmitAddressChangedData + { + DbusTestDbusMock * mock = nullptr; + DbusTestDbusMockObject * obj_geo_addr = nullptr; + std::string timezone; + EmitAddressChangedData(DbusTestDbusMock* mock_, + DbusTestDbusMockObject* obj_geo_addr_, + const std::string& timezone_): mock(mock_), obj_geo_addr(obj_geo_addr_), timezone(timezone_) {} + }; + + static gboolean emit_address_changed_idle (gpointer gdata) + { + auto data = static_cast<EmitAddressChangedData*>(gdata); + auto fmt = g_strdup_printf ("(1385238033, {'timezone': '%s'}, (3, 0.0, 0.0))", data->timezone.c_str()); + + GError * error = nullptr; + dbus_test_dbus_mock_object_emit_signal(data->mock, data->obj_geo_addr, + //"org.freedesktop.Geoclue.Address", + "AddressChanged", + G_VARIANT_TYPE("(ia{ss}(idd))"), + g_variant_new_parsed (fmt), + &error); + if (error) + { + g_warning("%s: %s", G_STRFUNC, error->message); + g_error_free (error); + } + + g_free (fmt); + delete data; + return G_SOURCE_REMOVE; + } + +public: + + void setGeoclueTimezoneOnIdle (const std::string& newZone) + { + g_timeout_add (50, emit_address_changed_idle, new EmitAddressChangedData(mock, obj_geo_addr, newZone.c_str())); + } + +}; + diff --git a/tests/glib-fixture.h b/tests/glib-fixture.h index c6ecc68..1914b8c 100644 --- a/tests/glib-fixture.h +++ b/tests/glib-fixture.h @@ -25,96 +25,114 @@ #include <gtest/gtest.h> +#include <locale.h> // setlocale() + class GlibFixture : public ::testing::Test { private: - GLogFunc realLogHandler; + //GLogFunc realLogHandler; protected: std::map<GLogLevelFlags,int> logCounts; - void testLogCount (GLogLevelFlags log_level, int expected G_GNUC_UNUSED) + void testLogCount(GLogLevelFlags log_level, int /*expected*/) { - ASSERT_EQ (expected, logCounts[log_level]); +#if 0 + EXPECT_EQ(expected, logCounts[log_level]); +#endif - logCounts.erase (log_level); + logCounts.erase(log_level); } private: - static void default_log_handler (const gchar * log_domain, - GLogLevelFlags log_level, - const gchar * message, - gpointer self) + static void default_log_handler(const gchar * log_domain, + GLogLevelFlags log_level, + const gchar * message, + gpointer self) { - g_print ("%s - %d - %s", log_domain, (int)log_level, message); + g_print("%s - %d - %s\n", log_domain, (int)log_level, message); static_cast<GlibFixture*>(self)->logCounts[log_level]++; } protected: - virtual void SetUp () + virtual void SetUp() { - loop = g_main_loop_new (NULL, FALSE); + setlocale(LC_ALL, "C.UTF-8"); + + loop = g_main_loop_new(nullptr, false); - g_log_set_default_handler (default_log_handler, this); + //g_log_set_default_handler(default_log_handler, this); // only use local, temporary settings - g_setenv ("GSETTINGS_SCHEMA_DIR", SCHEMA_DIR, TRUE); - g_setenv ("GSETTINGS_BACKEND", "memory", TRUE); - g_debug ("SCHEMA_DIR is %s", SCHEMA_DIR); + g_assert(g_setenv("GSETTINGS_SCHEMA_DIR", SCHEMA_DIR, true)); + g_assert(g_setenv("GSETTINGS_BACKEND", "memory", true)); + g_debug("SCHEMA_DIR is %s", SCHEMA_DIR); + + g_unsetenv("DISPLAY"); + } virtual void TearDown() { +#if 0 // confirm there aren't any unexpected log messages - ASSERT_EQ (0, logCounts[G_LOG_LEVEL_ERROR]); - ASSERT_EQ (0, logCounts[G_LOG_LEVEL_CRITICAL]); - ASSERT_EQ (0, logCounts[G_LOG_LEVEL_WARNING]); - ASSERT_EQ (0, logCounts[G_LOG_LEVEL_MESSAGE]); - ASSERT_EQ (0, logCounts[G_LOG_LEVEL_INFO]); + EXPECT_EQ(0, logCounts[G_LOG_LEVEL_ERROR]); + EXPECT_EQ(0, logCounts[G_LOG_LEVEL_CRITICAL]); + EXPECT_EQ(0, logCounts[G_LOG_LEVEL_WARNING]); + EXPECT_EQ(0, logCounts[G_LOG_LEVEL_MESSAGE]); + EXPECT_EQ(0, logCounts[G_LOG_LEVEL_INFO]); +#endif // revert to glib's log handler - g_log_set_default_handler (realLogHandler, this); + //g_log_set_default_handler(realLogHandler, this); - g_clear_pointer (&loop, g_main_loop_unref); + g_clear_pointer(&loop, g_main_loop_unref); } private: static gboolean - wait_for_signal__timeout (gpointer name) + wait_for_signal__timeout(gpointer name) { - g_error ("%s: timed out waiting for signal '%s'", G_STRLOC, (char*)name); + g_error("%s: timed out waiting for signal '%s'", G_STRLOC, (char*)name); return G_SOURCE_REMOVE; } + static gboolean + wait_msec__timeout(gpointer loop) + { + g_main_loop_quit(static_cast<GMainLoop*>(loop)); + return G_SOURCE_CONTINUE; + } + protected: /* convenience func to loop while waiting for a GObject's signal */ - void wait_for_signal (gpointer o, const gchar * signal, const int timeout_seconds=5) + void wait_for_signal(gpointer o, const gchar * signal, const int timeout_seconds=5) { // wait for the signal or for timeout, whichever comes first - guint handler_id = g_signal_connect_swapped (o, signal, - G_CALLBACK(g_main_loop_quit), - loop); - gulong timeout_id = g_timeout_add_seconds (timeout_seconds, - wait_for_signal__timeout, - loop); - g_main_loop_run (loop); - g_source_remove (timeout_id); - g_signal_handler_disconnect (o, handler_id); + const auto handler_id = g_signal_connect_swapped(o, signal, + G_CALLBACK(g_main_loop_quit), + loop); + const auto timeout_id = g_timeout_add_seconds(timeout_seconds, + wait_for_signal__timeout, + loop); + g_main_loop_run(loop); + g_source_remove(timeout_id); + g_signal_handler_disconnect(o, handler_id); } /* convenience func to loop for N msec */ - void wait_msec (int msec=50) + void wait_msec(int msec=50) { - guint id = g_timeout_add (msec, (GSourceFunc)g_main_loop_quit, loop); - g_main_loop_run (loop); - g_source_remove (id); + const auto id = g_timeout_add(msec, wait_msec__timeout, loop); + g_main_loop_run(loop); + g_source_remove(id); } - GMainLoop * loop; + GMainLoop * loop; }; diff --git a/tests/planner-mock.c b/tests/planner-mock.c deleted file mode 100644 index e67ad7e..0000000 --- a/tests/planner-mock.c +++ /dev/null @@ -1,178 +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 "config.h" - -#include "planner-mock.h" - -struct _IndicatorDatetimePlannerMockPriv -{ - gboolean is_configured; -}; - -typedef IndicatorDatetimePlannerMockPriv priv_t; - -G_DEFINE_TYPE (IndicatorDatetimePlannerMock, - indicator_datetime_planner_mock, - INDICATOR_TYPE_DATETIME_PLANNER) - -/*** -**** IndicatorDatetimePlanner virtual funcs -***/ - -static void -my_get_appointments (IndicatorDatetimePlanner * planner, - GDateTime * begin_datetime, - GDateTime * end_datetime G_GNUC_UNUSED, - GAsyncReadyCallback callback, - gpointer user_data) -{ - GTask * task; - GSList * appointments; - struct IndicatorDatetimeAppt * appt; - struct IndicatorDatetimeAppt * prev; - - task = g_task_new (planner, NULL, callback, user_data); - - /** - *** Build the appointments list - **/ - - appointments = NULL; - - /* add a daily appointment that occurs at the beginning of the next minute */ - appt = g_slice_new0 (struct IndicatorDatetimeAppt); - appt->is_daily = TRUE; - appt->begin = g_date_time_add_seconds (begin_datetime, 60-g_date_time_get_seconds(begin_datetime)); - appt->end = g_date_time_add_minutes (appt->begin, 1); - appt->color = g_strdup ("#00FF00"); - appt->is_event = TRUE; - appt->summary = g_strdup ("Daily alarm"); - appt->uid = g_strdup ("this uid isn't very random."); - appt->has_alarms = TRUE; - appt->url = g_strdup ("alarm:///some-alarm-info-goes-here"); - appointments = g_slist_prepend (appointments, appt); - prev = appt; - - /* and add one for a minute later that has an alarm uri */ - appt = g_slice_new0 (struct IndicatorDatetimeAppt); - appt->is_daily = TRUE; - appt->begin = g_date_time_add_minutes (prev->end, 1); - appt->end = g_date_time_add_minutes (appt->begin, 1); - appt->color = g_strdup ("#0000FF"); - appt->is_event = TRUE; - appt->summary = g_strdup ("Second Daily alarm"); - appt->uid = g_strdup ("this uid isn't very random either."); - appt->has_alarms = FALSE; - appointments = g_slist_prepend (appointments, appt); - - /* done */ - g_task_return_pointer (task, appointments, NULL); - g_object_unref (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) -{ - IndicatorDatetimePlannerMock * self; - self = INDICATOR_DATETIME_PLANNER_MOCK (planner); - return self->priv->is_configured; -} - -static void -my_activate (IndicatorDatetimePlanner * self G_GNUC_UNUSED) -{ - g_message ("%s %s", G_STRLOC, G_STRFUNC); -} - -static void -my_activate_time (IndicatorDatetimePlanner * self G_GNUC_UNUSED, - GDateTime * activate_time) -{ - gchar * str = g_date_time_format (activate_time, "%F %T"); - g_message ("%s %s: %s", G_STRLOC, G_STRFUNC, str); - g_free (str); -} - -/*** -**** GObject virtual funcs -***/ - -static void -my_dispose (GObject * o) -{ - G_OBJECT_CLASS (indicator_datetime_planner_mock_parent_class)->dispose (o); -} - -/*** -**** Instantiation -***/ - -static void -indicator_datetime_planner_mock_class_init (IndicatorDatetimePlannerMockClass * 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 (IndicatorDatetimePlannerMockPriv)); -} - -static void -indicator_datetime_planner_mock_init (IndicatorDatetimePlannerMock * self) -{ - priv_t * p; - - p = G_TYPE_INSTANCE_GET_PRIVATE (self, - INDICATOR_TYPE_DATETIME_PLANNER_MOCK, - IndicatorDatetimePlannerMockPriv); - - p->is_configured = TRUE; - - self->priv = p; -} - -/*** -**** Public -***/ - -IndicatorDatetimePlanner * -indicator_datetime_planner_mock_new (void) -{ - gpointer o = g_object_new (INDICATOR_TYPE_DATETIME_PLANNER_MOCK, NULL); - - return INDICATOR_DATETIME_PLANNER (o); -} diff --git a/tests/planner-mock.h b/tests/planner-mock.h index 8d7d7c2..44d30c7 100644 --- a/tests/planner-mock.h +++ b/tests/planner-mock.h @@ -1,9 +1,6 @@ /* * 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. @@ -15,44 +12,33 @@ * * 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> */ -#ifndef __INDICATOR_DATETIME_PLANNER_MOCK__H__ -#define __INDICATOR_DATETIME_PLANNER_MOCK__H__ - -#include "planner.h" /* parent class */ +#ifndef INDICATOR_DATETIME_PLANNER_MOCK_H +#define INDICATOR_DATETIME_PLANNER_MOCK_H -G_BEGIN_DECLS +#include <datetime/planner.h> -#define INDICATOR_TYPE_DATETIME_PLANNER_MOCK (indicator_datetime_planner_mock_get_type()) -#define INDICATOR_DATETIME_PLANNER_MOCK(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), INDICATOR_TYPE_DATETIME_PLANNER_MOCK, IndicatorDatetimePlannerMock)) -#define INDICATOR_DATETIME_PLANNER_MOCK_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), INDICATOR_TYPE_DATETIME_PLANNER_MOCK, IndicatorDatetimePlannerMockClass)) -#define INDICATOR_IS_DATETIME_PLANNER_MOCK(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), INDICATOR_TYPE_DATETIME_PLANNER_MOCK)) - -typedef struct _IndicatorDatetimePlannerMock IndicatorDatetimePlannerMock; -typedef struct _IndicatorDatetimePlannerMockPriv IndicatorDatetimePlannerMockPriv; -typedef struct _IndicatorDatetimePlannerMockClass IndicatorDatetimePlannerMockClass; - -GType indicator_datetime_planner_mock_get_type (void); +namespace unity { +namespace indicator { +namespace datetime { /** - * An IndicatorDatetimePlanner which uses Evolution Data Server - * to get its list of appointments. + * \brief Planner which does nothing on its own. + * It requires its client must set its appointments property. */ -struct _IndicatorDatetimePlannerMock -{ - /*< private >*/ - IndicatorDatetimePlanner parent; - IndicatorDatetimePlannerMockPriv * priv; -}; - -struct _IndicatorDatetimePlannerMockClass +class MockPlanner: public Planner { - IndicatorDatetimePlannerClass parent_class; +public: + MockPlanner() =default; + virtual ~MockPlanner() =default; }; -IndicatorDatetimePlanner * indicator_datetime_planner_mock_new (void); - -G_END_DECLS +} // namespace datetime +} // namespace indicator +} // namespace unity -#endif /* __INDICATOR_DATETIME_PLANNER_MOCK__H__ */ +#endif // INDICATOR_DATETIME_PLANNER_MOCK_H diff --git a/tests/state-fixture.h b/tests/state-fixture.h new file mode 100644 index 0000000..7d8358e --- /dev/null +++ b/tests/state-fixture.h @@ -0,0 +1,60 @@ +/* + * 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 "glib-fixture.h" + +#include "actions-mock.h" +#include "state-mock.h" + +using namespace unity::indicator::datetime; + +class StateFixture: public GlibFixture +{ +private: + typedef GlibFixture super; + +protected: + std::shared_ptr<MockState> m_mock_state; + std::shared_ptr<State> m_state; + std::shared_ptr<MockActions> m_mock_actions; + std::shared_ptr<Actions> m_actions; + + virtual void SetUp() + { + super::SetUp(); + + m_mock_state.reset(new MockState); + m_state = std::dynamic_pointer_cast<State>(m_mock_state); + + m_mock_actions.reset(new MockActions(m_state)); + m_actions = std::dynamic_pointer_cast<Actions>(m_mock_actions); + } + + virtual void TearDown() + { + m_actions.reset(); + m_mock_actions.reset(); + + m_state.reset(); + m_mock_state.reset(); + + super::TearDown(); + } +}; + diff --git a/tests/state-mock.h b/tests/state-mock.h new file mode 100644 index 0000000..721b82f --- /dev/null +++ b/tests/state-mock.h @@ -0,0 +1,43 @@ +/* + * 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 "planner-mock.h" + +#include <datetime/clock-mock.h> +#include <datetime/state.h> + +using namespace unity::indicator::datetime; + +class MockState: public State +{ +public: + std::shared_ptr<MockClock> mock_clock; + + MockState() + { + const DateTime now = DateTime::NowLocal(); + mock_clock.reset(new MockClock(now)); + settings.reset(new Settings); + clock = std::dynamic_pointer_cast<Clock>(mock_clock); + planner.reset(new MockPlanner); + planner->time = now; + locations.reset(new Locations); + } +}; + diff --git a/tests/test-actions.cpp b/tests/test-actions.cpp new file mode 100644 index 0000000..1865cfd --- /dev/null +++ b/tests/test-actions.cpp @@ -0,0 +1,232 @@ +/* + * 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 "state-fixture.h" + +using namespace unity::indicator::datetime; + +typedef StateFixture ActionsFixture; + +TEST_F(ActionsFixture, ActionsExist) +{ + EXPECT_TRUE(m_actions != nullptr); + + const char* names[] = { "desktop-header", + "calendar", + "set-location", + "activate-planner", + "activate-appointment", + "activate-phone-settings", + "activate-phone-clock-app", + "activate-desktop-settings" }; + for(const auto& name: names) + { + EXPECT_TRUE(g_action_group_has_action(m_actions->action_group(), name)); + } +} + +TEST_F(ActionsFixture, ActivateDesktopSettings) +{ + const auto action_name = "activate-desktop-settings"; + const auto expected_action = MockActions::OpenDesktopSettings; + + auto action_group = m_actions->action_group(); + auto history = m_mock_actions->history(); + EXPECT_EQ(0, history.size()); + EXPECT_TRUE(g_action_group_has_action(action_group, action_name)); + + g_action_group_activate_action(action_group, action_name, nullptr); + history = m_mock_actions->history(); + EXPECT_EQ(1, history.size()); + EXPECT_EQ(expected_action, history[0]); +} + +TEST_F(ActionsFixture, ActivatePhoneSettings) +{ + const auto action_name = "activate-phone-settings"; + const auto expected_action = MockActions::OpenPhoneSettings; + + auto action_group = m_actions->action_group(); + EXPECT_TRUE(m_mock_actions->history().empty()); + EXPECT_TRUE(g_action_group_has_action(action_group, action_name)); + + g_action_group_activate_action(action_group, action_name, nullptr); + auto history = m_mock_actions->history(); + EXPECT_EQ(1, history.size()); + EXPECT_EQ(expected_action, history[0]); +} + +TEST_F(ActionsFixture, ActivatePhoneClockApp) +{ + const auto action_name = "activate-phone-clock-app"; + const auto expected_action = MockActions::OpenPhoneClockApp; + + auto action_group = m_actions->action_group(); + EXPECT_TRUE(m_mock_actions->history().empty()); + EXPECT_TRUE(g_action_group_has_action(action_group, action_name)); + + g_action_group_activate_action(action_group, action_name, nullptr); + auto history = m_mock_actions->history(); + EXPECT_EQ(1, history.size()); + EXPECT_EQ(expected_action, history[0]); +} + +TEST_F(ActionsFixture, ActivatePlanner) +{ + const auto action_name = "activate-planner"; + auto action_group = m_actions->action_group(); + EXPECT_TRUE(m_mock_actions->history().empty()); + EXPECT_TRUE(g_action_group_has_action(action_group, action_name)); + + const auto expected_action = MockActions::OpenPlanner; + auto v = g_variant_new_int64(0); + g_action_group_activate_action(action_group, action_name, v); + auto history = m_mock_actions->history(); + EXPECT_EQ(1, history.size()); + EXPECT_EQ(expected_action, history[0]); +} + +TEST_F(ActionsFixture, ActivatePlannerAt) +{ + const auto action_name = "activate-planner"; + auto action_group = m_actions->action_group(); + EXPECT_TRUE(m_mock_actions->history().empty()); + EXPECT_TRUE(g_action_group_has_action(action_group, action_name)); + + const auto now = DateTime::NowLocal(); + auto v = g_variant_new_int64(now.to_unix()); + g_action_group_activate_action(action_group, action_name, v); + const auto a = MockActions::OpenPlannerAt; + EXPECT_EQ(std::vector<MockActions::Action>({a}), m_mock_actions->history()); + EXPECT_EQ(now.to_unix(), m_mock_actions->date_time().to_unix()); +} + +TEST_F(ActionsFixture, SetLocation) +{ + const auto action_name = "set-location"; + auto action_group = m_actions->action_group(); + EXPECT_TRUE(m_mock_actions->history().empty()); + EXPECT_TRUE(g_action_group_has_action(action_group, action_name)); + + auto v = g_variant_new_string("America/Chicago Oklahoma City"); + g_action_group_activate_action(action_group, action_name, v); + const auto expected_action = MockActions::SetLocation; + ASSERT_EQ(1, m_mock_actions->history().size()); + EXPECT_EQ(expected_action, m_mock_actions->history()[0]); + EXPECT_EQ("America/Chicago", m_mock_actions->zone()); + EXPECT_EQ("Oklahoma City", m_mock_actions->name()); +} + +TEST_F(ActionsFixture, SetCalendarDate) +{ + // confirm that such an action exists + const auto action_name = "calendar"; + auto action_group = m_actions->action_group(); + EXPECT_TRUE(m_mock_actions->history().empty()); + EXPECT_TRUE(g_action_group_has_action(action_group, action_name)); + + // pick an arbitrary DateTime... + auto tmp = g_date_time_new_local(2010, 1, 2, 3, 4, 5); + const auto now = DateTime(tmp); + g_date_time_unref(tmp); + + // confirm that Planner.time gets changed to that date when we + // activate the 'calendar' action with that date's time_t as the arg + EXPECT_NE (now, m_state->planner->time.get()); + auto v = g_variant_new_int64(now.to_unix()); + g_action_group_activate_action (action_group, action_name, v); + EXPECT_EQ (now, m_state->planner->time.get()); +} + +TEST_F(ActionsFixture, ActivatingTheCalendarResetsItsDate) +{ + // Confirm that the GActions exist + auto action_group = m_actions->action_group(); + EXPECT_TRUE(g_action_group_has_action(action_group, "calendar")); + EXPECT_TRUE(g_action_group_has_action(action_group, "calendar-active")); + + /// + /// Prerequisite for the test: move calendar-date away from today + /// + + // move calendar-date a week into the future... + const auto now = m_state->clock->localtime(); + auto next_week = g_date_time_add_weeks(now.get(), 1); + const auto next_week_unix = g_date_time_to_unix(next_week); + g_action_group_activate_action (action_group, "calendar", g_variant_new_int64(next_week_unix)); + + // confirm the planner and calendar action state moved a week into the future + // but that m_state->clock is unchanged + EXPECT_EQ(next_week_unix, m_state->planner->time.get().to_unix()); + EXPECT_EQ(now, m_state->clock->localtime()); + auto calendar_state = g_action_group_get_action_state(action_group, "calendar"); + EXPECT_TRUE(calendar_state != nullptr); + EXPECT_TRUE(g_variant_is_of_type(calendar_state, G_VARIANT_TYPE_DICTIONARY)); + auto v = g_variant_lookup_value(calendar_state, "calendar-day", G_VARIANT_TYPE_INT64); + EXPECT_TRUE(v != nullptr); + EXPECT_EQ(next_week_unix, g_variant_get_int64(v)); + g_clear_pointer(&v, g_variant_unref); + g_clear_pointer(&calendar_state, g_variant_unref); + + /// + /// Now the actual test. + /// We set the state of 'calendar-active' to true, which should reset the calendar date. + /// This is so the calendar always starts on today's date when the indicator's menu is pulled down. + /// + + // change the state... + g_action_group_change_action_state(action_group, "calendar-active", g_variant_new_boolean(true)); + + // confirm the planner and calendar action state were reset back to m_state->clock's time + EXPECT_EQ(now.to_unix(), m_state->planner->time.get().to_unix()); + EXPECT_EQ(now, m_state->clock->localtime()); + calendar_state = g_action_group_get_action_state(action_group, "calendar"); + EXPECT_TRUE(calendar_state != nullptr); + EXPECT_TRUE(g_variant_is_of_type(calendar_state, G_VARIANT_TYPE_DICTIONARY)); + v = g_variant_lookup_value(calendar_state, "calendar-day", G_VARIANT_TYPE_INT64); + EXPECT_TRUE(v != nullptr); + EXPECT_EQ(now.to_unix(), g_variant_get_int64(v)); + g_clear_pointer(&v, g_variant_unref); + g_clear_pointer(&calendar_state, g_variant_unref); + +} + + +TEST_F(ActionsFixture, OpenAppointment) +{ + Appointment appt; + appt.uid = "some arbitrary uid"; + appt.url = "http://www.canonical.com/"; + m_state->planner->upcoming.set(std::vector<Appointment>({appt})); + + const auto action_name = "activate-appointment"; + auto action_group = m_actions->action_group(); + EXPECT_TRUE(m_mock_actions->history().empty()); + EXPECT_TRUE(g_action_group_has_action(action_group, action_name)); + + auto v = g_variant_new_string(appt.uid.c_str()); + g_action_group_activate_action(action_group, action_name, v); + const auto a = MockActions::OpenAppointment; + ASSERT_EQ(1, m_mock_actions->history().size()); + ASSERT_EQ(a, m_mock_actions->history()[0]); + EXPECT_EQ(appt.url, m_mock_actions->url()); +} + diff --git a/tests/test-clock.cpp b/tests/test-clock.cpp new file mode 100644 index 0000000..4287e1c --- /dev/null +++ b/tests/test-clock.cpp @@ -0,0 +1,140 @@ +/* + * 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> + +#include "test-dbus-fixture.h" + +/*** +**** +***/ + +using namespace unity::indicator::datetime; + +class ClockFixture: public TestDBusFixture +{ + private: + typedef TestDBusFixture super; + + public: + void emitPrepareForSleep() + { + g_dbus_connection_emit_signal(g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr), + NULL, + "/org/freedesktop/login1", // object path + "org.freedesktop.login1.Manager", // interface + "PrepareForSleep", // signal name + g_variant_new("(b)", FALSE), + NULL); + } +}; + +TEST_F(ClockFixture, MinuteChangedSignalShouldTriggerOncePerMinute) +{ + // start up a live clock + std::shared_ptr<Timezones> zones(new Timezones); + zones->timezone.set("America/New_York"); + LiveClock clock(zones); + wait_msec(500); // wait for the bus to set up + + // count how many times clock.minute_changed() is emitted over the next minute + const DateTime now = clock.localtime(); + const auto gnow = now.get(); + auto gthen = g_date_time_add_minutes(gnow, 1); + int count = 0; + clock.minute_changed.connect([&count](){count++;}); + const auto msec = g_date_time_difference(gthen,gnow) / 1000; + wait_msec(msec); + EXPECT_EQ(1, count); + g_date_time_unref(gthen); +} + +/*** +**** +***/ + +#define TIMEZONE_FILE (SANDBOX"/timezone") + +TEST_F(ClockFixture, HelloFixture) +{ + std::shared_ptr<Timezones> zones(new Timezones); + zones->timezone.set("America/New_York"); + LiveClock clock(zones); +} + + +TEST_F(ClockFixture, TimezoneChangeTriggersSkew) +{ + std::shared_ptr<Timezones> zones(new Timezones); + zones->timezone.set("America/New_York"); + LiveClock clock(zones); + + auto tz_nyc = g_time_zone_new("America/New_York"); + auto now_nyc = g_date_time_new_now(tz_nyc); + auto now = clock.localtime(); + EXPECT_EQ(g_date_time_get_utc_offset(now_nyc), g_date_time_get_utc_offset(now.get())); + EXPECT_LE(abs(g_date_time_difference(now_nyc,now.get())), G_USEC_PER_SEC); + g_date_time_unref(now_nyc); + g_time_zone_unref(tz_nyc); + + /// change the timezones! + clock.minute_changed.connect([this](){ + g_main_loop_quit(loop); + }); + g_idle_add([](gpointer gs){ + static_cast<Timezones*>(gs)->timezone.set("America/Los_Angeles"); + return G_SOURCE_REMOVE; + }, zones.get()); + g_main_loop_run(loop); + + auto tz_la = g_time_zone_new("America/Los_Angeles"); + auto now_la = g_date_time_new_now(tz_la); + now = clock.localtime(); + EXPECT_EQ(g_date_time_get_utc_offset(now_la), g_date_time_get_utc_offset(now.get())); + EXPECT_LE(abs(g_date_time_difference(now_la,now.get())), G_USEC_PER_SEC); + g_date_time_unref(now_la); + g_time_zone_unref(tz_la); +} + +/** + * Confirm that a "PrepareForSleep" event wil trigger a skew event + */ +TEST_F(ClockFixture, SleepTriggersSkew) +{ + std::shared_ptr<Timezones> zones(new Timezones); + zones->timezone.set("America/New_York"); + LiveClock clock(zones); + wait_msec(500); // wait for the bus to set up + + bool skewed = false; + clock.minute_changed.connect([&skewed, this](){ + skewed = true; + g_main_loop_quit(loop); + return G_SOURCE_REMOVE; + }); + + g_idle_add([](gpointer gself){ + static_cast<ClockFixture*>(gself)->emitPrepareForSleep(); + return G_SOURCE_REMOVE; + }, this); + + g_main_loop_run(loop); + EXPECT_TRUE(skewed); +} diff --git a/tests/test-dbus-fixture.h b/tests/test-dbus-fixture.h new file mode 100644 index 0000000..db06be2 --- /dev/null +++ b/tests/test-dbus-fixture.h @@ -0,0 +1,102 @@ +/* + * 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 "glib-fixture.h" + +/*** +**** +***/ + +class TestDBusFixture: public GlibFixture +{ + public: + + TestDBusFixture() {} + + TestDBusFixture(const std::vector<std::string>& service_dirs_in): service_dirs(service_dirs_in) {} + + private: + + typedef GlibFixture super; + + static void + on_bus_opened (GObject* /*object*/, GAsyncResult * res, gpointer gself) + { + auto self = static_cast<TestDBusFixture*>(gself); + + GError * err = 0; + self->system_bus = g_bus_get_finish (res, &err); + g_assert_no_error (err); + + g_main_loop_quit (self->loop); + } + + static void + on_bus_closed (GObject* /*object*/, GAsyncResult * res, gpointer gself) + { + auto self = static_cast<TestDBusFixture*>(gself); + + GError * err = 0; + g_dbus_connection_close_finish (self->system_bus, res, &err); + g_assert_no_error (err); + + g_main_loop_quit (self->loop); + } + + protected: + + GTestDBus * test_dbus; + GDBusConnection * system_bus; + const std::vector<std::string> service_dirs; + + virtual void SetUp () + { + super::SetUp (); + + // pull up a test dbus + test_dbus = g_test_dbus_new (G_TEST_DBUS_NONE); + for (const auto& dir : service_dirs) + g_test_dbus_add_service_dir (test_dbus, dir.c_str()); + g_test_dbus_up (test_dbus); + const char * address = g_test_dbus_get_bus_address (test_dbus); + g_setenv ("DBUS_SYSTEM_BUS_ADDRESS", address, true); + g_setenv ("DBUS_SESSION_BUS_ADDRESS", address, true); + g_debug ("test_dbus's address is %s", address); + + // wait for the GDBusConnection before returning + g_bus_get (G_BUS_TYPE_SYSTEM, nullptr, on_bus_opened, this); + g_main_loop_run (loop); + } + + virtual void TearDown () + { + wait_msec(); + + // close the system bus + g_dbus_connection_close(system_bus, nullptr, on_bus_closed, this); + g_main_loop_run(loop); + g_clear_object(&system_bus); + + // tear down the test dbus + g_test_dbus_down(test_dbus); + g_clear_object(&test_dbus); + + super::TearDown(); + } +}; diff --git a/tests/test-exporter.cpp b/tests/test-exporter.cpp new file mode 100644 index 0000000..104fb4b --- /dev/null +++ b/tests/test-exporter.cpp @@ -0,0 +1,134 @@ +/* + * 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 "actions-mock.h" +#include "state-mock.h" +#include "glib-fixture.h" + +#include <datetime/actions.h> +#include <datetime/dbus-shared.h> +#include <datetime/exporter.h> + +#include <set> +#include <string> + +using namespace unity::indicator::datetime; + +class ExporterFixture: public GlibFixture +{ +private: + + typedef GlibFixture super; + +protected: + + GTestDBus* bus = nullptr; + + void SetUp() + { + super::SetUp(); + + // bring up the test bus + bus = g_test_dbus_new(G_TEST_DBUS_NONE); + g_test_dbus_up(bus); + const auto address = g_test_dbus_get_bus_address(bus); + g_setenv("DBUS_SYSTEM_BUS_ADDRESS", address, true); + g_setenv("DBUS_SESSION_BUS_ADDRESS", address, true); + } + + void TearDown() + { + GError * error = nullptr; + GDBusConnection* connection = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error); + if(!g_dbus_connection_is_closed(connection)) + g_dbus_connection_close_sync(connection, nullptr, &error); + g_assert_no_error(error); + g_clear_object(&connection); + g_test_dbus_down(bus); + g_clear_object(&bus); + + super::TearDown(); + } +}; + +TEST_F(ExporterFixture, HelloWorld) +{ + // confirms that the Test DBus SetUp() and TearDown() works +} + +TEST_F(ExporterFixture, Publish) +{ + std::shared_ptr<State> state(new MockState); + std::shared_ptr<Actions> actions(new MockActions(state)); + std::vector<std::shared_ptr<Menu>> menus; + + MenuFactory menu_factory (actions, state); + for(int i=0; i<Menu::NUM_PROFILES; i++) + menus.push_back(menu_factory.buildMenu(Menu::Profile(i))); + + Exporter exporter; + exporter.publish(actions, menus); + wait_msec(); + + auto connection = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, nullptr); + auto exported = g_dbus_action_group_get (connection, BUS_NAME, BUS_PATH); + auto names_strv = g_action_group_list_actions(G_ACTION_GROUP(exported)); + + // wait for the exported ActionGroup to be populated + if (g_strv_length(names_strv) == 0) + { + g_strfreev(names_strv); + wait_for_signal(exported, "action-added"); + names_strv = g_action_group_list_actions(G_ACTION_GROUP(exported)); + } + + // convert it to a std::set for easy prodding + std::set<std::string> names; + for(int i=0; names_strv && names_strv[i]; i++) + names.insert(names_strv[i]); + + // confirm the actions that we expect + EXPECT_EQ(1, names.count("activate-appointment")); + EXPECT_EQ(1, names.count("activate-desktop-settings")); + EXPECT_EQ(1, names.count("activate-phone-clock-app")); + EXPECT_EQ(1, names.count("activate-phone-settings")); + EXPECT_EQ(1, names.count("activate-planner")); + EXPECT_EQ(1, names.count("calendar")); + EXPECT_EQ(1, names.count("desktop_greeter-header")); + EXPECT_EQ(1, names.count("desktop-header")); + EXPECT_EQ(1, names.count("phone_greeter-header")); + EXPECT_EQ(1, names.count("phone-header")); + EXPECT_EQ(1, names.count("set-location")); + + // try closing the connection prematurely + // to test Exporter's name-lost signal + bool name_lost = false; + exporter.name_lost.connect([this,&name_lost](){ + name_lost = true; + g_main_loop_quit(loop); + }); + g_dbus_connection_close_sync(connection, nullptr, nullptr); + g_main_loop_run(loop); + EXPECT_TRUE(name_lost); + + // cleanup + g_strfreev(names_strv); + g_clear_object(&exported); + g_clear_object(&connection); +} diff --git a/tests/test-formatter.cc b/tests/test-formatter.cc deleted file mode 100644 index 6a408ab..0000000 --- a/tests/test-formatter.cc +++ /dev/null @@ -1,98 +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 <langinfo.h> -#include <locale.h> - -#include <glib/gi18n.h> - -#include "utils.h" - -#include "glib-fixture.h" - -/*** -**** -***/ - -class FormatterFixture: public GlibFixture -{ - private: - - typedef GlibFixture super; - gchar * original_locale = nullptr; - - protected: - - virtual void SetUp () - { - super::SetUp (); - - original_locale = g_strdup (setlocale (LC_TIME, NULL)); - } - - virtual void TearDown () - { - setlocale (LC_TIME, original_locale); - g_clear_pointer (&original_locale, g_free); - - super::TearDown (); - } - - bool SetLocale (const char * expected_locale, const char * name) - { - setlocale (LC_TIME, expected_locale); - const char * actual_locale = setlocale (LC_TIME, NULL); - if (!g_strcmp0 (expected_locale, actual_locale)) - { - return true; - } - else - { - g_warning ("Unable to set locale to %s; skipping %s locale tests.", expected_locale, name); - return false; - } - } - - inline bool Set24hLocale () { return SetLocale ("C", "24h"); } - inline bool Set12hLocale () { return SetLocale ("en_US.utf8", "12h"); } -}; - - -/** - * Test the phone header format - */ -TEST_F (FormatterFixture, TestPhoneHeader) -{ - // test the default value in a 24h locale - if (Set24hLocale ()) - { - const gchar * format = get_terse_header_time_format_string (); - ASSERT_NE (nullptr, format); - ASSERT_STREQ ("%H:%M", format); - } - - // test the default value in a 12h locale - if (Set12hLocale ()) - { - const gchar * format = get_terse_header_time_format_string (); - ASSERT_NE (nullptr, format); - ASSERT_STREQ ("%l:%M %p", format); - } -} diff --git a/tests/test-formatter.cpp b/tests/test-formatter.cpp new file mode 100644 index 0000000..01df4f2 --- /dev/null +++ b/tests/test-formatter.cpp @@ -0,0 +1,256 @@ + +/* + * 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-fixture.h" + +#include <datetime/clock-mock.h> +#include <datetime/formatter.h> +#include <datetime/settings.h> + +#include <glib/gi18n.h> + +#include <langinfo.h> +#include <locale.h> + +using namespace unity::indicator::datetime; + +/*** +**** +***/ + +class FormatterFixture: public GlibFixture +{ + private: + + typedef GlibFixture super; + gchar* m_original_locale = nullptr; + + protected: + + std::shared_ptr<Settings> m_settings; + + virtual void SetUp() + { + super::SetUp(); + + m_settings.reset(new Settings); + m_original_locale = g_strdup(setlocale(LC_TIME, nullptr)); + } + + virtual void TearDown() + { + m_settings.reset(); + + setlocale(LC_TIME, m_original_locale); + g_clear_pointer(&m_original_locale, g_free); + + super::TearDown(); + } + + bool SetLocale(const char* expected_locale, const char* name) + { + setlocale(LC_TIME, expected_locale); + const auto actual_locale = setlocale(LC_TIME, nullptr); + if (!g_strcmp0(expected_locale, actual_locale)) + { + return true; + } + else + { + g_warning("Unable to set locale to %s; skipping %s locale tests.", expected_locale, name); + return false; + } + } + + inline bool Set24hLocale() { return SetLocale("C", "24h"); } + inline bool Set12hLocale() { return SetLocale("en_US.utf8", "12h"); } +}; + + +/** + * Test the phone header format + */ +TEST_F(FormatterFixture, TestPhoneHeader) +{ + auto now = g_date_time_new_local(2020, 10, 31, 18, 30, 59); + std::shared_ptr<Clock> clock(new MockClock(DateTime(now))); + g_date_time_unref(now); + + // test the default value in a 24h locale + if(Set24hLocale()) + { + PhoneFormatter formatter(clock); + EXPECT_EQ(std::string("%H:%M"), formatter.header_format.get()); + EXPECT_EQ(std::string("18:30"), formatter.header.get()); + } + + // test the default value in a 12h locale + if(Set12hLocale()) + { + PhoneFormatter formatter(clock); + EXPECT_EQ(std::string("%l:%M %p"), formatter.header_format.get()); + EXPECT_EQ(std::string(" 6:30 PM"), formatter.header.get()); + } +} + +#define EM_SPACE "\u2003" + +/** + * Test the default values of the desktop header format + */ +TEST_F(FormatterFixture, TestDesktopHeader) +{ + struct { + bool is_12h; + bool show_day; + bool show_date; + bool show_year; + const char* expected_format_string; + } test_cases[] = { + { false, false, false, false, "%H:%M" }, + { false, false, false, true, "%H:%M" }, // show_year is ignored iff show_date is false + { false, false, true, false, "%b %e" EM_SPACE "%H:%M" }, + { false, false, true, true, "%b %e %Y" EM_SPACE "%H:%M" }, + { false, true, false, false, "%a" EM_SPACE "%H:%M" }, + { false, true, false, true, "%a" EM_SPACE "%H:%M" }, // show_year is ignored iff show_date is false + { false, true, true, false, "%a %b %e" EM_SPACE "%H:%M" }, + { false, true, true, true, "%a %b %e %Y" EM_SPACE "%H:%M" }, + { true, false, false, false, "%l:%M %p" }, + { true, false, false, true, "%l:%M %p" }, // show_year is ignored iff show_date is false + { true, false, true, false, "%b %e" EM_SPACE "%l:%M %p" }, + { true, false, true, true, "%b %e %Y" EM_SPACE "%l:%M %p" }, + { true, true, false, false, "%a" EM_SPACE "%l:%M %p" }, + { true, true, false, true, "%a" EM_SPACE "%l:%M %p" }, // show_year is ignored iff show_date is false + { true, true, true, false, "%a %b %e" EM_SPACE "%l:%M %p" }, + { true, true, true, true, "%a %b %e %Y" EM_SPACE "%l:%M %p" } + }; + + auto now = g_date_time_new_local(2020, 10, 31, 18, 30, 59); + std::shared_ptr<Clock> clock(new MockClock(DateTime(now))); + g_date_time_unref(now); + + for(const auto& test_case : test_cases) + { + if (test_case.is_12h ? Set12hLocale() : Set24hLocale()) + { + DesktopFormatter f(clock, m_settings); + + m_settings->show_day.set(test_case.show_day); + m_settings->show_date.set(test_case.show_date); + m_settings->show_year.set(test_case.show_year); + + ASSERT_STREQ(test_case.expected_format_string, f.header_format.get().c_str()); + } + } +} + +/** + * Test the default values of the desktop header format + */ +TEST_F(FormatterFixture, TestUpcomingTimes) +{ + auto a = g_date_time_new_local(2020, 10, 31, 18, 30, 59); + + struct { + gboolean is_12h; + GDateTime* now; + GDateTime* then; + const char* expected_format_string; + } test_cases[] = { + { true, g_date_time_ref(a), g_date_time_ref(a), "%l:%M %p" }, // identical time + { true, g_date_time_ref(a), g_date_time_add_hours(a,1), "%l:%M %p" }, // later today + { true, g_date_time_ref(a), g_date_time_add_days(a,1), "Tomorrow" EM_SPACE "%l:%M %p" }, // tomorrow + { true, g_date_time_ref(a), g_date_time_add_days(a,2), "%a" EM_SPACE "%l:%M %p" }, + { true, g_date_time_ref(a), g_date_time_add_days(a,6), "%a" EM_SPACE "%l:%M %p" }, + { true, g_date_time_ref(a), g_date_time_add_days(a,7), "%a %d %b" EM_SPACE "%l:%M %p" }, // over one week away + + { false, g_date_time_ref(a), g_date_time_ref(a), "%H:%M" }, // identical time + { false, g_date_time_ref(a), g_date_time_add_hours(a,1), "%H:%M" }, // later today + { false, g_date_time_ref(a), g_date_time_add_days(a,1), "Tomorrow" EM_SPACE "%H:%M" }, // tomorrow + { false, g_date_time_ref(a), g_date_time_add_days(a,2), "%a" EM_SPACE "%H:%M" }, + { false, g_date_time_ref(a), g_date_time_add_days(a,6), "%a" EM_SPACE "%H:%M" }, + { false, g_date_time_ref(a), g_date_time_add_days(a,7), "%a %d %b" EM_SPACE "%H:%M" } // over one week away + }; + + for(const auto& test_case : test_cases) + { + if (test_case.is_12h ? Set12hLocale() : Set24hLocale()) + { + std::shared_ptr<Clock> clock (new MockClock(DateTime(test_case.now))); + DesktopFormatter f(clock, m_settings); + + const auto fmt = f.relative_format(test_case.then); + ASSERT_EQ(test_case.expected_format_string, fmt); + + g_clear_pointer(&test_case.now, g_date_time_unref); + g_clear_pointer(&test_case.then, g_date_time_unref); + } + } + + g_date_time_unref(a); +} + + +/** + * Test the default values of the desktop header format + */ +TEST_F(FormatterFixture, TestEventTimes) +{ + auto day = g_date_time_new_local(2013, 1, 1, 13, 0, 0); + auto day_begin = g_date_time_new_local(2013, 1, 1, 13, 0, 0); + auto day_end = g_date_time_add_days(day_begin, 1); + auto tomorrow_begin = g_date_time_add_days(day_begin, 1); + auto tomorrow_end = g_date_time_add_days(tomorrow_begin, 1); + + struct { + bool is_12h; + GDateTime* now; + GDateTime* then; + GDateTime* then_end; + const char* expected_format_string; + } test_cases[] = { + { false, g_date_time_ref(day), g_date_time_ref(day_begin), g_date_time_ref(day_end), _("Today") }, + { true, g_date_time_ref(day), g_date_time_ref(day_begin), g_date_time_ref(day_end), _("Today") }, + { false, g_date_time_ref(day), g_date_time_ref(tomorrow_begin), g_date_time_ref(tomorrow_end), _("Tomorrow") }, + { true, g_date_time_ref(day), g_date_time_ref(tomorrow_begin), g_date_time_ref(tomorrow_end), _("Tomorrow") } + }; + + for(const auto& test_case : test_cases) + { + if (test_case.is_12h ? Set12hLocale() : Set24hLocale()) + { + std::shared_ptr<Clock> clock(new MockClock(DateTime(test_case.now))); + DesktopFormatter f(clock, m_settings); + + const auto fmt = f.relative_format(test_case.then, test_case.then_end); + ASSERT_STREQ(test_case.expected_format_string, fmt.c_str()); + + g_clear_pointer(&test_case.now, g_date_time_unref); + g_clear_pointer(&test_case.then, g_date_time_unref); + g_clear_pointer(&test_case.then_end, g_date_time_unref); + } + } + + g_date_time_unref(tomorrow_end); + g_date_time_unref(tomorrow_begin); + g_date_time_unref(day_end); + g_date_time_unref(day_begin); + g_date_time_unref(day); +} diff --git a/tests/test-indicator.cc b/tests/test-indicator.cc deleted file mode 100644 index 2480c94..0000000 --- a/tests/test-indicator.cc +++ /dev/null @@ -1,92 +0,0 @@ -/* -Copyright 2012 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 <gtest/gtest.h> - -#include <glib-object.h> - -/*** -**** -***/ - -namespace -{ - void ensure_glib_initialized () - { - static bool initialized = false; - - if (G_UNLIKELY(!initialized)) - { - initialized = true; - g_setenv ("GSETTINGS_SCHEMA_DIR", SCHEMA_DIR, TRUE); - } - } -} - -/*** -**** -***/ - -class IndicatorTest : public ::testing::Test -{ - private: - - guint log_handler_id; - - int log_count_ipower_actual; - - static void log_count_func (const gchar *log_domain, - GLogLevelFlags log_level, - const gchar *message, - gpointer user_data) - { - reinterpret_cast<IndicatorTest*>(user_data)->log_count_ipower_actual++; - } - - protected: - - int log_count_ipower_expected; - - protected: - - virtual void SetUp() - { - const GLogLevelFlags flags = GLogLevelFlags(G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING); - log_handler_id = g_log_set_handler ("Indicator-Power", flags, log_count_func, this); - log_count_ipower_expected = 0; - log_count_ipower_actual = 0; - - ensure_glib_initialized (); - } - - virtual void TearDown() - { - ASSERT_EQ (log_count_ipower_expected, log_count_ipower_actual); - g_log_remove_handler ("Indicator-Power", log_handler_id); - } -}; - -/*** -**** -***/ - -TEST_F(IndicatorTest, HelloWorld) -{ - ASSERT_TRUE (TRUE); -} diff --git a/tests/test-live-actions.cpp b/tests/test-live-actions.cpp new file mode 100644 index 0000000..eab8596 --- /dev/null +++ b/tests/test-live-actions.cpp @@ -0,0 +1,403 @@ +/* + * 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 "state-mock.h" +#include "glib-fixture.h" + +/*** +**** +***/ + +class MockLiveActions: public LiveActions +{ +public: + std::string last_cmd; + std::string last_url; + MockLiveActions(const std::shared_ptr<State>& state_in): LiveActions(state_in) {} + virtual ~MockLiveActions() {} + +protected: + void dispatch_url(const std::string& url) { last_url = url; } + void execute_command(const std::string& cmd) { last_cmd = cmd; } +}; + +/*** +**** +***/ + +using namespace unity::indicator::datetime; + +class LiveActionsFixture: public GlibFixture +{ +private: + + typedef GlibFixture super; + + static void on_bus_acquired(GDBusConnection* conn, + const gchar* name, + gpointer gself) + { + auto self = static_cast<LiveActionsFixture*>(gself); + g_debug("bus acquired: %s, connection is %p", name, conn); + + // Set up a mock GSD. + // All it really does is wait for calls to GetDevice and + // returns the get_devices_retval variant + static const GDBusInterfaceVTable vtable = { + timedate1_handle_method_call, + nullptr, /* GetProperty */ + nullptr, /* SetProperty */ + }; + + self->connection = G_DBUS_CONNECTION(g_object_ref(G_OBJECT(conn))); + + GError* error = nullptr; + self->object_register_id = g_dbus_connection_register_object( + conn, + "/org/freedesktop/timedate1", + self->node_info->interfaces[0], + &vtable, + self, + nullptr, + &error); + g_assert_no_error(error); + } + + static void on_name_acquired(GDBusConnection* /*conn*/, + const gchar* /*name*/, + gpointer gself) + { + auto self = static_cast<LiveActionsFixture*>(gself); + self->name_acquired = true; + g_main_loop_quit(self->loop); + } + + static void on_name_lost(GDBusConnection* /*conn*/, + const gchar* /*name*/, + gpointer gself) + { + auto self = static_cast<LiveActionsFixture*>(gself); + self->name_acquired = false; + } + + static void on_bus_closed(GObject* /*object*/, + GAsyncResult* res, + gpointer gself) + { + auto self = static_cast<LiveActionsFixture*>(gself); + GError* err = nullptr; + g_dbus_connection_close_finish(self->connection, res, &err); + g_assert_no_error(err); + g_main_loop_quit(self->loop); + } + + static void + timedate1_handle_method_call(GDBusConnection * /*connection*/, + const gchar * /*sender*/, + const gchar * /*object_path*/, + const gchar * /*interface_name*/, + const gchar * method_name, + GVariant * parameters, + GDBusMethodInvocation * invocation, + gpointer gself) + { + g_assert(!g_strcmp0(method_name, "SetTimezone")); + g_assert(g_variant_is_of_type(parameters, G_VARIANT_TYPE_TUPLE)); + g_assert(2 == g_variant_n_children(parameters)); + + auto child = g_variant_get_child_value(parameters, 0); + g_assert(g_variant_is_of_type(child, G_VARIANT_TYPE_STRING)); + auto self = static_cast<LiveActionsFixture*>(gself); + self->attempted_tzid = g_variant_get_string(child, nullptr); + g_variant_unref(child); + + g_dbus_method_invocation_return_value(invocation, nullptr); + g_main_loop_quit(self->loop); + } + +protected: + + std::shared_ptr<MockState> m_mock_state; + std::shared_ptr<State> m_state; + std::shared_ptr<MockLiveActions> m_live_actions; + std::shared_ptr<Actions> m_actions; + + bool name_acquired; + std::string attempted_tzid; + + GTestDBus* bus; + guint own_name; + GDBusConnection* connection; + GDBusNodeInfo* node_info; + int object_register_id; + + void SetUp() + { + super::SetUp(); + + name_acquired = false; + attempted_tzid.clear(); + connection = nullptr; + node_info = nullptr; + object_register_id = 0; + own_name = 0; + + // bring up the test bus + bus = g_test_dbus_new(G_TEST_DBUS_NONE); + g_test_dbus_up(bus); + const auto address = g_test_dbus_get_bus_address(bus); + g_setenv("DBUS_SYSTEM_BUS_ADDRESS", address, true); + g_setenv("DBUS_SESSION_BUS_ADDRESS", address, true); + g_debug("test_dbus's address is %s", address); + + // parse the org.freedesktop.timedate1 interface + const gchar introspection_xml[] = + "<node>" + " <interface name='org.freedesktop.timedate1'>" + " <method name='SetTimezone'>" + " <arg name='timezone' type='s' direction='in'/>" + " <arg name='user_interaction' type='b' direction='in'/>" + " </method>" + " </interface>" + "</node>"; + node_info = g_dbus_node_info_new_for_xml(introspection_xml, nullptr); + ASSERT_TRUE(node_info != nullptr); + ASSERT_TRUE(node_info->interfaces != nullptr); + ASSERT_TRUE(node_info->interfaces[0] != nullptr); + ASSERT_TRUE(node_info->interfaces[1] == nullptr); + ASSERT_STREQ("org.freedesktop.timedate1", node_info->interfaces[0]->name); + + // own the bus + own_name = g_bus_own_name(G_BUS_TYPE_SYSTEM, + "org.freedesktop.timedate1", + G_BUS_NAME_OWNER_FLAGS_NONE, + on_bus_acquired, on_name_acquired, on_name_lost, + this, nullptr); + ASSERT_TRUE(object_register_id == 0); + ASSERT_FALSE(name_acquired); + ASSERT_TRUE(connection == nullptr); + g_main_loop_run(loop); + ASSERT_TRUE(object_register_id != 0); + ASSERT_TRUE(name_acquired); + ASSERT_TRUE(G_IS_DBUS_CONNECTION(connection)); + + // create the State and Actions + m_mock_state.reset(new MockState); + m_mock_state->settings.reset(new Settings); + m_state = std::dynamic_pointer_cast<State>(m_mock_state); + m_live_actions.reset(new MockLiveActions(m_state)); + m_actions = std::dynamic_pointer_cast<Actions>(m_live_actions); + } + + void TearDown() + { + m_actions.reset(); + m_live_actions.reset(); + m_state.reset(); + m_mock_state.reset(); + + g_dbus_connection_unregister_object(connection, object_register_id); + g_dbus_node_info_unref(node_info); + g_bus_unown_name(own_name); + g_dbus_connection_close(connection, nullptr, on_bus_closed, this); + g_main_loop_run(loop); + g_clear_object(&connection); + g_test_dbus_down(bus); + g_clear_object(&bus); + + super::TearDown(); + } +}; + +/*** +**** +***/ + +TEST_F(LiveActionsFixture, HelloWorld) +{ + EXPECT_TRUE(true); +} + +TEST_F(LiveActionsFixture, SetLocation) +{ + const std::string tzid = "America/Chicago"; + const std::string name = "Oklahoma City"; + const std::string expected = tzid + " " + name; + + EXPECT_NE(expected, m_state->settings->timezone_name.get()); + + m_actions->set_location(tzid, name); + g_main_loop_run(loop); + EXPECT_EQ(attempted_tzid, tzid); + wait_msec(); + + EXPECT_EQ(expected, m_state->settings->timezone_name.get()); +} + +TEST_F(LiveActionsFixture, OpenDesktopSettings) +{ + m_actions->open_desktop_settings(); + const std::string expected_substr = "control-center"; + EXPECT_NE(m_live_actions->last_cmd.find(expected_substr), std::string::npos); +} + +TEST_F(LiveActionsFixture, OpenPlanner) +{ + m_actions->open_planner(); + const std::string expected = "evolution -c calendar"; + EXPECT_EQ(expected, m_live_actions->last_cmd); +} + +TEST_F(LiveActionsFixture, OpenPhoneSettings) +{ + m_actions->open_phone_settings(); + const std::string expected = "settings:///system/time-date"; + EXPECT_EQ(expected, m_live_actions->last_url); +} + +TEST_F(LiveActionsFixture, OpenPhoneClockApp) +{ + m_actions->open_phone_clock_app(); + const std::string expected = "appid://com.ubuntu.clock/clock/current-user-version"; + EXPECT_EQ(expected, m_live_actions->last_url); +} + +TEST_F(LiveActionsFixture, OpenPlannerAt) +{ + const auto now = DateTime::NowLocal(); + m_actions->open_planner_at(now); + const std::string expected = now.format("evolution \"calendar:///?startdate=%Y%m%d\""); + EXPECT_EQ(expected, m_live_actions->last_cmd); +} + +TEST_F(LiveActionsFixture, CalendarState) +{ + // init the clock + auto tmp = g_date_time_new_local (2014, 1, 1, 0, 0, 0); + const DateTime now (tmp); + g_date_time_unref (tmp); + m_mock_state->mock_clock->set_localtime (now); + m_state->planner->time.set(now); + + /// + /// Test the default calendar state. + /// + + auto action_group = m_actions->action_group(); + auto calendar_state = g_action_group_get_action_state (action_group, "calendar"); + EXPECT_TRUE (calendar_state != nullptr); + EXPECT_TRUE (g_variant_is_of_type (calendar_state, G_VARIANT_TYPE_DICTIONARY)); + + // there's nothing in the planner yet, so appointment-days should be an empty array + auto v = g_variant_lookup_value (calendar_state, "appointment-days", G_VARIANT_TYPE_ARRAY); + EXPECT_TRUE (v != nullptr); + EXPECT_EQ (0, g_variant_n_children (v)); + g_clear_pointer (&v, g_variant_unref); + + // calendar-day should be in sync with m_state->calendar_day + v = g_variant_lookup_value (calendar_state, "calendar-day", G_VARIANT_TYPE_INT64); + EXPECT_TRUE (v != nullptr); + EXPECT_EQ (m_state->planner->time.get().to_unix(), g_variant_get_int64(v)); + g_clear_pointer (&v, g_variant_unref); + + // show-week-numbers should be false because MockSettings defaults everything to 0 + v = g_variant_lookup_value (calendar_state, "show-week-numbers", G_VARIANT_TYPE_BOOLEAN); + EXPECT_TRUE (v != nullptr); + EXPECT_FALSE (g_variant_get_boolean (v)); + g_clear_pointer (&v, g_variant_unref); + + // cleanup this step + g_clear_pointer (&calendar_state, g_variant_unref); + + + /// + /// Now add appointments to the planner and confirm that the state keeps in sync + /// + + auto tomorrow = g_date_time_add_days (now.get(), 1); + auto tomorrow_begin = g_date_time_add_full (tomorrow, 0, 0, 0, + -g_date_time_get_hour(tomorrow), + -g_date_time_get_minute(tomorrow), + -g_date_time_get_seconds(tomorrow)); + auto tomorrow_end = g_date_time_add_full (tomorrow_begin, 0, 0, 1, 0, 0, -1); + Appointment a1; + a1.color = "green"; + a1.summary = "write unit tests"; + a1.url = "http://www.ubuntu.com/"; + a1.uid = "D4B57D50247291478ED31DED17FF0A9838DED402"; + a1.begin = tomorrow_begin; + a1.end = tomorrow_end; + + auto next_begin = g_date_time_add_days (tomorrow_begin, 1); + auto next_end = g_date_time_add_full (next_begin, 0, 0, 1, 0, 0, -1); + Appointment a2; + a2.color = "orange"; + a2.summary = "code review"; + a2.url = "http://www.ubuntu.com/"; + a2.uid = "2756ff7de3745bbffd65d2e4779c37c7ca60d843"; + a2.begin = next_begin; + a2.end = next_end; + + m_state->planner->this_month.set(std::vector<Appointment>({a1, a2})); + + /// + /// Now test the calendar state again. + /// The this_month field should now contain the appointments we just added. + /// + + calendar_state = g_action_group_get_action_state (action_group, "calendar"); + v = g_variant_lookup_value (calendar_state, "appointment-days", G_VARIANT_TYPE_ARRAY); + EXPECT_TRUE (v != nullptr); + int i; + g_variant_get_child (v, 0, "i", &i); + EXPECT_EQ (g_date_time_get_day_of_month(a1.begin.get()), i); + g_variant_get_child (v, 1, "i", &i); + EXPECT_EQ (g_date_time_get_day_of_month(a2.begin.get()), i); + g_clear_pointer(&v, g_variant_unref); + g_clear_pointer(&calendar_state, g_variant_unref); + + // cleanup this step + g_date_time_unref (next_end); + g_date_time_unref (next_begin); + g_date_time_unref (tomorrow_end); + g_date_time_unref (tomorrow_begin); + g_date_time_unref (tomorrow); + + /// + /// Confirm that the action state's dictionary + /// keeps in sync with settings.show_week_numbers + /// + + auto b = m_state->settings->show_week_numbers.get(); + for (i=0; i<2; i++) + { + b = !b; + m_state->settings->show_week_numbers.set(b); + + calendar_state = g_action_group_get_action_state (action_group, "calendar"); + v = g_variant_lookup_value (calendar_state, "show-week-numbers", G_VARIANT_TYPE_BOOLEAN); + EXPECT_TRUE(v != nullptr); + EXPECT_EQ(b, g_variant_get_boolean(v)); + + g_clear_pointer(&v, g_variant_unref); + g_clear_pointer(&calendar_state, g_variant_unref); + } +} diff --git a/tests/test-locations.cpp b/tests/test-locations.cpp new file mode 100644 index 0000000..65adbc7 --- /dev/null +++ b/tests/test-locations.cpp @@ -0,0 +1,169 @@ + + +/* + * 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-fixture.h" + +#include <datetime/locations-settings.h> + +using unity::indicator::datetime::Location; +using unity::indicator::datetime::Locations; +using unity::indicator::datetime::Settings; +using unity::indicator::datetime::SettingsLocations; +using unity::indicator::datetime::Timezones; + +/*** +**** +***/ + +class LocationsFixture: public GlibFixture +{ + private: + + typedef GlibFixture super; + + protected: + + //GSettings * settings = nullptr; + std::shared_ptr<Settings> m_settings; + std::shared_ptr<Timezones> m_timezones; + const std::string nyc = "America/New_York"; + const std::string chicago = "America/Chicago"; + + virtual void SetUp() + { + super::SetUp(); + + m_settings.reset(new Settings); + m_settings->show_locations.set(true); + m_settings->locations.set({"America/Los_Angeles Oakland", + "America/Chicago Chicago", + "America/Chicago Oklahoma City", + "America/Toronto Toronto", + "Europe/London London", + "Europe/Berlin Berlin"}); + + m_timezones.reset(new Timezones); + m_timezones->timezone.set(chicago); + m_timezones->timezones.set(std::set<std::string>({ nyc, chicago })); + } + + virtual void TearDown() + { + m_timezones.reset(); + m_settings.reset(); + + super::TearDown(); + } +}; + +TEST_F(LocationsFixture, Timezones) +{ + m_settings->show_locations.set(false); + + SettingsLocations locations(m_settings, m_timezones); + const auto l = locations.locations.get(); + EXPECT_EQ(2, l.size()); + EXPECT_STREQ("Chicago", l[0].name().c_str()); + EXPECT_EQ(chicago, l[0].zone()); + EXPECT_EQ("New York", l[1].name()); + EXPECT_EQ(nyc, l[1].zone()); +} + +TEST_F(LocationsFixture, SettingsLocations) +{ + SettingsLocations locations(m_settings, m_timezones); + + const auto l = locations.locations.get(); + EXPECT_EQ(7, l.size()); + EXPECT_EQ("Chicago", l[0].name()); + EXPECT_EQ(chicago, l[0].zone()); + EXPECT_EQ("New York", l[1].name()); + EXPECT_EQ(nyc, l[1].zone()); + EXPECT_EQ("Oakland", l[2].name()); + EXPECT_EQ("America/Los_Angeles", l[2].zone()); + EXPECT_EQ("Oklahoma City", l[3].name()); + EXPECT_EQ("America/Chicago", l[3].zone()); + EXPECT_EQ("Toronto", l[4].name()); + EXPECT_EQ("America/Toronto", l[4].zone()); + EXPECT_EQ("London", l[5].name()); + EXPECT_EQ("Europe/London", l[5].zone()); + EXPECT_EQ("Berlin", l[6].name()); + EXPECT_EQ("Europe/Berlin", l[6].zone()); +} + +TEST_F(LocationsFixture, ChangeLocationStrings) +{ + SettingsLocations locations(m_settings, m_timezones); + + bool locations_changed = false; + locations.locations.changed().connect([&locations_changed, this](const std::vector<Location>&){ + locations_changed = true; + g_main_loop_quit(loop); + }); + + g_idle_add([](gpointer settings){ + static_cast<Settings*>(settings)->locations.set({"America/Los_Angeles Oakland", "Europe/London London", "Europe/Berlin Berlin"}); + return G_SOURCE_REMOVE; + }, m_settings.get()); + + g_main_loop_run(loop); + + EXPECT_TRUE(locations_changed); + const auto l = locations.locations.get(); + EXPECT_EQ(5, l.size()); + EXPECT_EQ("Chicago", l[0].name()); + EXPECT_EQ(chicago, l[0].zone()); + EXPECT_EQ("New York", l[1].name()); + EXPECT_EQ(nyc, l[1].zone()); + EXPECT_EQ("Oakland", l[2].name()); + EXPECT_EQ("America/Los_Angeles", l[2].zone()); + EXPECT_EQ("London", l[3].name()); + EXPECT_EQ("Europe/London", l[3].zone()); + EXPECT_EQ("Berlin", l[4].name()); + EXPECT_EQ("Europe/Berlin", l[4].zone()); + locations_changed = false; +} + +TEST_F(LocationsFixture, ChangeLocationVisibility) +{ + SettingsLocations locations(m_settings, m_timezones); + + bool locations_changed = false; + locations.locations.changed().connect([&locations_changed, this](const std::vector<Location>&){ + locations_changed = true; + g_main_loop_quit(loop); + }); + + g_idle_add([](gpointer settings){ + static_cast<Settings*>(settings)->show_locations.set(false); + return G_SOURCE_REMOVE; + }, m_settings.get()); + + g_main_loop_run(loop); + + EXPECT_TRUE(locations_changed); + const auto l = locations.locations.get(); + EXPECT_EQ(2, l.size()); + EXPECT_EQ("Chicago", l[0].name()); + EXPECT_EQ(chicago, l[0].zone()); + EXPECT_EQ("New York", l[1].name()); + EXPECT_EQ(nyc, l[1].zone()); +} diff --git a/tests/test-menus.cpp b/tests/test-menus.cpp new file mode 100644 index 0000000..73d6036 --- /dev/null +++ b/tests/test-menus.cpp @@ -0,0 +1,524 @@ +/* + * 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 "actions-mock.h" +#include "state-fixture.h" + +#include <datetime/clock-mock.h> +#include <datetime/locations.h> +#include <datetime/menu.h> +#include <datetime/state.h> + +#include <gio/gio.h> + +using namespace unity::indicator::datetime; + +class MenuFixture: public StateFixture +{ +private: + typedef StateFixture super; + +protected: + std::shared_ptr<MenuFactory> m_menu_factory; + std::vector<std::shared_ptr<Menu>> m_menus; + + virtual void SetUp() + { + super::SetUp(); + + // build the menus on top of the actions and state + m_menu_factory.reset(new MenuFactory(m_actions, m_state)); + for(int i=0; i<Menu::NUM_PROFILES; i++) + m_menus.push_back(m_menu_factory->buildMenu(Menu::Profile(i))); + } + + virtual void TearDown() + { + m_menus.clear(); + m_menu_factory.reset(); + + super::TearDown(); + } + + void InspectHeader(GMenuModel* menu_model, const std::string& name) + { + // check that there's a header menuitem + EXPECT_EQ(1,g_menu_model_get_n_items(menu_model)); + gchar* str = nullptr; + g_menu_model_get_item_attribute(menu_model, 0, "x-canonical-type", "s", &str); + EXPECT_STREQ("com.canonical.indicator.root", str); + g_clear_pointer(&str, g_free); + g_menu_model_get_item_attribute(menu_model, 0, G_MENU_ATTRIBUTE_ACTION, "s", &str); + const auto action_name = name + "-header"; + EXPECT_EQ(std::string("indicator.")+action_name, str); + g_clear_pointer(&str, g_free); + + // check the header + auto dict = g_action_group_get_action_state(m_actions->action_group(), action_name.c_str()); + EXPECT_TRUE(dict != nullptr); + EXPECT_TRUE(g_variant_is_of_type(dict, G_VARIANT_TYPE_VARDICT)); + auto v = g_variant_lookup_value(dict, "accessible-desc", G_VARIANT_TYPE_STRING); + EXPECT_TRUE(v != nullptr); + g_variant_unref(v); + v = g_variant_lookup_value(dict, "label", G_VARIANT_TYPE_STRING); + EXPECT_TRUE(v != nullptr); + g_variant_unref(v); + v = g_variant_lookup_value(dict, "title", G_VARIANT_TYPE_STRING); + EXPECT_TRUE(v != nullptr); + g_variant_unref(v); + v = g_variant_lookup_value(dict, "visible", G_VARIANT_TYPE_BOOLEAN); + EXPECT_TRUE(v != nullptr); + g_variant_unref(v); + g_variant_unref(dict); + } + + void InspectCalendar(GMenuModel* menu_model, Menu::Profile profile) + { + gchar* str = nullptr; + const auto actions_expected = (profile == Menu::Desktop) || (profile == Menu::Phone); + const auto calendar_expected = ((profile == Menu::Desktop) || (profile == Menu::DesktopGreeter)) + && (m_state->settings->show_calendar.get()); + + // get the calendar section + auto submenu = g_menu_model_get_item_link(menu_model, 0, G_MENU_LINK_SUBMENU); + auto section = g_menu_model_get_item_link(submenu, Menu::Calendar, G_MENU_LINK_SECTION); + + // should be one or two items: a date label and maybe a calendar + ASSERT_TRUE(section != nullptr); + auto n_expected = calendar_expected ? 2 : 1; + EXPECT_EQ(n_expected, g_menu_model_get_n_items(section)); + + // look at the date menuitem + g_menu_model_get_item_attribute(section, 0, G_MENU_ATTRIBUTE_LABEL, "s", &str); + const auto now = m_state->clock->localtime(); + EXPECT_EQ(now.format("%A, %e %B %Y"), str); + + g_clear_pointer(&str, g_free); + + g_menu_model_get_item_attribute(section, 0, G_MENU_ATTRIBUTE_ACTION, "s", &str); + if (actions_expected) + EXPECT_STREQ("indicator.activate-planner", str); + else + EXPECT_TRUE(str == nullptr); + g_clear_pointer(&str, g_free); + + // look at the calendar menuitem + if (calendar_expected) + { + g_menu_model_get_item_attribute(section, 1, "x-canonical-type", "s", &str); + EXPECT_STREQ("com.canonical.indicator.calendar", str); + g_clear_pointer(&str, g_free); + + g_menu_model_get_item_attribute(section, 1, G_MENU_ATTRIBUTE_ACTION, "s", &str); + EXPECT_STREQ("indicator.calendar", str); + g_clear_pointer(&str, g_free); + + g_menu_model_get_item_attribute(section, 1, "activation-action", "s", &str); + if (actions_expected) + EXPECT_STREQ("indicator.activate-planner", str); + else + EXPECT_TRUE(str == nullptr); + g_clear_pointer(&str, g_free); + } + + g_clear_object(§ion); + + // now change the clock and see if the date label changes appropriately + + auto gdt_tomorrow = g_date_time_add_days(now.get(), 1); + auto tomorrow = DateTime(gdt_tomorrow); + g_date_time_unref(gdt_tomorrow); + m_mock_state->mock_clock->set_localtime(tomorrow); + wait_msec(); + + section = g_menu_model_get_item_link(submenu, Menu::Calendar, G_MENU_LINK_SECTION); + g_menu_model_get_item_attribute(section, 0, G_MENU_ATTRIBUTE_LABEL, "s", &str); + EXPECT_EQ(tomorrow.format("%A, %e %B %Y"), str); + g_clear_pointer(&str, g_free); + g_clear_object(§ion); + + // cleanup + g_object_unref(submenu); + } + +private: + + void InspectEmptySection(GMenuModel* menu_model, Menu::Section section) + { + // get the Appointments section + auto submenu = g_menu_model_get_item_link(menu_model, 0, G_MENU_LINK_SUBMENU); + auto menu_section = g_menu_model_get_item_link(submenu, section, G_MENU_LINK_SECTION); + EXPECT_EQ(0, g_menu_model_get_n_items(menu_section)); + g_clear_object(&menu_section); + g_clear_object(&submenu); + } + + std::vector<Appointment> build_some_appointments() + { + const auto now = m_state->clock->localtime(); + auto gdt_tomorrow = g_date_time_add_days(now.get(), 1); + const auto tomorrow = DateTime(gdt_tomorrow); + g_date_time_unref(gdt_tomorrow); + + Appointment a1; // an alarm clock appointment + a1.color = "red"; + a1.summary = "Alarm"; + a1.summary = "http://www.example.com/"; + a1.uid = "example"; + a1.has_alarms = true; + a1.begin = a1.end = tomorrow; + + Appointment a2; // a non-alarm appointment + a2.color = "green"; + a2.summary = "Other Text"; + a2.summary = "http://www.monkey.com/"; + a2.uid = "monkey"; + a2.has_alarms = false; + a2.begin = a2.end = tomorrow; + + return std::vector<Appointment>({a1, a2}); + } + + void InspectAppointmentMenuItem(GMenuModel* section, + int index, + const Appointment& appt) + { + // confirm it has the right x-canonical-type + gchar * str = nullptr; + g_menu_model_get_item_attribute(section, index, "x-canonical-type", "s", &str); + if (appt.has_alarms) + EXPECT_STREQ("com.canonical.indicator.alarm", str); + else + EXPECT_STREQ("com.canonical.indicator.appointment", str); + g_clear_pointer(&str, g_free); + + // confirm it has a nonempty x-canonical-time-format + g_menu_model_get_item_attribute(section, index, "x-canonical-time-format", "s", &str); + EXPECT_TRUE(str && *str); + g_clear_pointer(&str, g_free); + + // confirm the color hint, if it exists, + // is in the x-canonical-color attribute + if (appt.color.empty()) + { + EXPECT_FALSE(g_menu_model_get_item_attribute(section, + index, + "x-canonical-color", + "s", + &str)); + } + else + { + EXPECT_TRUE(g_menu_model_get_item_attribute(section, + index, + "x-canonical-color", + "s", + &str)); + EXPECT_EQ(appt.color, str); + } + g_clear_pointer(&str, g_free); + + // confirm that alarms have an icon + if (appt.has_alarms) + { + auto v = g_menu_model_get_item_attribute_value(section, + index, + G_MENU_ATTRIBUTE_ICON, + nullptr); + EXPECT_TRUE(v != nullptr); + auto icon = g_icon_deserialize(v); + EXPECT_TRUE(icon != nullptr); + g_clear_object(&icon); + g_clear_pointer(&v, g_variant_unref); + } + } + + void InspectAppointmentMenuItems(GMenuModel* section, + int first_appt_index, + const std::vector<Appointment>& appointments) + { + // try adding a few appointments and see if the menu updates itself + m_state->planner->upcoming.set(appointments); + wait_msec(); // wait a moment for the menu to update + + //auto submenu = g_menu_model_get_item_link(menu_model, 0, G_MENU_LINK_SUBMENU); + //auto section = g_menu_model_get_item_link(submenu, Menu::Appointments, G_MENU_LINK_SECTION); + EXPECT_EQ(appointments.size()+1, g_menu_model_get_n_items(section)); + + for (int i=0, n=appointments.size(); i<n; i++) + InspectAppointmentMenuItem(section, first_appt_index+i, appointments[i]); + + //g_clear_object(§ion); + //g_clear_object(&submenu); + } + + void InspectDesktopAppointments(GMenuModel* menu_model) + { + // get the Appointments section + auto submenu = g_menu_model_get_item_link(menu_model, 0, G_MENU_LINK_SUBMENU); + + // there shouldn't be any menuitems when "show events" is false + m_state->settings->show_events.set(false); + wait_msec(); + auto section = g_menu_model_get_item_link(submenu, Menu::Appointments, G_MENU_LINK_SECTION); + EXPECT_EQ(0, g_menu_model_get_n_items(section)); + g_clear_object(§ion); + + // when "show_events" is true, + // there should be an "add event" button even if there aren't any appointments + std::vector<Appointment> appointments; + m_state->settings->show_events.set(true); + m_state->planner->upcoming.set(appointments); + wait_msec(); + section = g_menu_model_get_item_link(submenu, Menu::Appointments, G_MENU_LINK_SECTION); + EXPECT_EQ(1, g_menu_model_get_n_items(section)); + gchar* action = nullptr; + EXPECT_TRUE(g_menu_model_get_item_attribute(section, 0, G_MENU_ATTRIBUTE_ACTION, "s", &action)); + const char* expected_action = "activate-planner"; + EXPECT_EQ(std::string("indicator.")+expected_action, action); + EXPECT_TRUE(g_action_group_has_action(m_actions->action_group(), expected_action)); + g_free(action); + g_clear_object(§ion); + + // try adding a few appointments and see if the menu updates itself + appointments = build_some_appointments(); + m_state->planner->upcoming.set(appointments); + wait_msec(); // wait a moment for the menu to update + section = g_menu_model_get_item_link(submenu, Menu::Appointments, G_MENU_LINK_SECTION); + EXPECT_EQ(3, g_menu_model_get_n_items(section)); + InspectAppointmentMenuItems(section, 0, appointments); + g_clear_object(§ion); + + // cleanup + g_clear_object(&submenu); + } + + void InspectPhoneAppointments(GMenuModel* menu_model) + { + auto submenu = g_menu_model_get_item_link(menu_model, 0, G_MENU_LINK_SUBMENU); + + // clear all the appointments + std::vector<Appointment> appointments; + m_state->planner->upcoming.set(appointments); + wait_msec(); // wait a moment for the menu to update + + // check that there's a "clock app" menuitem even when there are no appointments + auto section = g_menu_model_get_item_link(submenu, Menu::Appointments, G_MENU_LINK_SECTION); + const char* expected_action = "activate-phone-clock-app"; + EXPECT_EQ(1, g_menu_model_get_n_items(section)); + gchar* action = nullptr; + EXPECT_TRUE(g_menu_model_get_item_attribute(section, 0, G_MENU_ATTRIBUTE_ACTION, "s", &action)); + EXPECT_EQ(std::string("indicator.")+expected_action, action); + EXPECT_TRUE(g_action_group_has_action(m_actions->action_group(), expected_action)); + g_free(action); + g_clear_object(§ion); + + // add some appointments and test them + appointments = build_some_appointments(); + m_state->planner->upcoming.set(appointments); + wait_msec(); // wait a moment for the menu to update + section = g_menu_model_get_item_link(submenu, Menu::Appointments, G_MENU_LINK_SECTION); + EXPECT_EQ(3, g_menu_model_get_n_items(section)); + InspectAppointmentMenuItems(section, 1, appointments); + g_clear_object(§ion); + + // cleanup + g_clear_object(&submenu); + } + +protected: + + void InspectAppointments(GMenuModel* menu_model, Menu::Profile profile) + { + switch (profile) + { + case Menu::Desktop: + InspectDesktopAppointments(menu_model); + break; + + case Menu::DesktopGreeter: + InspectEmptySection(menu_model, Menu::Appointments); + break; + + case Menu::Phone: + InspectPhoneAppointments(menu_model); + break; + + case Menu::PhoneGreeter: + InspectEmptySection(menu_model, Menu::Appointments); + break; + + default: + g_warn_if_reached(); + break; + } + } + + void CompareLocationsTo(GMenuModel* menu_model, const std::vector<Location>& locations) + { + // get the Locations section + auto submenu = g_menu_model_get_item_link(menu_model, 0, G_MENU_LINK_SUBMENU); + auto section = g_menu_model_get_item_link(submenu, Menu::Locations, G_MENU_LINK_SECTION); + + // confirm that section's menuitems mirror the "locations" vector + const auto n = locations.size(); + ASSERT_EQ(n, g_menu_model_get_n_items(section)); + for (guint i=0; i<n; i++) + { + gchar* str = nullptr; + + // confirm that the x-canonical-type is right + g_menu_model_get_item_attribute(section, i, "x-canonical-type", "s", &str); + EXPECT_STREQ("com.canonical.indicator.location", str); + g_clear_pointer(&str, g_free); + + // confirm that the timezones match the ones in the vector + g_menu_model_get_item_attribute(section, i, "x-canonical-timezone", "s", &str); + EXPECT_EQ(locations[i].zone(), str); + g_clear_pointer(&str, g_free); + + // confirm that x-canonical-time-format has some kind of time format string + g_menu_model_get_item_attribute(section, i, "x-canonical-time-format", "s", &str); + EXPECT_TRUE(str && *str && (strchr(str,'%')!=nullptr)); + g_clear_pointer(&str, g_free); + } + + g_clear_object(§ion); + g_clear_object(&submenu); + } + + void InspectLocations(GMenuModel* menu_model, Menu::Profile profile) + { + const bool locations_expected = profile == Menu::Desktop; + + // when there aren't any locations, confirm the menu is empty + const std::vector<Location> empty; + m_state->locations->locations.set(empty); + wait_msec(); + CompareLocationsTo(menu_model, empty); + + // add some locations and confirm the menu picked up our changes + Location l1 ("America/Chicago", "Dallas"); + Location l2 ("America/Arizona", "Phoenix"); + std::vector<Location> locations({l1, l2}); + m_state->locations->locations.set(locations); + wait_msec(); + CompareLocationsTo(menu_model, locations_expected ? locations : empty); + + // now remove one of the locations... + locations.pop_back(); + m_state->locations->locations.set(locations); + wait_msec(); + CompareLocationsTo(menu_model, locations_expected ? locations : empty); + } + + void InspectSettings(GMenuModel* menu_model, Menu::Profile profile) + { + std::string expected_action; + + if (profile == Menu::Desktop) + expected_action = "indicator.activate-desktop-settings"; + else if (profile == Menu::Phone) + expected_action = "indicator.activate-phone-settings"; + + // get the Settings section + auto submenu = g_menu_model_get_item_link(menu_model, 0, G_MENU_LINK_SUBMENU); + auto section = g_menu_model_get_item_link(submenu, Menu::Settings, G_MENU_LINK_SECTION); + + if (expected_action.empty()) + { + EXPECT_EQ(0, g_menu_model_get_n_items(section)); + } + else + { + EXPECT_EQ(1, g_menu_model_get_n_items(section)); + gchar* str = nullptr; + g_menu_model_get_item_attribute(section, 0, G_MENU_ATTRIBUTE_ACTION, "s", &str); + EXPECT_EQ(expected_action, str); + g_clear_pointer(&str, g_free); + } + + g_clear_object(§ion); + g_object_unref(submenu); + } +}; + + +TEST_F(MenuFixture, HelloWorld) +{ + EXPECT_EQ(Menu::NUM_PROFILES, m_menus.size()); + for (int i=0; i<Menu::NUM_PROFILES; i++) + { + EXPECT_TRUE(m_menus[i] != false); + EXPECT_TRUE(m_menus[i]->menu_model() != nullptr); + EXPECT_EQ(i, m_menus[i]->profile()); + } + EXPECT_EQ(m_menus[Menu::Desktop]->name(), "desktop"); +} + +TEST_F(MenuFixture, Header) +{ + for(auto& menu : m_menus) + InspectHeader(menu->menu_model(), menu->name()); +} + +TEST_F(MenuFixture, Sections) +{ + for(auto& menu : m_menus) + { + // check that the header has a submenu + auto menu_model = menu->menu_model(); + auto submenu = g_menu_model_get_item_link(menu_model, 0, G_MENU_LINK_SUBMENU); + EXPECT_TRUE(submenu != nullptr); + EXPECT_EQ(Menu::NUM_SECTIONS, g_menu_model_get_n_items(submenu)); + g_object_unref(submenu); + } +} + +TEST_F(MenuFixture, Calendar) +{ + m_state->settings->show_calendar.set(true); + for(auto& menu : m_menus) + InspectCalendar(menu->menu_model(), menu->profile()); + + m_state->settings->show_calendar.set(false); + for(auto& menu : m_menus) + InspectCalendar(menu->menu_model(), menu->profile()); +} + +TEST_F(MenuFixture, Appointments) +{ + for(auto& menu : m_menus) + InspectAppointments(menu->menu_model(), menu->profile()); +} + +TEST_F(MenuFixture, Locations) +{ + for(auto& menu : m_menus) + InspectLocations(menu->menu_model(), menu->profile()); +} + +TEST_F(MenuFixture, Settings) +{ + for(auto& menu : m_menus) + InspectSettings(menu->menu_model(), menu->profile()); +} + + diff --git a/tests/test-planner.cpp b/tests/test-planner.cpp new file mode 100644 index 0000000..b476ee8 --- /dev/null +++ b/tests/test-planner.cpp @@ -0,0 +1,85 @@ +/* + * 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-fixture.h" + +#include <datetime/appointment.h> +#include <datetime/clock-mock.h> +#include <datetime/date-time.h> +#include <datetime/planner.h> +#include <datetime/planner-eds.h> + +#include <langinfo.h> +#include <locale.h> + +using unity::indicator::datetime::Appointment; +using unity::indicator::datetime::DateTime; +using unity::indicator::datetime::PlannerEds; + +/*** +**** +***/ + +typedef GlibFixture PlannerFixture; + +TEST_F(PlannerFixture, EDS) +{ + PlannerEds planner; + wait_msec(100); + + auto now = g_date_time_new_now_local(); + planner.time.set(DateTime(now)); + wait_msec(2500); + + std::vector<Appointment> this_month = planner.this_month.get(); + std::cerr << this_month.size() << " appointments this month" << std::endl; + for(const auto& a : this_month) + std::cerr << a.summary << std::endl; +} + + +TEST_F(PlannerFixture, HelloWorld) +{ + auto halloween = g_date_time_new_local(2020, 10, 31, 18, 30, 59); + auto christmas = g_date_time_new_local(2020, 12, 25, 0, 0, 0); + + Appointment a; + a.summary = "Test"; + a.begin = halloween; + a.end = g_date_time_add_hours(halloween, 1); + const Appointment b = a; + a.summary = "Foo"; + + EXPECT_EQ(a.summary, "Foo"); + EXPECT_EQ(b.summary, "Test"); + EXPECT_EQ(0, g_date_time_compare(a.begin(), b.begin())); + EXPECT_EQ(0, g_date_time_compare(a.end(), b.end())); + + Appointment c; + c.begin = christmas; + c.end = g_date_time_add_hours(christmas, 1); + Appointment d; + d = c; + EXPECT_EQ(0, g_date_time_compare(c.begin(), d.begin())); + EXPECT_EQ(0, g_date_time_compare(c.end(), d.end())); + a = d; + EXPECT_EQ(0, g_date_time_compare(d.begin(), a.begin())); + EXPECT_EQ(0, g_date_time_compare(d.end(), a.end())); +} + diff --git a/tests/test-settings.cpp b/tests/test-settings.cpp new file mode 100644 index 0000000..980e7fa --- /dev/null +++ b/tests/test-settings.cpp @@ -0,0 +1,191 @@ +/* + * 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-fixture.h" + +#include <datetime/settings-live.h> +#include <datetime/settings-shared.h> + +using namespace unity::indicator::datetime; + +/*** +**** +***/ + +class SettingsFixture: public GlibFixture +{ +private: + typedef GlibFixture super; + +protected: + + std::shared_ptr<LiveSettings> m_live; + std::shared_ptr<Settings> m_settings; + GSettings * m_gsettings; + + virtual void SetUp() + { + super::SetUp(); + + m_gsettings = g_settings_new(SETTINGS_INTERFACE); + m_live.reset(new LiveSettings); + m_settings = std::dynamic_pointer_cast<Settings>(m_live); + } + + virtual void TearDown() + { + g_clear_object(&m_gsettings); + m_settings.reset(); + m_live.reset(); + + super::TearDown(); + } + + void TestBoolProperty(core::Property<bool>& property, const gchar* key) + { + EXPECT_EQ(g_settings_get_boolean(m_gsettings, key), property.get()); + g_settings_set_boolean(m_gsettings, key, false); + EXPECT_FALSE(property.get()); + g_settings_set_boolean(m_gsettings, key, true); + EXPECT_TRUE(property.get()); + + property.set(false); + EXPECT_FALSE(g_settings_get_boolean(m_gsettings, key)); + property.set(true); + EXPECT_TRUE(g_settings_get_boolean(m_gsettings, key)); + } + + void TestStringProperty(core::Property<std::string>& property, const gchar* key) + { + gchar* tmp; + std::string str; + + tmp = g_settings_get_string(m_gsettings, key); + EXPECT_EQ(tmp, property.get()); + g_clear_pointer(&tmp, g_free); + + str = "a"; + g_settings_set_string(m_gsettings, key, str.c_str()); + EXPECT_EQ(str, property.get()); + + str = "b"; + g_settings_set_string(m_gsettings, key, str.c_str()); + EXPECT_EQ(str, property.get()); + + str = "a"; + property.set(str); + tmp = g_settings_get_string(m_gsettings, key); + EXPECT_EQ(str, tmp); + g_clear_pointer(&tmp, g_free); + + str = "b"; + property.set(str); + tmp = g_settings_get_string(m_gsettings, key); + EXPECT_EQ(str, tmp); + g_clear_pointer(&tmp, g_free); + } +}; + +/*** +**** +***/ + +TEST_F(SettingsFixture, HelloWorld) +{ + EXPECT_TRUE(true); +} + +TEST_F(SettingsFixture, BoolProperties) +{ + TestBoolProperty(m_settings->show_seconds, SETTINGS_SHOW_SECONDS_S); + TestBoolProperty(m_settings->show_calendar, SETTINGS_SHOW_CALENDAR_S); + TestBoolProperty(m_settings->show_clock, SETTINGS_SHOW_CLOCK_S); + TestBoolProperty(m_settings->show_date, SETTINGS_SHOW_DATE_S); + TestBoolProperty(m_settings->show_day, SETTINGS_SHOW_DAY_S); + TestBoolProperty(m_settings->show_detected_location, SETTINGS_SHOW_DETECTED_S); + TestBoolProperty(m_settings->show_events, SETTINGS_SHOW_EVENTS_S); + TestBoolProperty(m_settings->show_locations, SETTINGS_SHOW_LOCATIONS_S); + TestBoolProperty(m_settings->show_week_numbers, SETTINGS_SHOW_WEEK_NUMBERS_S); + TestBoolProperty(m_settings->show_year, SETTINGS_SHOW_YEAR_S); +} + +TEST_F(SettingsFixture, StringProperties) +{ + TestStringProperty(m_settings->custom_time_format, SETTINGS_CUSTOM_TIME_FORMAT_S); + TestStringProperty(m_settings->timezone_name, SETTINGS_TIMEZONE_NAME_S); +} + +TEST_F(SettingsFixture, TimeFormatMode) +{ + const auto key = SETTINGS_TIME_FORMAT_S; + const TimeFormatMode modes[] = { TIME_FORMAT_MODE_LOCALE_DEFAULT, + TIME_FORMAT_MODE_12_HOUR, + TIME_FORMAT_MODE_24_HOUR, + TIME_FORMAT_MODE_CUSTOM }; + + for(const auto& mode : modes) + { + g_settings_set_enum(m_gsettings, key, mode); + EXPECT_EQ(mode, m_settings->time_format_mode.get()); + } + + for(const auto& mode : modes) + { + m_settings->time_format_mode.set(mode); + EXPECT_EQ(mode, g_settings_get_enum(m_gsettings, key)); + } +} + +namespace +{ + std::vector<std::string> strv_to_vector(const gchar** strv) + { + std::vector<std::string> v; + for(int i=0; strv && strv[i]; i++) + v.push_back(strv[i]); + return v; + } +}; + +TEST_F(SettingsFixture, Locations) +{ + const auto key = SETTINGS_LOCATIONS_S; + + const gchar* astrv[] = {"America/Los_Angeles Oakland", "America/Chicago Oklahoma City", "Europe/London London", NULL}; + const gchar* bstrv[] = {"America/Denver", "Europe/London London", "Europe/Berlin Berlin", NULL}; + const std::vector<std::string> av = strv_to_vector(astrv); + const std::vector<std::string> bv = strv_to_vector(bstrv); + + g_settings_set_strv(m_gsettings, key, astrv); + EXPECT_EQ(av, m_settings->locations.get()); + g_settings_set_strv(m_gsettings, key, bstrv); + EXPECT_EQ(bv, m_settings->locations.get()); + + m_settings->locations.set(av); + auto tmp = g_settings_get_strv(m_gsettings, key); + auto vtmp = strv_to_vector((const gchar**)tmp); + g_strfreev(tmp); + EXPECT_EQ(av, vtmp); + + m_settings->locations.set(bv); + tmp = g_settings_get_strv(m_gsettings, key); + vtmp = strv_to_vector((const gchar**)tmp); + g_strfreev(tmp); + EXPECT_EQ(bv, vtmp); +} diff --git a/tests/test-timezone-file.cpp b/tests/test-timezone-file.cpp new file mode 100644 index 0000000..453b353 --- /dev/null +++ b/tests/test-timezone-file.cpp @@ -0,0 +1,133 @@ + +/* + * 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-fixture.h" + +#include <datetime/timezone-file.h> + +//#include <condition_variable> +//#include <mutex> +//#include <queue> +//#include <string> +//#include <thread> +//#include <iostream> +//#include <istream> +//#include <fstream> + +#include <cstdio> // fopen() +//#include <sys/stat.h> // chmod() +#include <unistd.h> // sync() + +using unity::indicator::datetime::FileTimezone; + + +/*** +**** +***/ + +#define TIMEZONE_FILE (SANDBOX"/timezone") + +class TimezoneFixture: public GlibFixture +{ + private: + + typedef GlibFixture super; + + protected: + + virtual void SetUp() + { + super::SetUp(); + } + + virtual void TearDown() + { + super::TearDown(); + } + + public: + + /* convenience func to set the timezone file */ + void set_file(const std::string& text) + { + auto fp = fopen(TIMEZONE_FILE, "w+"); + fprintf(fp, "%s\n", text.c_str()); + fclose(fp); + sync(); + } +}; + + +/** + * Test that timezone-file warns, but doesn't crash, if the timezone file doesn't exist + */ +TEST_F(TimezoneFixture, NoFile) +{ + remove(TIMEZONE_FILE); + ASSERT_FALSE(g_file_test(TIMEZONE_FILE, G_FILE_TEST_EXISTS)); + + FileTimezone tz(TIMEZONE_FILE); + testLogCount(G_LOG_LEVEL_WARNING, 1); +} + + +/** + * Test that timezone-file picks up the initial value + */ +TEST_F(TimezoneFixture, InitialValue) +{ + const std::string expected_timezone = "America/Chicago"; + set_file(expected_timezone); + FileTimezone tz(TIMEZONE_FILE); + ASSERT_EQ(expected_timezone, tz.timezone.get()); +} + + +/** + * Test that clearing the timezone results in an empty string + */ +TEST_F(TimezoneFixture, ChangedValue) +{ + const std::string initial_timezone = "America/Chicago"; + const std::string changed_timezone = "America/New_York"; + set_file(initial_timezone); + + FileTimezone tz(TIMEZONE_FILE); + ASSERT_EQ(initial_timezone, tz.timezone.get()); + + bool changed = false; + auto connection = tz.timezone.changed().connect( + [&changed, this](const std::string& s){ + g_message("timezone changed to %s", s.c_str()); + changed = true; + g_main_loop_quit(loop); + }); + + g_idle_add([](gpointer gself){ + static_cast<TimezoneFixture*>(gself)->set_file("America/New_York"); + // static_cast<FileTimezone*>(gtz)->timezone.set("America/New_York"); + return G_SOURCE_REMOVE; + }, this);//&tz); + + g_main_loop_run(loop); + + ASSERT_TRUE(changed); + ASSERT_EQ(changed_timezone, tz.timezone.get()); +} diff --git a/tests/test-timezone-geoclue.cpp b/tests/test-timezone-geoclue.cpp new file mode 100644 index 0000000..3cc1393 --- /dev/null +++ b/tests/test-timezone-geoclue.cpp @@ -0,0 +1,48 @@ +/* + * 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-fixture.h" + +#include <datetime/timezone-geoclue.h> + +using unity::indicator::datetime::GeoclueTimezone; + +// This test looks small because the interesting +// work is all happening in GeoclueFixture... +TEST_F(GeoclueFixture, ChangeDetected) +{ + GeoclueTimezone tz; + wait_msec(500); // wait for the bus to get set up + EXPECT_EQ(timezone_1, tz.timezone.get()); + + // Start listening for a timezone change, then change the timezone. + + bool changed = false; + auto connection = tz.timezone.changed().connect( + [&changed, this](const std::string& s){ + g_debug("timezone changed to %s", s.c_str()); + changed = true; + g_main_loop_quit(loop); + }); + + const std::string timezone_2 = "America/Chicago"; + setGeoclueTimezoneOnIdle(timezone_2); + g_main_loop_run(loop); + EXPECT_EQ(timezone_2, tz.timezone.get()); +} diff --git a/tests/test-timezones.cpp b/tests/test-timezones.cpp new file mode 100644 index 0000000..3f02761 --- /dev/null +++ b/tests/test-timezones.cpp @@ -0,0 +1,124 @@ +/* + * 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 "geoclue-fixture.h" + +#include <datetime/settings.h> +#include <datetime/timezones-live.h> + +#include <memory> // std::shared_ptr + +#include <cstdio> // fopen() +#include <unistd.h> // sync() + +using namespace unity::indicator::datetime; + +typedef GeoclueFixture TimezonesFixture; + +#define TIMEZONE_FILE (SANDBOX "/timezone") + +namespace +{ + /* convenience func to set the timezone file */ + void set_file(const std::string& text) + { + auto fp = fopen(TIMEZONE_FILE, "w+"); + fprintf(fp, "%s\n", text.c_str()); + fclose(fp); + sync(); + } +} + + +TEST_F(TimezonesFixture, ManagerTest) +{ + std::string timezone_file = "America/New_York"; + std::string timezone_geo = "America/Denver"; + + set_file(timezone_file); + std::shared_ptr<Settings> settings(new Settings); + LiveTimezones z(settings, TIMEZONE_FILE); + wait_msec(500); // wait for the bus to get set up + EXPECT_EQ(timezone_file, z.timezone.get()); + auto zones = z.timezones.get(); + //std::set<std::string> zones = z.timezones.get(); + EXPECT_EQ(1, zones.size()); + EXPECT_EQ(1, zones.count(timezone_file)); + + bool zone_changed = false; + auto zone_connection = z.timezone.changed().connect([&zone_changed, this](const std::string&) { + zone_changed = true; + g_main_loop_quit(loop); + }); + + // start listening for a timezone change, then change the timezone + bool zones_changed = false; + auto zones_connection = z.timezones.changed().connect([&zones_changed, &zones, this](const std::set<std::string>& timezones) { + zones_changed = true; + zones = timezones; + g_main_loop_quit(loop); + }); + + g_idle_add([](gpointer s_in) { + auto s = static_cast<Settings*>(s_in); + g_message("geolocation was %d", (int)s->show_detected_location.get()); + g_message("turning geolocation on"); + s->show_detected_location.set(true); + return G_SOURCE_REMOVE; + }, settings.get()); + + // turn on geoclue during the idle... this should add timezone_1 to the 'timezones' property + g_main_loop_run(loop); + EXPECT_TRUE(zones_changed); + EXPECT_EQ(timezone_file, z.timezone.get()); + EXPECT_EQ(2, zones.size()); + EXPECT_EQ(1, zones.count(timezone_file)); + EXPECT_EQ(1, zones.count(timezone_geo)); + zones_changed = false; + + // now tweak the geoclue value... the geoclue-detected timezone should change, + // causing the 'timezones' property to change + zone_changed = false; + zones_changed = false; + timezone_geo = "America/Chicago"; + setGeoclueTimezoneOnIdle(timezone_geo); + g_main_loop_run(loop); + EXPECT_FALSE(zone_changed); + EXPECT_TRUE(zones_changed); + EXPECT_EQ(timezone_file, z.timezone.get()); + EXPECT_EQ(2, zones.size()); + EXPECT_EQ(1, zones.count(timezone_file)); + EXPECT_EQ(1, zones.count(timezone_geo)); + + // now set the file value... this should change both the primary property and set property + zone_changed = false; + zones_changed = false; + timezone_file = "America/Los_Angeles"; + EXPECT_EQ(0, zones.count(timezone_file)); + g_idle_add([](gpointer str) {set_file(static_cast<const char*>(str)); return G_SOURCE_REMOVE;}, const_cast<char*>(timezone_file.c_str())); + g_main_loop_run(loop); + EXPECT_TRUE(zone_changed); + EXPECT_TRUE(zones_changed); + EXPECT_EQ(timezone_file, z.timezone.get()); + EXPECT_EQ(2, zones.size()); + EXPECT_EQ(1, zones.count(timezone_file)); + EXPECT_EQ(1, zones.count(timezone_geo)); +} + + diff --git a/tests/test-utils.cc b/tests/test-utils.cc deleted file mode 100644 index d0f277b..0000000 --- a/tests/test-utils.cc +++ /dev/null @@ -1,112 +0,0 @@ -/* -Copyright 2012 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 <gtest/gtest.h> - -#include <glib-object.h> - -#include "utils.h" - -/*** -**** -***/ - -TEST (UtilsTest, SplitSettingsLocation) -{ - guint i; - guint n; - - struct { - const char * location; - const char * expected_zone; - const char * expected_name; - } test_cases[] = { - { "America/Chicago Chicago", "America/Chicago", "Chicago" }, - { "America/Chicago Oklahoma City", "America/Chicago", "Oklahoma City" }, - { "America/Los_Angeles", "America/Los_Angeles", "Los Angeles" }, - { "America/Los_Angeles ", "America/Los_Angeles", "Los Angeles" }, - { " America/Los_Angeles", "America/Los_Angeles", "Los Angeles" }, - { " America/Los_Angeles ", "America/Los_Angeles", "Los Angeles" }, - { "UTC UTC", "UTC", "UTC" } - }; - - for (i=0, n=G_N_ELEMENTS(test_cases); i<n; i++) - { - char * zone = NULL; - char * name = NULL; - - split_settings_location (test_cases[i].location, &zone, &name); - ASSERT_STREQ (test_cases[i].expected_zone, zone); - ASSERT_STREQ (test_cases[i].expected_name, name); - - g_free (zone); - g_free (name); - } -} - -/*** -**** -***/ - -#define EM_SPACE "\xE2\x80\x82" - -TEST (UtilsTest, GenerateTerseFormatString) -{ - guint i; - guint n; - GDateTime * arbitrary_day = g_date_time_new_local (2013, 6, 25, 12, 34, 56); - GDateTime * on_the_hour = g_date_time_new_local (2013, 6, 25, 12, 0, 0); - - struct { - GDateTime * now; - GDateTime * time; - const char * expected_format_string; - } test_cases[] = { - { g_date_time_ref(arbitrary_day), g_date_time_ref(arbitrary_day), "%l:%M %p" }, /* identical time */ - { g_date_time_ref(arbitrary_day), g_date_time_add_hours(arbitrary_day,1), "%l:%M %p" }, /* later today */ - { g_date_time_ref(arbitrary_day), g_date_time_add_days(arbitrary_day,1), "Tomorrow" EM_SPACE "%l:%M %p" }, /* tomorrow */ - { g_date_time_ref(arbitrary_day), g_date_time_add_days(arbitrary_day,2), "%a" EM_SPACE "%l:%M %p" }, - { g_date_time_ref(arbitrary_day), g_date_time_add_days(arbitrary_day,6), "%a" EM_SPACE "%l:%M %p" }, - { g_date_time_ref(arbitrary_day), g_date_time_add_days(arbitrary_day,7), "%d %b" EM_SPACE "%l:%M %p" }, /* over one week away */ - - { g_date_time_ref(on_the_hour), g_date_time_ref(on_the_hour), "%l %p" }, /* identical time */ - { g_date_time_ref(on_the_hour), g_date_time_add_hours(on_the_hour,1), "%l %p" }, /* later today */ - { g_date_time_ref(on_the_hour), g_date_time_add_days(on_the_hour,1), "Tomorrow" EM_SPACE "%l %p" }, /* tomorrow */ - { g_date_time_ref(on_the_hour), g_date_time_add_days(on_the_hour,2), "%a" EM_SPACE "%l %p" }, - { g_date_time_ref(on_the_hour), g_date_time_add_days(on_the_hour,6), "%a" EM_SPACE "%l %p" }, - { g_date_time_ref(on_the_hour), g_date_time_add_days(on_the_hour,7), "%d %b" EM_SPACE "%l %p" }, /* over one week away */ - }; - - for (i=0, n=G_N_ELEMENTS(test_cases); i<n; i++) - { - char * format_string; - - format_string = generate_terse_format_string_at_time (test_cases[i].now, - test_cases[i].time); - - ASSERT_STREQ (test_cases[i].expected_format_string, format_string); - - g_free (format_string); - g_date_time_unref (test_cases[i].now); - g_date_time_unref (test_cases[i].time); - } - - g_date_time_unref (arbitrary_day); - g_date_time_unref (on_the_hour); -} diff --git a/tests/test-utils.cpp b/tests/test-utils.cpp new file mode 100644 index 0000000..036c13f --- /dev/null +++ b/tests/test-utils.cpp @@ -0,0 +1,98 @@ +/* + * 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-shared.h> +#include <datetime/utils.h> + +#include <gtest/gtest.h> + +TEST(UtilsTest, SplitSettingsLocation) +{ + struct { + const char* location; + const char* expected_zone; + const char* expected_name; + } test_cases[] = { + { "America/Chicago Chicago", "America/Chicago", "Chicago" }, + { "America/Chicago Oklahoma City", "America/Chicago", "Oklahoma City" }, + { "America/Los_Angeles", "America/Los_Angeles", "Los Angeles" }, + { "America/Los_Angeles ", "America/Los_Angeles", "Los Angeles" }, + { " America/Los_Angeles", "America/Los_Angeles", "Los Angeles" }, + { " America/Los_Angeles ", "America/Los_Angeles", "Los Angeles" }, + { "UTC UTC", "UTC", "UTC" } + }; + + for(const auto& test_case : test_cases) + { + char * zone = nullptr; + char * name = nullptr; + + split_settings_location(test_case.location, &zone, &name); + ASSERT_STREQ(test_case.expected_zone, zone); + ASSERT_STREQ(test_case.expected_name, name); + + g_free(zone); + g_free(name); + } +} + +namespace +{ + struct { + const char* timezone; + const char* location; + const char* expected_name; + } beautify_timezone_test_cases[] = { + { "America/Chicago", NULL, "Chicago" }, + { "America/Chicago", "America/Chicago", "Chicago" }, + { "America/Chicago", "America/Chigago Chicago", "Chicago" }, + { "America/Chicago", "America/Chicago Oklahoma City", "Oklahoma City" }, + { "America/Chicago", "Europe/London London", "Chicago" } + }; +} + +TEST(UtilsTest, BeautifulTimezoneName) +{ + for(const auto& test_case : beautify_timezone_test_cases) + { + auto name = get_beautified_timezone_name(test_case.timezone, test_case.location); + EXPECT_STREQ(test_case.expected_name, name); + g_free(name); + } +} + + +TEST(UtilsTest, GetTimezonename) +{ + // set up a local GSettings + g_assert(g_setenv("GSETTINGS_SCHEMA_DIR", SCHEMA_DIR, true)); + g_assert(g_setenv("GSETTINGS_BACKEND", "memory", true)); + g_debug("SCHEMA_DIR is %s", SCHEMA_DIR); + auto settings = g_settings_new(SETTINGS_INTERFACE); + + for(const auto& test_case : beautify_timezone_test_cases) + { + g_settings_set_string(settings, SETTINGS_TIMEZONE_NAME_S, test_case.location); + auto name = get_timezone_name (test_case.timezone, settings); + EXPECT_STREQ(test_case.expected_name, name); + g_free(name); + } + + g_clear_object(&settings); +} diff --git a/trim-lcov.py b/trim-lcov.py new file mode 100755 index 0000000..78613d3 --- /dev/null +++ b/trim-lcov.py @@ -0,0 +1,53 @@ +#!/usr/bin/python + +# This script removes branch and/or line coverage data for lines that +# contain a particular substring. +# +# In the interest of "fairness" it removes all branch or coverage data +# when a match is found -- not just negative data. It is therefore +# likely that running this script will actually reduce the total number +# of lines and branches that are marked as covered (in absolute terms). +# +# This script intentionally avoids checking for errors. Any exceptions +# will trigger make to fail. +# +# Author: Ryan Lortie <desrt@desrt.ca> + +import sys + +line_suppress = ['g_assert_not_reached'] +branch_suppress = ['g_assert', 'g_return_if_fail', 'g_clear_object', 'g_clear_pointer', 'g_return_val_if_fail', 'G_DEFINE_TYPE'] + +def check_suppress(suppressions, source, data): + line, _, rest = data.partition(',') + line = int(line) - 1 + + assert line < len(source) + + for suppression in suppressions: + if suppression in source[line]: + return True + + return False + +source = [] +for line in sys.stdin: + line = line[:-1] + + keyword, _, rest = line.partition(':') + + # Source file + if keyword == 'SF': + source = file(rest).readlines() + + # Branch coverage data + elif keyword == 'BRDA': + if check_suppress(branch_suppress, source, rest): + continue + + # Line coverage data + elif keyword == 'DA': + if check_suppress(line_suppress, source, rest): + continue + + print line |