aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt34
-rw-r--r--README53
-rw-r--r--cmake/GCov.cmake3
-rw-r--r--data/CMakeLists.txt22
-rw-r--r--data/datetime-dialog.ui864
-rw-r--r--data/gnome-indicator-datetime-panel.desktop.in13
-rw-r--r--debian/changelog49
-rw-r--r--debian/control23
-rw-r--r--debian/gnome-control-center-datetime.install3
-rw-r--r--debian/indicator-datetime.install6
-rw-r--r--include/CMakeLists.txt1
-rw-r--r--include/datetime/CMakeLists.txt2
-rw-r--r--include/datetime/actions-live.h60
-rw-r--r--include/datetime/actions.h74
-rw-r--r--include/datetime/appointment.h55
-rw-r--r--include/datetime/clock-mock.h61
-rw-r--r--include/datetime/clock-watcher.h72
-rw-r--r--include/datetime/clock.h99
-rw-r--r--include/datetime/date-time.h71
-rw-r--r--include/datetime/dbus-shared.h25
-rw-r--r--include/datetime/exporter.h74
-rw-r--r--include/datetime/formatter.h138
-rw-r--r--include/datetime/locations-settings.h55
-rw-r--r--include/datetime/locations.h79
-rw-r--r--include/datetime/menu.h85
-rw-r--r--include/datetime/planner-eds.h50
-rw-r--r--include/datetime/planner.h76
-rw-r--r--include/datetime/settings-live.h70
-rw-r--r--include/datetime/settings-shared.h (renamed from src/settings-shared.h)45
-rw-r--r--include/datetime/settings.h65
-rw-r--r--include/datetime/snap.h51
-rw-r--r--include/datetime/state.h75
-rw-r--r--include/datetime/timezone-file.h63
-rw-r--r--include/datetime/timezone-geoclue.h69
-rw-r--r--include/datetime/timezone.h45
-rw-r--r--include/datetime/timezones-live.h56
-rw-r--r--include/datetime/timezones.h59
-rw-r--r--include/datetime/utils.h54
-rw-r--r--panel/CMakeLists.txt25
-rw-r--r--panel/datetime-prefs-locations.c683
-rw-r--r--panel/datetime-prefs-locations.h35
-rw-r--r--panel/datetime-prefs.c863
-rw-r--r--po/CMakeLists.txt2
-rw-r--r--po/POTFILES.in10
-rw-r--r--src/CMakeLists.txt58
-rw-r--r--src/actions-live.cpp213
-rw-r--r--src/actions.cpp266
-rw-r--r--src/appointment.cpp48
-rw-r--r--src/clock-live.c278
-rw-r--r--src/clock-live.cpp163
-rw-r--r--src/clock-live.h73
-rw-r--r--src/clock-watcher.cpp71
-rw-r--r--src/clock.c110
-rw-r--r--src/clock.cpp93
-rw-r--r--src/clock.h76
-rw-r--r--src/date-time.cpp177
-rw-r--r--src/dbus-shared.h24
-rw-r--r--src/exporter.cpp145
-rw-r--r--src/formatter-desktop.cpp169
-rw-r--r--src/formatter.cpp267
-rw-r--r--src/locations-settings.cpp92
-rw-r--r--src/locations.cpp59
-rw-r--r--src/main.c83
-rw-r--r--src/main.cpp99
-rw-r--r--src/menu.cpp572
-rw-r--r--src/planner-eds.c653
-rw-r--r--src/planner-eds.cpp536
-rw-r--r--src/planner-eds.h58
-rw-r--r--src/planner.c281
-rw-r--r--src/planner.h167
-rw-r--r--src/service.c2403
-rw-r--r--src/service.h84
-rw-r--r--src/settings-live.cpp257
-rw-r--r--src/snap.cpp256
-rw-r--r--src/timezone-file.c212
-rw-r--r--src/timezone-file.cpp116
-rw-r--r--src/timezone-file.h58
-rw-r--r--src/timezone-geoclue.c227
-rw-r--r--src/timezone-geoclue.cpp250
-rw-r--r--src/timezone-geoclue.h57
-rw-r--r--src/timezone.c134
-rw-r--r--src/timezone.h72
-rw-r--r--src/timezones-live.cpp72
-rw-r--r--src/utils.c611
-rw-r--r--src/utils.h66
-rw-r--r--tests/CMakeLists.txt43
-rw-r--r--tests/Makefile.am.strings38
-rw-r--r--tests/actions-mock.h82
-rw-r--r--tests/geoclue-fixture.h150
-rw-r--r--tests/glib-fixture.h96
-rw-r--r--tests/manual-test-snap.cpp63
-rw-r--r--tests/planner-mock.c178
-rw-r--r--tests/planner-mock.h52
-rw-r--r--tests/state-fixture.h60
-rw-r--r--tests/state-mock.h43
-rw-r--r--tests/test-actions.cpp232
-rw-r--r--tests/test-clock-watcher.cpp166
-rw-r--r--tests/test-clock.cpp140
-rw-r--r--tests/test-dbus-fixture.h102
-rw-r--r--tests/test-exporter.cpp134
-rw-r--r--tests/test-formatter.cc98
-rw-r--r--tests/test-formatter.cpp256
-rw-r--r--tests/test-indicator.cc92
-rw-r--r--tests/test-live-actions.cpp403
-rw-r--r--tests/test-locations.cpp169
-rw-r--r--tests/test-menus.cpp524
-rw-r--r--tests/test-planner.cpp87
-rw-r--r--tests/test-settings.cpp191
-rw-r--r--tests/test-timezone-file.cpp133
-rw-r--r--tests/test-timezone-geoclue.cpp48
-rw-r--r--tests/test-timezones.cpp124
-rw-r--r--tests/test-utils.cc112
-rw-r--r--tests/test-utils.cpp98
-rwxr-xr-xtrim-lcov.py53
114 files changed, 9377 insertions, 8713 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0ee5cf9..9b468a5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -38,21 +38,11 @@ pkg_check_modules (SERVICE_DEPS REQUIRED
libical>=0.48
libecal-1.2>=3.5
libedataserver-1.2>=3.5
+ libcanberra>=0.12
libnotify>=0.7.6
url-dispatcher-1>=1
- json-glib-1.0>=0.16.2)
-include_directories (${SERVICE_INCLUDE_DIRS})
-
-pkg_check_modules (PANEL_DEPS
- glib-2.0>=2.36
- gio-unix-2.0>=2.36
- gtk+-3.0>=3.1.4
- timezonemap
- libgnome-control-center
- polkit-gobject-1)
-if (PANEL_DEPS_FOUND)
- set (BUILD_PANEL 1)
-endif ()
+ properties-cpp>=0.0.1)
+include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS})
##
## custom targets
@@ -75,12 +65,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)
@@ -91,13 +83,11 @@ if (${enable_tests})
endif ()
# actually build things
-add_subdirectory (src)
-if (BUILD_PANEL)
- add_subdirectory (panel)
-endif ()
-add_subdirectory (data)
-add_subdirectory (po)
+add_subdirectory(include)
+add_subdirectory(src)
+add_subdirectory(data)
+add_subdirectory(po)
if (${enable_tests})
- add_subdirectory (tests)
+ add_subdirectory(tests)
endif ()
diff --git a/README b/README
index b31db05..6bd0edb 100644
--- a/README
+++ b/README
@@ -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/data/CMakeLists.txt b/data/CMakeLists.txt
index ab393a7..c45e1f9 100644
--- a/data/CMakeLists.txt
+++ b/data/CMakeLists.txt
@@ -62,25 +62,3 @@ set (UNITY_INDICATOR_FILE "${CMAKE_CURRENT_SOURCE_DIR}/${UNITY_INDICATOR_NAME}")
install (FILES "${UNITY_INDICATOR_FILE}"
DESTINATION "${UNITY_INDICATOR_DIR}")
-
-
-##
-## gnome-control-center panel: .ui and .desktop files
-##
-
-if (BUILD_PANEL)
-
- # the .ui file
- install (FILES "datetime-dialog.ui"
- DESTINATION "${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME}")
-
- # the .desktop file
- set (DESKTOP_NAME "gnome-indicator-datetime-panel.desktop")
- set (DESKTOP_FILE "${CMAKE_CURRENT_BINARY_DIR}/${DESKTOP_NAME}")
- set (DESKTOP_FILE_IN "${CMAKE_CURRENT_SOURCE_DIR}/${DESKTOP_NAME}.in")
- set (ENV{LC_ALL} "C")
- execute_process (COMMAND intltool-merge -quiet --desktop-style --utf8 "${CMAKE_SOURCE_DIR}/po" "${DESKTOP_FILE_IN}" "${DESKTOP_FILE}")
- install (FILES ${DESKTOP_FILE}
- DESTINATION "${CMAKE_INSTALL_DATADIR}/applications")
-
-endif ()
diff --git a/data/datetime-dialog.ui b/data/datetime-dialog.ui
deleted file mode 100644
index 6f74cd0..0000000
--- a/data/datetime-dialog.ui
+++ /dev/null
@@ -1,864 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<interface>
- <requires lib="gtk+" version="2.24"/>
- <!-- interface-naming-policy project-wide -->
- <object class="GtkAdjustment" id="dateAdjustment">
- <property name="upper">1.8446744073709552e+19</property>
- <property name="step_increment">86400</property>
- <property name="page_increment">864000</property>
- </object>
- <object class="GtkWindow" id="locationsDialog">
- <property name="can_focus">False</property>
- <property name="title" translatable="yes">Locations</property>
- <property name="default_width">300</property>
- <property name="default_height">200</property>
- <property name="destroy_with_parent">True</property>
- <child>
- <object class="GtkVBox" id="vbox1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <child>
- <object class="GtkScrolledWindow" id="scrolledwindow1">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="hscrollbar_policy">automatic</property>
- <property name="vscrollbar_policy">automatic</property>
- <child>
- <object class="GtkTreeView" id="locationsView">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="model">locationsStore</property>
- <property name="headers_visible">False</property>
- <property name="headers_clickable">False</property>
- <property name="reorderable">True</property>
- <property name="search_column">0</property>
- </object>
- </child>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkHSeparator" id="hseparator1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkHBox" id="hbox10">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="spacing">4</property>
- <child>
- <object class="GtkButton" id="addButton">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="use_action_appearance">False</property>
- <child internal-child="accessible">
- <object class="AtkObject" id="addButton-atkobject">
- <property name="AtkObject::accessible-description" translatable="yes">Add a Location…</property>
- </object>
- </child>
- <child>
- <object class="GtkImage" id="addImage">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="stock">gtk-add</property>
- </object>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkButton" id="removeButton">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="use_action_appearance">False</property>
- <child internal-child="accessible">
- <object class="AtkObject" id="removeButton-atkobject">
- <property name="AtkObject::accessible-description" translatable="yes">Remove This Location</property>
- </object>
- </child>
- <child>
- <object class="GtkImage" id="removeImage">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="stock">gtk-remove</property>
- </object>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkButton" id="sortByNameButton">
- <property name="label" translatable="yes">Sort by _Name</property>
- <property name="use_action_appearance">False</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="use_action_appearance">False</property>
- <property name="use_underline">True</property>
- <accelerator key="n" signal="clicked" modifiers="GDK_MOD1_MASK"/>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="padding">6</property>
- <property name="position">2</property>
- </packing>
- </child>
- <child>
- <object class="GtkButton" id="sortByTimeButton">
- <property name="label" translatable="yes">Sort by _Time</property>
- <property name="use_action_appearance">False</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="use_action_appearance">False</property>
- <property name="use_underline">True</property>
- <accelerator key="t" signal="clicked" modifiers="GDK_MOD1_MASK"/>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">3</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="padding">4</property>
- <property name="position">2</property>
- </packing>
- </child>
- </object>
- </child>
- </object>
- <object class="GtkListStore" id="locationsStore">
- <columns>
- <!-- column-name Location -->
- <column type="gchararray"/>
- <!-- column-name Time -->
- <column type="gchararray"/>
- <!-- column-name Zone -->
- <column type="gchararray"/>
- <!-- column-name Visible Name -->
- <column type="gchararray"/>
- <!-- column-name Icon -->
- <column type="gchararray"/>
- </columns>
- </object>
- <object class="GtkAdjustment" id="timeAdjustment">
- <property name="upper">1.8446744073709552e+19</property>
- <property name="step_increment">60</property>
- <property name="page_increment">3600</property>
- </object>
- <object class="GtkEventBox" id="timeDatePanel">
- <property name="can_focus">False</property>
- <property name="border_width">5</property>
- <child>
- <object class="GtkNotebook" id="notebook1">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <child>
- <object class="GtkVBox" id="timeDateBox">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="border_width">12</property>
- <property name="spacing">6</property>
- <child>
- <object class="GtkVBox" id="timeDateOptions">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="spacing">12</property>
- <child>
- <object class="GtkVBox" id="vbox6">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="spacing">6</property>
- <child>
- <object class="GtkAspectFrame" id="mapBox">
- <property name="height_request">265</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label_xalign">0</property>
- <property name="shadow_type">none</property>
- <child>
- <placeholder/>
- </child>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkHBox" id="hbox1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="spacing">6</property>
- <child>
- <object class="GtkLabel" id="label5">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">1</property>
- <property name="label" translatable="yes">_Location:</property>
- <property name="use_underline">True</property>
- <property name="mnemonic_widget">timezoneEntry</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkEntry" id="timezoneEntry">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="invisible_char">•</property>
- <property name="invisible_char_set">True</property>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkTable" id="table1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="n_rows">2</property>
- <property name="n_columns">2</property>
- <property name="column_spacing">6</property>
- <property name="row_spacing">6</property>
- <child>
- <object class="GtkHBox" id="hbox2">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="spacing">6</property>
- <child>
- <object class="GtkRadioButton" id="manualTimeRadio">
- <property name="label" translatable="yes">_Manually</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="use_action_appearance">False</property>
- <property name="use_underline">True</property>
- <property name="xalign">0</property>
- <property name="active">True</property>
- <property name="draw_indicator">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkRadioButton" id="automaticTimeRadio">
- <property name="label" translatable="yes">_Automatically from the Internet</property>
- <property name="visible">True</property>
- <property name="sensitive">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="use_action_appearance">False</property>
- <property name="use_underline">True</property>
- <property name="xalign">0</property>
- <property name="draw_indicator">True</property>
- <property name="group">manualTimeRadio</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="right_attach">2</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="label9">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">1</property>
- <property name="label" translatable="yes">Set the time:</property>
- </object>
- <packing>
- <property name="x_options">GTK_FILL</property>
- <property name="y_options">GTK_FILL</property>
- </packing>
- </child>
- <child>
- <object class="GtkHBox" id="manualOptions">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="spacing">12</property>
- <child>
- <object class="GtkHBox" id="hbox8">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="spacing">6</property>
- <child>
- <object class="GtkSpinButton" id="timeSpinner">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="invisible_char">•</property>
- <property name="width_chars">11</property>
- <property name="xalign">1</property>
- <property name="invisible_char_set">True</property>
- <property name="adjustment">timeAdjustment</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkHBox" id="hbox9">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="spacing">6</property>
- <child>
- <object class="GtkLabel" id="label10">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">0</property>
- <property name="label" translatable="yes">_Date:</property>
- <property name="use_underline">True</property>
- <property name="mnemonic_widget">dateSpinner</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkSpinButton" id="dateSpinner">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="invisible_char">•</property>
- <property name="width_chars">11</property>
- <property name="xalign">1</property>
- <property name="invisible_char_set">True</property>
- <property name="adjustment">dateAdjustment</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="right_attach">2</property>
- <property name="top_attach">1</property>
- <property name="bottom_attach">2</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="label11">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">1</property>
- <property name="label" translatable="yes">Tim_e:</property>
- <property name="use_underline">True</property>
- <property name="mnemonic_widget">timeSpinner</property>
- </object>
- <packing>
- <property name="top_attach">1</property>
- <property name="bottom_attach">2</property>
- <property name="x_options">GTK_FILL</property>
- <property name="y_options">GTK_FILL</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="pack_type">end</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <placeholder/>
- </child>
- </object>
- </child>
- <child type="tab">
- <object class="GtkLabel" id="label1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xpad">1</property>
- <property name="label" translatable="yes">_Time &amp; Date</property>
- <property name="use_underline">True</property>
- </object>
- <packing>
- <property name="tab_fill">False</property>
- </packing>
- </child>
- <child>
- <object class="GtkAlignment" id="alignment1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="yalign">0</property>
- <property name="yscale">0</property>
- <child>
- <object class="GtkVBox" id="clockBox">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="border_width">12</property>
- <property name="spacing">12</property>
- <child>
- <object class="GtkCheckButton" id="showClockCheck">
- <property name="label" translatable="yes">_Show a clock in the menu bar</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="use_action_appearance">False</property>
- <property name="use_underline">True</property>
- <property name="xalign">0</property>
- <property name="draw_indicator">True</property>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkHBox" id="clockOptions">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="homogeneous">True</property>
- <child>
- <object class="GtkVBox" id="vbox2">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="spacing">6</property>
- <child>
- <object class="GtkLabel" id="label3">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">0</property>
- <property name="label" translatable="yes">In the clock, show:</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkCheckButton" id="showWeekdayCheck">
- <property name="label" translatable="yes">_Weekday</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="use_action_appearance">False</property>
- <property name="use_underline">True</property>
- <property name="xalign">0</property>
- <property name="draw_indicator">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkCheckButton" id="showDateAndMonthCheck">
- <property name="label" translatable="yes">_Date and month</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="use_action_appearance">False</property>
- <property name="use_underline">True</property>
- <property name="xalign">0</property>
- <property name="draw_indicator">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">2</property>
- </packing>
- </child>
- <child>
- <object class="GtkAlignment" id="showYearAlignment">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">0</property>
- <property name="yalign">0</property>
- <property name="yscale">0</property>
- <property name="left_padding">24</property>
- <child>
- <object class="GtkVBox" id="showYearVbox">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="spacing">6</property>
- <child>
- <object class="GtkCheckButton" id="showYearCheck">
- <property name="label" translatable="yes">_Year</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="use_action_appearance">False</property>
- <property name="use_underline">True</property>
- <property name="xalign">0</property>
- <property name="draw_indicator">True</property>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- </object>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">3</property>
- </packing>
- </child>
- <child>
- <object class="GtkVBox" id="vbox5">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <child>
- <object class="GtkRadioButton" id="show12HourRadio">
- <property name="label" translatable="yes">_12-hour time</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="use_action_appearance">False</property>
- <property name="use_underline">True</property>
- <property name="xalign">0</property>
- <property name="active">True</property>
- <property name="draw_indicator">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkRadioButton" id="show24HourRadio">
- <property name="label" translatable="yes">_24-hour time</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="use_action_appearance">False</property>
- <property name="use_underline">True</property>
- <property name="xalign">0</property>
- <property name="draw_indicator">True</property>
- <property name="group">show12HourRadio</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">4</property>
- </packing>
- </child>
- <child>
- <object class="GtkCheckButton" id="showSecondsCheck">
- <property name="label" translatable="yes">Seco_nds</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="use_action_appearance">False</property>
- <property name="use_underline">True</property>
- <property name="xalign">0</property>
- <property name="draw_indicator">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">5</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkVBox" id="vbox3">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="spacing">6</property>
- <child>
- <object class="GtkLabel" id="label4">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">0</property>
- <property name="label" translatable="yes">In the clock’s menu, show:</property>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkCheckButton" id="showCalendarCheck">
- <property name="label" translatable="yes">_Monthly calendar</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="use_action_appearance">False</property>
- <property name="use_underline">True</property>
- <property name="xalign">0</property>
- <property name="draw_indicator">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkAlignment" id="calendarOptions">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">0</property>
- <property name="yalign">0</property>
- <property name="yscale">0</property>
- <property name="left_padding">24</property>
- <child>
- <object class="GtkVBox" id="vbox4">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="spacing">6</property>
- <child>
- <object class="GtkCheckButton" id="includeWeekNumbersCheck">
- <property name="label" translatable="yes">Include week num_bers</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="use_action_appearance">False</property>
- <property name="use_underline">True</property>
- <property name="xalign">0</property>
- <property name="draw_indicator">True</property>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- </object>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">2</property>
- </packing>
- </child>
- <child>
- <object class="GtkCheckButton" id="showEventsCheck">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="use_action_appearance">False</property>
- <property name="use_underline">True</property>
- <property name="xalign">0</property>
- <property name="draw_indicator">True</property>
- <child>
- <object class="GtkLabel" id="label12">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">0</property>
- <property name="label" translatable="yes">Coming _events from Evolution Calendar</property>
- <property name="use_underline">True</property>
- <property name="wrap">True</property>
- <property name="mnemonic_widget">showEventsCheck</property>
- </object>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">3</property>
- </packing>
- </child>
- <child>
- <object class="GtkCheckButton" id="showDetectedCheck">
- <property name="label" translatable="yes">Time in _auto-detected location</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="use_action_appearance">False</property>
- <property name="use_underline">True</property>
- <property name="xalign">0</property>
- <property name="draw_indicator">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">4</property>
- </packing>
- </child>
- <child>
- <object class="GtkCheckButton" id="showLocationsCheck">
- <property name="label" translatable="yes">Time in _other locations</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="use_action_appearance">False</property>
- <property name="use_underline">True</property>
- <property name="xalign">0</property>
- <property name="draw_indicator">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">5</property>
- </packing>
- </child>
- <child>
- <object class="GtkAlignment" id="alignment2">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">0</property>
- <property name="yalign">0</property>
- <property name="xscale">0</property>
- <property name="yscale">0</property>
- <property name="left_padding">24</property>
- <child>
- <object class="GtkButton" id="locationsButton">
- <property name="label" translatable="yes">Choose _Locations…</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="use_action_appearance">False</property>
- <property name="use_underline">True</property>
- <property name="xalign">0</property>
- </object>
- </child>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">6</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
- </child>
- </object>
- <packing>
- <property name="position">1</property>
- <property name="tab_fill">False</property>
- </packing>
- </child>
- <child type="tab">
- <object class="GtkLabel" id="label2">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes">_Clock</property>
- <property name="use_underline">True</property>
- </object>
- <packing>
- <property name="position">1</property>
- <property name="tab_fill">False</property>
- </packing>
- </child>
- </object>
- </child>
- </object>
- <object class="GtkSizeGroup" id="timeSizeGroup">
- <widgets>
- <widget name="label5"/>
- <widget name="label9"/>
- <widget name="label11"/>
- </widgets>
- </object>
-</interface>
diff --git a/data/gnome-indicator-datetime-panel.desktop.in b/data/gnome-indicator-datetime-panel.desktop.in
deleted file mode 100644
index b7202b4..0000000
--- a/data/gnome-indicator-datetime-panel.desktop.in
+++ /dev/null
@@ -1,13 +0,0 @@
-[Desktop Entry]
-Version=1.0
-_Name=Time & Date
-_Comment=Change your clock and date settings
-Icon=preferences-system-time
-TryExec=gnome-control-center
-Exec=gnome-control-center indicator-datetime
-StartupNotify=true
-Type=Application
-Categories=GNOME;GTK;Utility;DesktopSettings;Settings;X-GNOME-SystemSettings;X-GNOME-Settings-Panel;
-X-GNOME-Settings-Panel=indicator-datetime
-OnlyShowIn=Unity;
-X-Ubuntu-Gettext-Domain=indicator-datetime
diff --git a/debian/changelog b/debian/changelog
index 5ceaecf..097df7e 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,52 @@
+indicator-datetime (13.10.0+14.04.20140217-0ubuntu1) trusty; urgency=low
+
+ [ Robert Ancell ]
+ * Move date/time panel into unity-control-center
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Mon, 17 Feb 2014 12:53:26 +0000
+
+indicator-datetime (13.10.0+14.04.20140205-0ubuntu1) trusty; urgency=low
+
+ [ Charles Kerr ]
+ * Don't load the alarm icon until it's needed s.t. we don't hit click
+ unnecessarily when starting up in the greeter.
+
+ [ Sebastien Bacher ]
+ * use the correct naming under unity-control-center
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Wed, 05 Feb 2014 18:22:52 +0000
+
+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 ]
+ * Support both gnome-control-center and unity-control-center. (LP:
+ #1257505)
+ * Run the unity-control-center panel under Unity. Rename the unity
+ panel to just "datetime".
+
+ [ Ubuntu daily release ]
+ * Automatic snapshot from revision 294
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 24 Jan 2014 11:00:08 +0000
+
indicator-datetime (13.10.0+14.04.20131217-0ubuntu1) trusty; urgency=low
[ Charles Kerr ]
diff --git a/debian/control b/debian/control
index d9d4f56..84f76ca 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),
@@ -14,19 +16,19 @@ Build-Depends: cmake,
libgtest-dev,
libglib2.0-dev (>= 2.35.4),
libnotify-dev (>= 0.7.6),
+ libcanberra-dev,
libido3-0.1-dev (>= 0.2.90),
libgeoclue-dev (>= 0.12.0),
libecal1.2-dev (>= 3.5),
libical-dev (>= 1.0),
libgtk-3-dev (>= 3.1.4),
libcairo2-dev (>= 1.10),
- libjson-glib-dev,
libpolkit-gobject-1-dev,
libedataserver1.2-dev (>= 3.5),
libgconf2-dev (>= 2.31),
- libgnome-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
@@ -45,22 +47,9 @@ Depends: ${shlibs:Depends},
systemd-shim,
Recommends: indicator-applet | indicator-renderer,
evolution-data-server,
- gnome-control-center-datetime | ubuntu-system-settings,
+ unity-control-center (>= 14.04.3) | ubuntu-system-settings,
Suggests: click,
Conflicts: indicator-datetime (<< 13.10.0)
Replaces: indicator-datetime (<< 13.10.0)
Description: Simple clock
A simple clock appearing in the indicator bar
-
-Package: gnome-control-center-datetime
-Architecture: any
-Depends: ${shlibs:Depends},
- ${misc:Depends},
- indicator-datetime (=${binary:Version}),
- gnome-control-center,
-Conflicts: indicator-datetime (<< 13.10.0)
-Replaces: indicator-datetime (<< 13.10.0)
-Description: Clock settings in the GNOME Control Center
- A module to get date time and clock settings in the GNOME control
- center.
-
diff --git a/debian/gnome-control-center-datetime.install b/debian/gnome-control-center-datetime.install
deleted file mode 100644
index 52a8c70..0000000
--- a/debian/gnome-control-center-datetime.install
+++ /dev/null
@@ -1,3 +0,0 @@
-usr/lib/*/control-center-1/panels/*
-usr/share/indicator-datetime/*
-usr/share/applications/*
diff --git a/debian/indicator-datetime.install b/debian/indicator-datetime.install
deleted file mode 100644
index 1085d5f..0000000
--- a/debian/indicator-datetime.install
+++ /dev/null
@@ -1,6 +0,0 @@
-usr/share/glib-2.0/schemas/*
-usr/share/upstart/sessions/*
-usr/share/unity/indicators/*
-usr/lib/*/indicator-datetime/indicator-datetime-service
-usr/share/locale/*
-etc/xdg/autostart/*
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-watcher.h b/include/datetime/clock-watcher.h
new file mode 100644
index 0000000..e93b468
--- /dev/null
+++ b/include/datetime/clock-watcher.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright 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:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef INDICATOR_DATETIME_CLOCK_WATCHER_H
+#define INDICATOR_DATETIME_CLOCK_WATCHER_H
+
+#include <datetime/state.h>
+#include <datetime/appointment.h>
+
+#include <core/signal.h>
+
+#include <memory>
+#include <set>
+#include <string>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+
+/**
+ * \brief Watches the clock and appointments to notify when an
+ * appointment's time is reached.
+ */
+class ClockWatcher
+{
+public:
+ ClockWatcher() =default;
+ virtual ~ClockWatcher() =default;
+ virtual core::Signal<const Appointment&>& alarm_reached() = 0;
+};
+
+
+/**
+ * \brief A #ClockWatcher implementation
+ */
+class ClockWatcherImpl: public ClockWatcher
+{
+public:
+ ClockWatcherImpl(const std::shared_ptr<const State>& state);
+ ~ClockWatcherImpl() =default;
+ core::Signal<const Appointment&>& alarm_reached();
+
+private:
+ void pulse();
+ std::set<std::string> m_triggered;
+ std::shared_ptr<const State> m_state;
+ core::Signal<const Appointment&> m_alarm_reached;
+};
+
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
+
+#endif // INDICATOR_DATETIME_CLOCK_WATCHER_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..b054a1f
--- /dev/null
+++ b/include/datetime/date-time.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2013 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#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;
+ DateTime add_full(int years, int months, int days, int hours, int minutes, double seconds) 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;
+ double seconds() const;
+ int64_t to_unix() const;
+
+ bool operator<(const DateTime& that) 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..a99f611
--- /dev/null
+++ b/include/datetime/planner-eds.h
@@ -0,0 +1,50 @@
+/*
+ * 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/clock.h>
+#include <datetime/planner.h>
+
+#include <memory> // shared_ptr, unique_ptr
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/**
+ * \brief Planner which uses EDS as its backend
+ */
+class PlannerEds: public Planner
+{
+public:
+ PlannerEds(const std::shared_ptr<Clock>& clock);
+ 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/snap.h b/include/datetime/snap.h
new file mode 100644
index 0000000..a493772
--- /dev/null
+++ b/include/datetime/snap.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright 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:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef INDICATOR_DATETIME_SNAP_H
+#define INDICATOR_DATETIME_SNAP_H
+
+#include <datetime/appointment.h>
+
+#include <memory>
+#include <functional>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/**
+ * \brief Pops up Snap Decisions for appointments
+ */
+class Snap
+{
+public:
+ Snap();
+ virtual ~Snap();
+
+ typedef std::function<void(const Appointment&)> appointment_func;
+ void operator()(const Appointment& appointment,
+ appointment_func show,
+ appointment_func dismiss);
+};
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
+
+#endif // INDICATOR_DATETIME_SNAP_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..a67c01a
--- /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 set_filename(const std::string& filename);
+ static void on_file_changed(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/CMakeLists.txt b/panel/CMakeLists.txt
deleted file mode 100644
index b3fcc7b..0000000
--- a/panel/CMakeLists.txt
+++ /dev/null
@@ -1,25 +0,0 @@
-set (PANEL_LIB "indicator-datetime")
-
-add_definitions (-DPKGDATADIR="${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME}")
-
-add_library (${PANEL_LIB} SHARED
- datetime-prefs.c
- datetime-prefs-locations.c
- datetime-prefs-locations.h
- ${CMAKE_SOURCE_DIR}/src/utils.c
- ${CMAKE_SOURCE_DIR}/src/utils.h
- ${CMAKE_SOURCE_DIR}/src/settings-shared.h)
-
-include_directories (${PANEL_DEPS_INCLUDE_DIRS})
-
-link_directories (${PANEL_DEPS_LIBRARY_DIRS})
-
-set_property (TARGET ${PANEL_LIB}
- APPEND_STRING PROPERTY COMPILE_FLAGS
- " -g ${CC_WARNING_ARGS} ${GCOV_FLAGS}")
-
-target_link_libraries (${PANEL_LIB} ${PANEL_DEPS_LIBRARIES} ${GCOV_LIBS})
-
-install (TARGETS ${PANEL_LIB}
- DESTINATION ${CMAKE_INSTALL_LIBDIR}/control-center-1/panels)
-
diff --git a/panel/datetime-prefs-locations.c b/panel/datetime-prefs-locations.c
deleted file mode 100644
index 54ab8f4..0000000
--- a/panel/datetime-prefs-locations.c
+++ /dev/null
@@ -1,683 +0,0 @@
-/* -*- Mode: C; coding: utf-8; indent-tabs-mode: nil; tab-width: 2 -*-
-
-A dialog for setting time and date preferences.
-
-Copyright 2011 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/>.
-*/
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include <stdlib.h>
-#include <time.h> /* time_t */
-#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"
-
-#define DATETIME_DIALOG_UI_FILE PKGDATADIR "/datetime-dialog.ui"
-
-#define COL_NAME 0
-#define COL_TIME 1
-#define COL_ZONE 2
-#define COL_VISIBLE_NAME 3
-#define COL_ICON 4
-
-static gboolean update_times (GtkWidget * dlg);
-static void save_when_idle (GtkWidget * dlg);
-
-/***
-**** Sorting
-***/
-
-/**
- * A temporary struct used for sorting
- */
-struct TimeLocation
-{
- gchar * collated_name;
- gint pos;
- gint32 offset;
-};
-
-static struct TimeLocation*
-time_location_new (const char * zone, const char * name, int pos, time_t now)
-{
- struct TimeLocation * loc = g_new (struct TimeLocation, 1);
- GTimeZone * tz = g_time_zone_new (zone);
- const gint interval = g_time_zone_find_interval (tz, G_TIME_TYPE_UNIVERSAL, now);
- loc->offset = g_time_zone_get_offset (tz, interval);
- loc->collated_name = g_utf8_collate_key (name, -1);
- loc->pos = pos;
- g_time_zone_unref (tz);
- return loc;
-}
-
-static void
-time_location_free (struct TimeLocation * loc)
-{
- g_free (loc->collated_name);
- g_free (loc);
-}
-
-static GSList*
-time_location_array_new_from_model (GtkTreeModel * model)
-{
- int pos = 0;
- GtkTreeIter iter;
- GSList * list = NULL;
- const time_t now = time (NULL);
-
- if (gtk_tree_model_get_iter_first (model, &iter)) do
- {
- gchar * zone = NULL;
- gchar * name = NULL;
-
- gtk_tree_model_get (model, &iter,
- COL_ZONE, &zone,
- COL_VISIBLE_NAME, &name,
- -1);
-
- if (zone && name)
- list = g_slist_prepend (list, time_location_new (zone, name, pos++, now));
-
- g_free (name);
- g_free (zone);
- }
- while (gtk_tree_model_iter_next (model, &iter));
-
- return g_slist_reverse (list);
-}
-
-static void
-handle_sort(GtkWidget * button G_GNUC_UNUSED,
- GtkTreeView * tree_view,
- GCompareFunc compare)
-{
- GtkTreeModel * model = gtk_tree_view_get_model (tree_view);
- GSList * l;
- GSList * list = g_slist_sort (time_location_array_new_from_model(model), compare);
-
- gint i;
- gint * reorder = g_new (gint, g_slist_length(list));
- for (i=0, l=list; l!=NULL; l=l->next, i++)
- reorder[i] = ((struct TimeLocation*)l->data)->pos;
- gtk_list_store_reorder (GTK_LIST_STORE(model), reorder);
-
- g_free (reorder);
- g_slist_free_full (list, (GDestroyNotify)time_location_free);
-}
-
-static gint
-time_location_compare_by_name (gconstpointer ga, gconstpointer gb)
-{
- const struct TimeLocation * a = ga;
- const struct TimeLocation * b = gb;
- int ret = g_strcmp0 (a->collated_name, b->collated_name); /* primary key */
- if (!ret)
- ret = a->offset - b->offset; /* secondary key */
- return ret;
-}
-static void
-handle_sort_by_name (GtkWidget * button, GtkTreeView * tree_view)
-{
- handle_sort (button, tree_view, time_location_compare_by_name);
-}
-
-static gint
-time_location_compare_by_time (gconstpointer ga, gconstpointer gb)
-{
- const struct TimeLocation * a = ga;
- const struct TimeLocation * b = gb;
- int ret = a->offset - b->offset; /* primary key */
- if (!ret)
- ret = g_strcmp0 (a->collated_name, b->collated_name); /* secondary key */
- return ret;
-}
-static void
-handle_sort_by_time (GtkWidget * button, GtkTreeView * tree_view)
-{
- handle_sort (button, tree_view, time_location_compare_by_time);
-}
-
-static gboolean
-time_location_list_test_sorted (GSList * list, GCompareFunc compare)
-{
- GSList * l;
- for (l=list; l!=NULL && l->next!=NULL; l=l->next)
- if (compare(l->data, l->next->data) > 0)
- return FALSE;
- return TRUE;
-}
-static void
-location_model_test_sorted (GtkTreeModel * model, gboolean * is_sorted_by_name, gboolean * is_sorted_by_time)
-{
- GSList * list = time_location_array_new_from_model(model);
- *is_sorted_by_name = time_location_list_test_sorted (list, time_location_compare_by_name);
- *is_sorted_by_time = time_location_list_test_sorted (list, time_location_compare_by_time);
- g_slist_free_full (list, (GDestroyNotify)time_location_free);
-}
-
-/***
-****
-***/
-
-static void
-handle_add (GtkWidget * button G_GNUC_UNUSED, GtkTreeView * tree)
-{
- GtkListStore * store = GTK_LIST_STORE (gtk_tree_view_get_model (tree));
-
- GtkTreeIter iter;
- gtk_list_store_append (store, &iter);
-
- GtkTreePath * path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
- gtk_tree_view_set_cursor (tree, path, gtk_tree_view_get_column (tree, 0), TRUE);
- gtk_tree_path_free (path);
-}
-
-static void
-handle_remove (GtkWidget * button G_GNUC_UNUSED, GtkTreeView * tree)
-{
- GtkListStore * store = GTK_LIST_STORE (gtk_tree_view_get_model (tree));
- GtkTreeSelection * selection = gtk_tree_view_get_selection (tree);
-
- GList * paths = gtk_tree_selection_get_selected_rows (selection, NULL);
-
- /* Convert all paths to iters so we can safely delete multiple paths. For a
- GtkListStore, iters persist past model changes. */
- GList * tree_iters = NULL;
- GList * iter;
- for (iter = paths; iter; iter = iter->next) {
- GtkTreeIter * tree_iter = g_new(GtkTreeIter, 1);
- if (gtk_tree_model_get_iter (GTK_TREE_MODEL (store), tree_iter, (GtkTreePath *)iter->data)) {
- tree_iters = g_list_prepend (tree_iters, tree_iter);
- }
- gtk_tree_path_free (iter->data);
- }
- g_list_free (paths);
-
- // Find the next item to select
- GtkTreeIter *last_selected = g_list_nth_data(tree_iters, 0);
- GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL (store), last_selected);
- GtkTreeIter titer;
- if (!gtk_tree_model_get_iter(GTK_TREE_MODEL (store), &titer, path)) {
- g_debug("Failed to get last selected iter from path");
- last_selected = NULL;
- } else {
- if (!gtk_tree_model_iter_next(GTK_TREE_MODEL (store), &titer)) {
- if (gtk_tree_path_prev(path)) {
- if (!gtk_tree_model_get_iter(GTK_TREE_MODEL (store), &titer, path)) {
- g_debug("Failed to get iter from path");
- last_selected = NULL;
- } else {
- last_selected = &titer;
- }
- } else {
- g_debug("handle_remove: Failed to find another location to select (assume single selected)");
- last_selected = NULL;
- }
- } else {
- g_debug("Got next item in model");
- last_selected = &titer;
- }
- }
-
- if (last_selected) {
- gboolean clear = TRUE;
- path = gtk_tree_model_get_path(GTK_TREE_MODEL (store), last_selected);
-
- // Step over the path to find an item which isn't in the delete list
- if (g_list_length(tree_iters) > 1) {
- for (iter = tree_iters; iter; iter = iter->next) {
- GtkTreePath *ipath = gtk_tree_model_get_path(GTK_TREE_MODEL (store), (GtkTreeIter *)iter->data);
- if (gtk_tree_path_compare(path, ipath) == 0) {
- clear = FALSE;
- break;
- }
- }
- while (clear == FALSE) {
- if (gtk_tree_path_prev(path)) {
- clear = TRUE;
- for (iter = tree_iters; iter; iter = iter->next) {
- GtkTreePath *ipath = gtk_tree_model_get_path(GTK_TREE_MODEL (store), (GtkTreeIter *)iter->data);
- if (gtk_tree_path_compare(path, ipath) == 0) {
- clear = FALSE;
- break;
- }
- }
- if (clear) {
- if (!gtk_tree_model_get_iter(GTK_TREE_MODEL (store), &titer, path)) {
- g_debug("Failed to get iter from path");
- last_selected = NULL;
- } else {
- last_selected = &titer;
- }
- }
- } else {
- last_selected = NULL;
- break;
- }
- }
- }
- }
-
- /* Now delete each iterator */
- for (iter = tree_iters; iter; iter = iter->next) {
- gtk_list_store_remove (store, (GtkTreeIter *)iter->data);
- g_free (iter->data);
- }
- g_list_free (tree_iters);
-
- if (last_selected)
- gtk_tree_selection_select_iter(selection, last_selected);
-}
-
-static void
-handle_edit (GtkCellRendererText * renderer G_GNUC_UNUSED,
- gchar * path,
- gchar * new_text,
- GtkListStore * store)
-{
- GtkTreeIter iter;
-
- // Manual user edits are always wrong (unless they are undoing a previous
- // edit), so we set the error icon here if needed. Common way to get to
- // this code path is to lose entry focus.
- if (gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (store), &iter, path)) {
- gchar * name;
- gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, COL_NAME, &name, -1);
- gboolean correct = g_strcmp0 (name, new_text) == 0;
- g_free (name);
-
- gtk_list_store_set (store, &iter,
- COL_VISIBLE_NAME, new_text,
- COL_ICON, correct ? NULL : GTK_STOCK_DIALOG_ERROR,
- -1);
- }
-}
-
-static gboolean
-timezone_selected (GtkEntryCompletion * widget, GtkTreeModel * model,
- GtkTreeIter * iter, GtkWidget * dlg)
-{
- gchar * zone = NULL;
- gchar * name = NULL;
-
- gtk_tree_model_get (model, iter,
- CC_TIMEZONE_COMPLETION_ZONE, &zone,
- CC_TIMEZONE_COMPLETION_NAME, &name,
- -1);
-
- /* if no explicit timezone, try to determine one from latlon */
- if (!zone || !*zone)
- {
- gchar * strlat = NULL;
- gchar * strlon = NULL;
- gdouble lat = 0;
- gdouble lon = 0;
-
- gtk_tree_model_get (model, iter,
- CC_TIMEZONE_COMPLETION_LATITUDE, &strlat,
- CC_TIMEZONE_COMPLETION_LONGITUDE, &strlon,
- -1);
-
- if (strlat && *strlat) lat = g_ascii_strtod(strlat, NULL);
- if (strlon && *strlon) lon = g_ascii_strtod(strlon, NULL);
-
- CcTimezoneMap * tzmap = CC_TIMEZONE_MAP (g_object_get_data (G_OBJECT (widget), "tzmap"));
- g_free (zone);
- zone = g_strdup (cc_timezone_map_get_timezone_at_coords (tzmap, lon, lat));
-
- g_free (strlat);
- g_free (strlon);
- }
-
- GtkListStore * store = GTK_LIST_STORE (g_object_get_data (G_OBJECT (widget), "store"));
- GtkTreeIter * store_iter = (GtkTreeIter *)g_object_get_data (G_OBJECT (widget), "store_iter");
- if (store != NULL && store_iter != NULL) {
- gtk_list_store_set (store, store_iter,
- COL_VISIBLE_NAME, name,
- COL_ICON, NULL,
- COL_NAME, name,
- COL_ZONE, zone, -1);
- }
-
- update_times (dlg);
-
- /* cleanup */
- g_free (name);
- g_free (zone);
-
- return FALSE; // Do normal action too
-}
-
-static gboolean
-query_tooltip (GtkTreeView * tree, gint x, gint y, gboolean keyboard_mode,
- GtkTooltip * tooltip, GtkCellRenderer * cell)
-{
- GtkTreeModel * model;
- GtkTreeIter iter;
- if (!gtk_tree_view_get_tooltip_context (tree, &x, &y, keyboard_mode,
- &model, NULL, &iter))
- return FALSE;
-
- const gchar * icon;
- gtk_tree_model_get (model, &iter, COL_ICON, &icon, -1);
- if (icon == NULL)
- return FALSE;
-
- GtkTreeViewColumn * col = gtk_tree_view_get_column (tree, 0);
- gtk_tree_view_set_tooltip_cell (tree, tooltip, NULL, col, cell);
- gtk_tooltip_set_text (tooltip, _("You need to complete this location for it to appear in the menu."));
- return TRUE;
-}
-
-static void
-handle_edit_started (GtkCellRendererText * renderer G_GNUC_UNUSED,
- GtkCellEditable * editable,
- gchar * path,
- CcTimezoneCompletion * completion)
-{
- if (GTK_IS_ENTRY (editable)) {
- GtkEntry *entry = GTK_ENTRY (editable);
- cc_timezone_completion_watch_entry (completion, entry);
-
- GtkListStore * store = GTK_LIST_STORE (g_object_get_data (G_OBJECT (completion), "store"));
- GtkTreeIter * store_iter = g_new(GtkTreeIter, 1);
- if (gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (store), store_iter, path)) {
- g_object_set_data_full (G_OBJECT (completion), "store_iter", store_iter, g_free);
- }
- }
-}
-
-static gboolean
-update_times (GtkWidget * dlg)
-{
- /* For each entry, check zone in column 2 and set column 1 to it's time */
- CcTimezoneCompletion * completion = CC_TIMEZONE_COMPLETION (g_object_get_data (G_OBJECT (dlg), "completion"));
- GtkListStore * store = GTK_LIST_STORE (g_object_get_data (G_OBJECT (completion), "store"));
- GObject * cell = G_OBJECT (g_object_get_data (G_OBJECT (completion), "name-cell"));
-
- gboolean editing;
- g_object_get (cell, "editing", &editing, NULL);
- if (editing) { /* No updates while editing, it cancels the edit */
- return TRUE;
- }
-
- 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 ();
- do {
- gchar * strzone;
-
- gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, COL_ZONE, &strzone, -1);
-
- 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 * time_str = g_date_time_format (now_tz, format);
- gchar * old_time_str;
-
- gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, COL_TIME, &old_time_str, -1);
- if (g_strcmp0 (old_time_str, time_str))
- gtk_list_store_set (store, &iter, COL_TIME, time_str, -1);
-
- g_free (old_time_str);
- g_free (time_str);
- g_free (format);
- g_date_time_unref (now_tz);
- g_time_zone_unref (tz);
- }
- g_free (strzone);
- } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter));
- g_date_time_unref (now);
- }
-
- g_object_unref (settings);
-
- g_signal_handlers_unblock_by_func (store, save_when_idle, dlg);
-
- return TRUE;
-}
-
-static void
-fill_from_settings (GObject * store, GSettings * conf)
-{
- gchar ** locations = g_settings_get_strv (conf, SETTINGS_LOCATIONS_S);
-
- gtk_list_store_clear (GTK_LIST_STORE (store));
-
- gchar ** striter;
- GtkTreeIter iter;
- for (striter = locations; *striter; ++striter) {
- gchar * zone, * name;
- split_settings_location (*striter, &zone, &name);
-
- gtk_list_store_append (GTK_LIST_STORE (store), &iter);
- gtk_list_store_set (GTK_LIST_STORE (store), &iter,
- COL_VISIBLE_NAME, name,
- COL_ICON, NULL,
- COL_NAME, name,
- COL_ZONE, zone, -1);
-
- g_free (zone);
- g_free (name);
- }
-
- g_strfreev (locations);
-}
-
-static void
-save_to_settings (GObject * store, GSettings * conf)
-{
- gboolean empty = TRUE;
- GVariantBuilder builder;
- g_variant_builder_init (&builder, G_VARIANT_TYPE_STRING_ARRAY);
-
- GtkTreeIter iter;
- if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) {
- GString * gstr = g_string_new (NULL);
- do {
- gchar * strname;
- gchar * strzone;
- gtk_tree_model_get (GTK_TREE_MODEL (store), &iter,
- COL_NAME, &strname,
- COL_ZONE, &strzone,
- -1);
- if (strzone && *strzone && strname && *strname) {
- g_string_printf (gstr, "%s %s", strzone, strname);
- g_variant_builder_add (&builder, "s", gstr->str);
- empty = FALSE;
- }
- g_free (strname);
- g_free (strzone);
- } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter));
- g_string_free (gstr, TRUE);
- }
-
- if (empty) {
- /* Empty list */
- g_variant_builder_clear (&builder);
- g_settings_set_strv (conf, SETTINGS_LOCATIONS_S, NULL);
- }
- else {
- g_settings_set_value (conf, SETTINGS_LOCATIONS_S, g_variant_builder_end (&builder));
- }
-}
-
-static gboolean
-save_now (GtkWidget *dlg)
-{
- GSettings * conf = G_SETTINGS (g_object_get_data (G_OBJECT (dlg), "conf"));
- GObject * completion = G_OBJECT (g_object_get_data (G_OBJECT (dlg), "completion"));
- GObject * store = G_OBJECT (g_object_get_data (completion, "store"));
-
- save_to_settings (store, conf);
-
- g_object_set_data (G_OBJECT (dlg), "save-id", GINT_TO_POINTER(0));
-
- return FALSE;
-}
-
-static void
-save_when_idle (GtkWidget *dlg)
-{
- guint save_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dlg), "save-id"));
-
- if (save_id == 0) {
- save_id = g_idle_add ((GSourceFunc)save_now, dlg);
- g_object_set_data (G_OBJECT (dlg), "save-id", GINT_TO_POINTER(save_id));
- }
-}
-
-static void
-dialog_closed (GtkWidget * dlg, GObject * store G_GNUC_UNUSED)
-{
- /* Cleanup a tad */
- guint time_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dlg), "time-id"));
- g_source_remove (time_id);
-
- guint save_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dlg), "save-id"));
- if (save_id > 0)
- g_source_remove (save_id);
-}
-
-static void
-selection_changed (GtkTreeSelection * selection, GtkWidget * remove_button)
-{
- gint count = gtk_tree_selection_count_selected_rows (selection);
- gtk_widget_set_sensitive (remove_button, count > 0);
-}
-
-static void
-update_button_sensitivity (GtkWidget * dlg)
-{
- GObject * odlg = G_OBJECT(dlg);
- GObject * completion = g_object_get_data(odlg, "completion");
- GtkTreeModel * model = GTK_TREE_MODEL (g_object_get_data (completion, "store"));
- gboolean is_sorted_by_name;
- gboolean is_sorted_by_time;
- location_model_test_sorted (model, &is_sorted_by_name, &is_sorted_by_time);
- gtk_widget_set_sensitive (GTK_WIDGET(g_object_get_data(odlg, "sortByNameButton")), !is_sorted_by_name);
- gtk_widget_set_sensitive (GTK_WIDGET(g_object_get_data(odlg, "sortByTimeButton")), !is_sorted_by_time);
-}
-
-static void
-model_changed (GtkWidget * dlg)
-{
- update_button_sensitivity (dlg);
- save_when_idle (dlg);
-}
-
-GtkWidget *
-datetime_setup_locations_dialog (CcTimezoneMap * map)
-{
- GError * error = NULL;
- GtkBuilder * builder = gtk_builder_new ();
- gtk_builder_set_translation_domain (builder, GETTEXT_PACKAGE);
- gtk_builder_add_from_file (builder, DATETIME_DIALOG_UI_FILE, &error);
- if (error != NULL) {
- /* We have to abort, we can't continue without the ui file */
- g_error ("Could not load ui file %s: %s", DATETIME_DIALOG_UI_FILE, error->message);
- g_error_free (error);
- return NULL;
- }
-
- GSettings * conf = g_settings_new (SETTINGS_INTERFACE);
-
-#define WIG(name) GTK_WIDGET (gtk_builder_get_object (builder, name))
-
- GtkWidget * dlg = WIG ("locationsDialog");
- GtkWidget * tree = WIG ("locationsView");
- GObject * store = gtk_builder_get_object (builder, "locationsStore");
-
- /* Configure tree */
- CcTimezoneCompletion * completion = cc_timezone_completion_new ();
- g_object_set_data (G_OBJECT (completion), "tzmap", map);
- g_object_set_data (G_OBJECT (completion), "store", store);
- g_signal_connect (completion, "match-selected", G_CALLBACK (timezone_selected), dlg);
-
- GtkCellRenderer * cell = gtk_cell_renderer_text_new ();
- g_object_set (cell, "editable", TRUE, NULL);
- g_signal_connect (cell, "editing-started", G_CALLBACK (handle_edit_started), completion);
- g_signal_connect (cell, "edited", G_CALLBACK (handle_edit), store);
- gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree), -1,
- _("Location"), cell,
- "text", COL_VISIBLE_NAME, NULL);
- GtkTreeViewColumn * loc_col = gtk_tree_view_get_column (GTK_TREE_VIEW (tree), 0);
- gtk_tree_view_column_set_expand (loc_col, TRUE);
- g_object_set_data (G_OBJECT (completion), "name-cell", cell);
-
- cell = gtk_cell_renderer_pixbuf_new ();
- gtk_tree_view_column_pack_start (loc_col, cell, FALSE);
- gtk_tree_view_column_add_attribute (loc_col, cell, "icon-name", COL_ICON);
-
- gtk_widget_set_has_tooltip (tree, TRUE);
- g_signal_connect (tree, "query-tooltip", G_CALLBACK (query_tooltip), cell);
-
- cell = gtk_cell_renderer_text_new ();
- gtk_cell_renderer_set_alignment (cell, 1.0f, 0.5f);
- gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree), -1,
- _("Time"), cell,
- "text", COL_TIME, NULL);
-
- GtkTreeSelection * selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));
- gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
- g_signal_connect (selection, "changed", G_CALLBACK (selection_changed), WIG ("removeButton"));
- selection_changed (selection, WIG ("removeButton"));
-
- g_signal_connect (WIG ("addButton"), "clicked", G_CALLBACK (handle_add), tree);
- g_signal_connect (WIG ("removeButton"), "clicked", G_CALLBACK (handle_remove), tree);
-
- GtkWidget * w = WIG ("sortByNameButton");
- g_signal_connect (w, "clicked", G_CALLBACK (handle_sort_by_name), tree);
- g_object_set_data (G_OBJECT(dlg), "sortByNameButton", w);
-
- w = WIG ("sortByTimeButton");
- g_signal_connect (w, "clicked", G_CALLBACK (handle_sort_by_time), tree);
- g_object_set_data (G_OBJECT(dlg), "sortByTimeButton", w);
-
- fill_from_settings (store, conf);
- g_signal_connect_swapped (store, "row-deleted", G_CALLBACK (model_changed), dlg);
- g_signal_connect_swapped (store, "row-inserted", G_CALLBACK (model_changed), dlg);
- g_signal_connect_swapped (store, "row-changed", G_CALLBACK (model_changed), dlg);
- g_signal_connect_swapped (store, "rows-reordered", G_CALLBACK (model_changed), dlg);
- g_object_set_data_full (G_OBJECT (dlg), "conf", g_object_ref (conf), g_object_unref);
- g_object_set_data_full (G_OBJECT (dlg), "completion", completion, g_object_unref);
- g_signal_connect (dlg, "destroy", G_CALLBACK (dialog_closed), store);
-
- guint time_id = g_timeout_add_seconds (2, (GSourceFunc)update_times, dlg);
- g_object_set_data (G_OBJECT (dlg), "time-id", GINT_TO_POINTER(time_id));
- update_times (dlg);
-
-#undef WIG
-
- g_object_unref (conf);
- g_object_unref (builder);
-
- return dlg;
-}
-
diff --git a/panel/datetime-prefs-locations.h b/panel/datetime-prefs-locations.h
deleted file mode 100644
index 45d3b23..0000000
--- a/panel/datetime-prefs-locations.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/* -*- Mode: C; coding: utf-8; indent-tabs-mode: nil; tab-width: 2 -*-
-
-A dialog for setting time and date preferences.
-
-Copyright 2011 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_PREFS_LOCATIONS_H__
-#define __DATETIME_PREFS_LOCATIONS_H__
-
-#include <gtk/gtk.h>
-#include <timezonemap/cc-timezone-map.h>
-
-G_BEGIN_DECLS
-
-GtkWidget * datetime_setup_locations_dialog (CcTimezoneMap * map);
-
-G_END_DECLS
-
-#endif
diff --git a/panel/datetime-prefs.c b/panel/datetime-prefs.c
deleted file mode 100644
index 55456ac..0000000
--- a/panel/datetime-prefs.c
+++ /dev/null
@@ -1,863 +0,0 @@
-/* -*- Mode: C; coding: utf-8; indent-tabs-mode: nil; tab-width: 2 -*-
-
-A dialog for setting time and date preferences.
-
-Copyright 2011 Canonical Ltd.
-
-Authors:
- Ted Gould <ted@canonical.com>
- 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/>.
-*/
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include <stdlib.h>
-#include <libintl.h>
-#include <locale.h>
-#include <langinfo.h>
-#include <glib/gi18n-lib.h>
-#include <gdk/gdkkeysyms.h>
-#include <gtk/gtk.h>
-#include <polkit/polkit.h>
-#include <libgnome-control-center/cc-panel.h>
-#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"
-
-#define DATETIME_DIALOG_UI_FILE PKGDATADIR "/datetime-dialog.ui"
-
-#define INDICATOR_DATETIME_TYPE_PANEL indicator_datetime_panel_get_type()
-
-typedef struct _IndicatorDatetimePanel IndicatorDatetimePanel;
-typedef struct _IndicatorDatetimePanelPrivate IndicatorDatetimePanelPrivate;
-typedef struct _IndicatorDatetimePanelClass IndicatorDatetimePanelClass;
-
-struct _IndicatorDatetimePanel
-{
- CcPanel parent;
- IndicatorDatetimePanelPrivate * priv;
-};
-
-struct _IndicatorDatetimePanelPrivate
-{
- guint name_watch_id;
- GtkBuilder * builder;
- GDBusProxy * proxy;
- GtkWidget * auto_radio;
- GtkWidget * tz_entry;
- CcTimezoneMap * tzmap;
- GtkWidget * time_spin;
- GtkWidget * date_spin;
- guint save_time_id;
- gboolean user_edited_time;
- gboolean changing_time;
- GtkWidget * loc_dlg;
- GSettings * settings;
- CcTimezoneCompletion * completion;
-};
-
-struct _IndicatorDatetimePanelClass
-{
- CcPanelClass parent_class;
-};
-
-G_DEFINE_DYNAMIC_TYPE (IndicatorDatetimePanel, indicator_datetime_panel, CC_TYPE_PANEL)
-
-/* Turns the boolean property into a string gsettings */
-static GVariant *
-bind_hours_set (const GValue * value,
- const GVariantType * type G_GNUC_UNUSED,
- gpointer user_data)
-{
- const gchar * output = NULL;
- gboolean is_12hour_button = (gboolean)GPOINTER_TO_INT(user_data);
-
- if (g_value_get_boolean(value)) {
- /* Only do anything if we're setting active = true */
- output = is_12hour_button ? "12-hour" : "24-hour";
- } else {
- return NULL;
- }
-
- return g_variant_new_string (output);
-}
-
-/* Turns a string gsettings into a boolean property */
-static gboolean
-bind_hours_get (GValue * value, GVariant * variant, gpointer user_data)
-{
- const gchar * str = g_variant_get_string(variant, NULL);
- gboolean output = FALSE;
- gboolean is_12hour_button = (gboolean)GPOINTER_TO_INT(user_data);
-
- if (g_strcmp0(str, "locale-default") == 0) {
- output = (is_12hour_button == is_locale_12h ());
- } else if (g_strcmp0(str, "12-hour") == 0) {
- output = is_12hour_button;
- } else if (g_strcmp0(str, "24-hour") == 0) {
- output = !is_12hour_button;
- } else {
- return FALSE;
- }
-
- g_value_set_boolean (value, output);
- return TRUE;
-}
-
-static void
-widget_dependency_cb (GtkWidget * parent, GParamSpec *pspec G_GNUC_UNUSED, GtkWidget * dependent)
-{
- gboolean active, sensitive;
- g_object_get (G_OBJECT (parent),
- "active", &active,
- "sensitive", &sensitive, NULL);
- gtk_widget_set_sensitive (dependent, active && sensitive);
-}
-
-static void
-add_widget_dependency (GtkWidget * parent, GtkWidget * dependent)
-{
- g_signal_connect (parent, "notify::active", G_CALLBACK(widget_dependency_cb),
- dependent);
- g_signal_connect (parent, "notify::sensitive", G_CALLBACK(widget_dependency_cb),
- dependent);
- widget_dependency_cb (parent, NULL, dependent);
-}
-
-static void
-polkit_dependency_cb (GPermission * permission, GParamSpec *pspec G_GNUC_UNUSED, GtkWidget * dependent)
-{
- gboolean allowed = FALSE;
-
- g_object_get (G_OBJECT (permission),
- "allowed", &allowed, NULL);
-
- gtk_widget_set_sensitive (dependent, allowed);
-}
-
-static void
-add_polkit_dependency_helper (GtkWidget * parent, GParamSpec *pspec G_GNUC_UNUSED, GtkWidget * dependent)
-{
- GtkLockButton * button = GTK_LOCK_BUTTON (parent);
- GPermission * permission = gtk_lock_button_get_permission (button);
- g_signal_connect (permission, "notify::allowed",
- G_CALLBACK(polkit_dependency_cb), dependent);
- polkit_dependency_cb (permission, NULL, dependent);
-}
-
-static void
-add_polkit_dependency (GtkWidget * parent, GtkWidget * dependent)
-{
- /* polkit async hasn't finished at this point, so wait for permission to come in */
- g_signal_connect (parent, "notify::permission", G_CALLBACK(add_polkit_dependency_helper),
- dependent);
- gtk_widget_set_sensitive (dependent, FALSE);
-}
-
-static void
-polkit_perm_ready (GObject *source_object G_GNUC_UNUSED, GAsyncResult *res, gpointer user_data)
-{
- GError * error = NULL;
- GPermission * permission = polkit_permission_new_finish (res, &error);
-
- if (error != NULL) {
- if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
- g_warning ("Could not get permission object: %s", error->message);
- g_error_free (error);
- return;
- }
-
- GtkLockButton * button = GTK_LOCK_BUTTON (user_data);
- gtk_lock_button_set_permission (button, permission);
-}
-
-static void
-dbus_set_answered (GObject *object, GAsyncResult *res, gpointer command)
-{
- GError * error = NULL;
- GVariant * answers = g_dbus_proxy_call_finish (G_DBUS_PROXY (object), res, &error);
-
- if (error != NULL) {
- if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
- g_warning("Could not set '%s' using timedated: %s", (gchar *)command, error->message);
- g_error_free(error);
- return;
- }
-
- g_variant_unref (answers);
-}
-
-static void
-toggle_ntp (GtkWidget * radio, GParamSpec * pspec G_GNUC_UNUSED, IndicatorDatetimePanel * self)
-{
- gboolean active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (radio));
-
- g_dbus_proxy_call (self->priv->proxy, "SetNTP", g_variant_new ("(bb)", active, TRUE),
- G_DBUS_CALL_FLAGS_NONE, -1, NULL, dbus_set_answered, "NTP");
-}
-
-static void
-sync_entry (IndicatorDatetimePanel * self, const gchar * location)
-{
- gchar * name = get_current_zone_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);
-}
-
-static void
-tz_changed (CcTimezoneMap * map G_GNUC_UNUSED,
- CcTimezoneLocation * location,
- IndicatorDatetimePanel * self)
-{
- if (location == NULL)
- return;
-
- gchar * zone;
- g_object_get (location, "zone", &zone, NULL);
-
- g_dbus_proxy_call (self->priv->proxy, "SetTimezone", g_variant_new ("(sb)", zone, TRUE),
- G_DBUS_CALL_FLAGS_NONE, -1, NULL, dbus_set_answered, "timezone");
-
- sync_entry (self, zone);
-
- g_free (zone);
-}
-
-static void
-proxy_ready (GObject *object G_GNUC_UNUSED,
- GAsyncResult *res,
- IndicatorDatetimePanel * self)
-{
- GError * error = NULL;
- IndicatorDatetimePanelPrivate * priv = self->priv;
- GVariant *value;
-
- self->priv->proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
-
- if (error != NULL) {
- if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
- g_critical("Could not grab DBus proxy for timedated: %s", error->message);
- g_error_free(error);
- return;
- }
-
- /* And now, do initial proxy configuration */
- value = g_dbus_proxy_get_cached_property (priv->proxy, "CanNTP");
- if (value != NULL)
- {
- if (g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN))
- gtk_widget_set_sensitive (priv->auto_radio, g_variant_get_boolean (value));
- g_variant_unref (value);
- }
-
- value = g_dbus_proxy_get_cached_property (priv->proxy, "NTP");
- if (value != NULL)
- {
- if (g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN))
- {
- gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->auto_radio), g_variant_get_boolean (value));
- g_signal_connect (priv->auto_radio, "notify::active", G_CALLBACK (toggle_ntp), self);
- }
- g_variant_unref (value);
- }
-
- value = g_dbus_proxy_get_cached_property (priv->proxy, "Timezone");
- if (value != NULL)
- {
- if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
- {
- const gchar *timezone = g_variant_get_string (value, NULL);
-
- cc_timezone_map_set_timezone (priv->tzmap, timezone);
- sync_entry (self, timezone);
- g_signal_connect (priv->tzmap, "location-changed", G_CALLBACK (tz_changed), self);
- }
- g_variant_unref (value);
- }
-}
-
-#define WIG(name) GTK_WIDGET (gtk_builder_get_object(self->priv->builder, name))
-
-static void
-set_show_clock_check_sensitive (IndicatorDatetimePanel * self,
- gboolean sensitive)
-{
- gtk_widget_set_sensitive (WIG("showClockCheck"), sensitive);
-}
-
-static void
-on_bus_name_appeared (GDBusConnection * connection G_GNUC_UNUSED,
- const char * name G_GNUC_UNUSED,
- const char * name_owner,
- gpointer self)
-{
- set_show_clock_check_sensitive (self, name_owner && *name_owner);
-}
-
-static void
-on_bus_name_vanished (GDBusConnection * connection G_GNUC_UNUSED,
- const char * name G_GNUC_UNUSED,
- gpointer self)
-{
- set_show_clock_check_sensitive (self, FALSE);
-}
-
-static gboolean
-are_spinners_focused (IndicatorDatetimePanel * self)
-{
- // save_time_id means that we were in focus and haven't finished our save
- // yet, so act like we are still focused.
- return self->priv->save_time_id ||
- gtk_widget_has_focus (self->priv->time_spin) ||
- gtk_widget_has_focus (self->priv->date_spin);
-}
-
-static gboolean
-save_time (IndicatorDatetimePanel * self)
-{
- if (self->priv->user_edited_time) {
- gdouble current_value = gtk_spin_button_get_value (GTK_SPIN_BUTTON (self->priv->date_spin));
- g_dbus_proxy_call (self->priv->proxy, "SetTime",
- g_variant_new ("(xbb)", (gint64) (current_value * G_TIME_SPAN_SECOND), FALSE, TRUE),
- G_DBUS_CALL_FLAGS_NONE, -1, NULL, dbus_set_answered, "time");
- }
- self->priv->user_edited_time = FALSE;
- self->priv->save_time_id = 0;
- return FALSE;
-}
-
-static gboolean
-spin_focus_in (IndicatorDatetimePanel * self)
-{
- if (self->priv->save_time_id > 0) {
- g_source_remove (self->priv->save_time_id);
- self->priv->save_time_id = 0;
- }
- return FALSE;
-}
-
-static gboolean
-spin_focus_out (IndicatorDatetimePanel * self)
-{
- /* We want to only save when both spinners are unfocused. But it's difficult
- to tell who is about to get focus during a focus-out. So we set an idle
- callback to save the time if we don't focus in to another spinner by that
- time. */
- if (self->priv->save_time_id == 0) {
- self->priv->save_time_id = g_idle_add ((GSourceFunc)save_time, self);
- }
- return FALSE;
-}
-
-static int
-input_time_text (GtkWidget * spinner, gdouble * value, IndicatorDatetimePanel * self)
-{
- gboolean is_time = (gboolean)GPOINTER_TO_INT (g_object_get_data (G_OBJECT (spinner), "is-time"));
- const gchar * text = gtk_entry_get_text (GTK_ENTRY (spinner));
-
- gdouble current_value = gtk_spin_button_get_value (GTK_SPIN_BUTTON (spinner));
- *value = current_value;
-
- GDateTime * now = g_date_time_new_from_unix_local (current_value);
- gint year, month, day, hour, minute, second;
- year = g_date_time_get_year (now);
- month = g_date_time_get_month (now);
- day = g_date_time_get_day_of_month (now);
- hour = g_date_time_get_hour (now);
- minute = g_date_time_get_minute (now);
- second = g_date_time_get_second (now);
- g_date_time_unref (now);
-
- /* Parse this string as if it were in the output format */
- gint scanned = 0;
- gboolean passed = TRUE, skip = FALSE;
- if (is_time) {
- gint hour_in, minute_in, second_in;
-
- if (is_locale_12h ()) { // TODO: make this look-at/watch gsettings?
- char ampm[51];
-
- /* coverity[secure_coding] */
- scanned = sscanf (text, "%u:%u:%u %50s", &hour_in, &minute_in, &second_in, ampm);
- passed = (scanned == 4);
-
- if (passed) {
- const char *pm_str = nl_langinfo (PM_STR);
- if (g_ascii_strcasecmp (pm_str, ampm) == 0) {
- hour_in += 12;
- }
- }
- } else {
- /* coverity[secure_coding] */
- scanned = sscanf (text, "%u:%u:%u", &hour_in, &minute_in, &second_in);
- passed = (scanned == 3);
- }
-
- if (passed && (hour_in > 23 || minute_in > 59 || second_in > 59)) {
- passed = FALSE;
- }
- if (passed && hour == hour_in && minute == minute_in && second == second_in) {
- skip = TRUE; // no change
- } else {
- hour = hour_in;
- minute = minute_in;
- second = second_in;
- }
- }
- else {
- gint year_in, month_in, day_in;
-
- /* coverity[secure_coding] */
- scanned = sscanf (text, "%u-%u-%u", &year_in, &month_in, &day_in);
-
- if (scanned != 3 || year_in < 1 || year_in > 9999 ||
- month_in < 1 || month_in > 12 || day_in < 1 || day_in > 31) {
- passed = FALSE;
- }
- if (passed && year == year_in && month == month_in && day == day_in) {
- skip = TRUE; // no change
- } else {
- year = year_in;
- month = month_in;
- day = day_in;
- }
- }
-
- if (!passed) {
- g_warning ("Could not understand %s", text);
- return TRUE;
- }
-
- if (skip) {
- return TRUE;
- }
-
- gboolean prev_changing = self->priv->changing_time;
- self->priv->changing_time = TRUE;
- GDateTime * new_time = g_date_time_new_local (year, month, day, hour, minute, second);
- *value = g_date_time_to_unix (new_time);
- self->priv->user_edited_time = TRUE;
- g_date_time_unref (new_time);
- self->priv->changing_time = prev_changing;
-
- return TRUE;
-}
-
-static gboolean
-format_time_text (GtkWidget * spinner, gpointer user_data G_GNUC_UNUSED)
-{
- gboolean is_time = (gboolean)GPOINTER_TO_INT (g_object_get_data (G_OBJECT (spinner), "is-time"));
-
- const gchar * format;
- if (is_time) {
- if (is_locale_12h ()) { // TODO: make this look-at/watch gsettings?
- format = "%I:%M:%S %p";
- } else {
- format = "%H:%M:%S";
- }
- }
- else {
- // This is intentionally not "%x". See https://launchpad.net/bugs/1149696
- // If you are willing to do the hard work of writing a locale-sensitive
- // date parser, there is an open bug: https://launchpad.net/bugs/729056
- format = "%Y-%m-%d";
- }
-
- GDateTime * datetime = g_date_time_new_from_unix_local (gtk_spin_button_get_value (GTK_SPIN_BUTTON (spinner)));
- gchar * formatted = g_date_time_format (datetime, format);
- gtk_entry_set_text (GTK_ENTRY (spinner), formatted);
- g_date_time_unref (datetime);
-
- return TRUE;
-}
-
-static void
-spin_copy_value (GtkSpinButton * spinner, IndicatorDatetimePanel * self)
-{
- GtkSpinButton * other = NULL;
- if (GTK_WIDGET (spinner) == self->priv->date_spin)
- other = GTK_SPIN_BUTTON (self->priv->time_spin);
- else
- other = GTK_SPIN_BUTTON (self->priv->date_spin);
-
- if (gtk_spin_button_get_value (spinner) != gtk_spin_button_get_value (other)) {
- gtk_spin_button_set_value (other, gtk_spin_button_get_value (spinner));
- }
- if (!self->priv->changing_time) { /* Means user pressed spin buttons */
- self->priv->user_edited_time = TRUE;
- }
-}
-
-static gboolean
-update_spinners (IndicatorDatetimePanel * self)
-{
- /* Add datetime object to spinner, which will hold the real time value, rather
- then using the value of the spinner itself. And don't update while user is
- editing. */
- if (!are_spinners_focused (self)) {
- gboolean prev_changing = self->priv->changing_time;
- self->priv->changing_time = TRUE;
- GDateTime * now = g_date_time_new_now_local ();
- gtk_spin_button_set_value (GTK_SPIN_BUTTON (self->priv->time_spin),
- (gdouble)g_date_time_to_unix (now));
- /* will be copied to other spin button */
- g_date_time_unref (now);
- self->priv->changing_time = prev_changing;
- }
- return TRUE;
-}
-
-static void
-setup_time_spinners (IndicatorDatetimePanel * self, GtkWidget * time, GtkWidget * date)
-{
- g_signal_connect (time, "input", G_CALLBACK (input_time_text), self);
- g_signal_connect (date, "input", G_CALLBACK (input_time_text), self);
-
- g_signal_connect (time, "output", G_CALLBACK (format_time_text), date);
- g_signal_connect (date, "output", G_CALLBACK (format_time_text), time);
-
- g_signal_connect_swapped (time, "focus-in-event", G_CALLBACK (spin_focus_in), self);
- g_signal_connect_swapped (date, "focus-in-event", G_CALLBACK (spin_focus_in), self);
-
- g_signal_connect_swapped (time, "focus-out-event", G_CALLBACK (spin_focus_out), self);
- g_signal_connect_swapped (date, "focus-out-event", G_CALLBACK (spin_focus_out), self);
-
- g_signal_connect (time, "value-changed", G_CALLBACK (spin_copy_value), self);
- g_signal_connect (date, "value-changed", G_CALLBACK (spin_copy_value), self);
-
- g_object_set_data (G_OBJECT (time), "is-time", GINT_TO_POINTER (TRUE));
- g_object_set_data (G_OBJECT (date), "is-time", GINT_TO_POINTER (FALSE));
-
- self->priv->time_spin = time;
- self->priv->date_spin = date;
-
- /* 2 seconds is what the indicator itself uses */
- guint time_id = g_timeout_add_seconds (2, (GSourceFunc)update_spinners, self);
- g_signal_connect_swapped (self->priv->time_spin, "destroy",
- G_CALLBACK (g_source_remove), GINT_TO_POINTER (time_id));
- update_spinners (self);
-}
-
-static void
-show_locations (IndicatorDatetimePanel * self)
-{
- if (self->priv->loc_dlg == NULL) {
- self->priv->loc_dlg = datetime_setup_locations_dialog (self->priv->tzmap);
- GtkWidget * dlg = gtk_widget_get_toplevel (GTK_WIDGET (self));
- gtk_window_set_type_hint (GTK_WINDOW(self->priv->loc_dlg), GDK_WINDOW_TYPE_HINT_DIALOG);
- gtk_window_set_transient_for (GTK_WINDOW (self->priv->loc_dlg), GTK_WINDOW (dlg));
- g_signal_connect (self->priv->loc_dlg, "destroy", G_CALLBACK (gtk_widget_destroyed), &self->priv->loc_dlg);
- gtk_widget_show_all (self->priv->loc_dlg);
- }
- else {
- gtk_window_present_with_time (GTK_WINDOW (self->priv->loc_dlg), gtk_get_current_event_time ());
- }
-}
-
-static gboolean
-timezone_selected (GtkEntryCompletion * widget G_GNUC_UNUSED,
- GtkTreeModel * model,
- GtkTreeIter * iter,
- IndicatorDatetimePanel * self)
-{
- const gchar * name, * zone;
-
- gtk_tree_model_get (model, iter,
- CC_TIMEZONE_COMPLETION_NAME, &name,
- CC_TIMEZONE_COMPLETION_ZONE, &zone,
- -1);
-
- if (zone == NULL || zone[0] == 0) {
- const gchar * strlon, * strlat;
- gdouble lon = 0.0, lat = 0.0;
-
- gtk_tree_model_get (model, iter,
- CC_TIMEZONE_COMPLETION_LONGITUDE, &strlon,
- CC_TIMEZONE_COMPLETION_LATITUDE, &strlat,
- -1);
-
- if (strlon != NULL && strlon[0] != 0) {
- lon = g_ascii_strtod(strlon, NULL);
- }
-
- if (strlat != NULL && strlat[0] != 0) {
- lat = g_ascii_strtod(strlat, NULL);
- }
-
- zone = cc_timezone_map_get_timezone_at_coords (self->priv->tzmap, lon, lat);
- }
-
- gchar * tz_name = g_strdup_printf ("%s %s", zone, name);
- g_settings_set_string (self->priv->settings, SETTINGS_TIMEZONE_NAME_S, tz_name);
- g_free (tz_name);
-
- cc_timezone_map_set_timezone (self->priv->tzmap, zone);
-
- return FALSE; // Do normal action too
-}
-
-static gboolean
-entry_focus_out (GtkEntry * entry,
- GdkEventFocus * event G_GNUC_UNUSED,
- IndicatorDatetimePanel * self)
-{
- // If the name left in the entry doesn't match the current timezone name,
- // show an error icon. It's always an error for the user to manually type in
- // a timezone.
- CcTimezoneLocation * location = cc_timezone_map_get_location (self->priv->tzmap);
- if (location == NULL)
- return FALSE;
-
- gchar * zone;
- g_object_get (location, "zone", &zone, NULL);
-
- gchar * name = get_current_zone_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_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);
- return FALSE;
-}
-
-static void
-indicator_datetime_panel_init (IndicatorDatetimePanel * self)
-{
- GError * error;
- GSettings * conf;
-
- self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
- INDICATOR_DATETIME_TYPE_PANEL,
- IndicatorDatetimePanelPrivate);
-
- self->priv->settings = conf = g_settings_new (SETTINGS_INTERFACE);
-
- self->priv->builder = gtk_builder_new ();
- gtk_builder_set_translation_domain (self->priv->builder, GETTEXT_PACKAGE);
- error = NULL;
- gtk_builder_add_from_file (self->priv->builder, DATETIME_DIALOG_UI_FILE, &error);
- if (error != NULL) {
- /* We have to abort, we can't continue without the ui file */
- g_error ("Could not load ui file %s: %s", DATETIME_DIALOG_UI_FILE, error->message);
- g_error_free (error);
- return;
- }
-
-
- /* Add policykit button */
- GtkWidget * polkit_button = gtk_lock_button_new (NULL);
- g_object_set (G_OBJECT (polkit_button),
- "text-unlock", _("Unlock to change these settings"),
- "text-lock", _("Lock to prevent further changes"),
- NULL);
- GtkWidget * alignment = gtk_alignment_new (0.0, 0.5, 0.0, 0.0);
- gtk_container_add (GTK_CONTAINER (alignment), polkit_button);
- gtk_box_pack_start (GTK_BOX (WIG ("timeDateBox")), alignment, FALSE, TRUE, 0);
-
- const gchar * polkit_name = "org.gnome.controlcenter.datetime.configure";
- polkit_permission_new (polkit_name, NULL, NULL, polkit_perm_ready, polkit_button);
-
- /* Add map */
- self->priv->tzmap = cc_timezone_map_new ();
- gtk_container_add (GTK_CONTAINER (WIG ("mapBox")), GTK_WIDGET (self->priv->tzmap));
- /* Fufill the CC by Attribution license requirements for the Geonames lookup */
- cc_timezone_map_set_watermark (self->priv->tzmap, "Geonames.org");
-
- /* And completion entry */
- self->priv->completion = cc_timezone_completion_new ();
- cc_timezone_completion_watch_entry (self->priv->completion, GTK_ENTRY (WIG ("timezoneEntry")));
- g_signal_connect (self->priv->completion, "match-selected", G_CALLBACK (timezone_selected), self);
- g_signal_connect (WIG ("timezoneEntry"), "focus-out-event", G_CALLBACK (entry_focus_out), self);
-
- /* Set up settings bindings */
- g_settings_bind (conf, SETTINGS_SHOW_CLOCK_S, WIG ("showClockCheck"),
- "active", G_SETTINGS_BIND_DEFAULT);
- g_settings_bind (conf, SETTINGS_SHOW_DAY_S, WIG ("showWeekdayCheck"),
- "active", G_SETTINGS_BIND_DEFAULT);
- g_settings_bind (conf, SETTINGS_SHOW_DATE_S, WIG ("showDateAndMonthCheck"),
- "active", G_SETTINGS_BIND_DEFAULT);
- g_settings_bind (conf, SETTINGS_SHOW_YEAR_S, WIG ("showYearCheck"),
- "active", G_SETTINGS_BIND_DEFAULT);
- g_settings_bind (conf, SETTINGS_SHOW_SECONDS_S, WIG ("showSecondsCheck"),
- "active", G_SETTINGS_BIND_DEFAULT);
- g_settings_bind_with_mapping (conf, SETTINGS_TIME_FORMAT_S,
- WIG ("show12HourRadio"), "active",
- G_SETTINGS_BIND_DEFAULT,
- bind_hours_get, bind_hours_set,
- GINT_TO_POINTER(TRUE), NULL);
- g_settings_bind_with_mapping (conf, SETTINGS_TIME_FORMAT_S,
- WIG ("show24HourRadio"), "active",
- G_SETTINGS_BIND_DEFAULT,
- bind_hours_get, bind_hours_set,
- GINT_TO_POINTER(FALSE), NULL);
- g_settings_bind (conf, SETTINGS_SHOW_CALENDAR_S, WIG ("showCalendarCheck"),
- "active", G_SETTINGS_BIND_DEFAULT);
- g_settings_bind (conf, SETTINGS_SHOW_WEEK_NUMBERS_S, WIG ("includeWeekNumbersCheck"),
- "active", G_SETTINGS_BIND_DEFAULT);
- g_settings_bind (conf, SETTINGS_SHOW_EVENTS_S, WIG ("showEventsCheck"),
- "active", G_SETTINGS_BIND_DEFAULT);
- g_settings_bind (conf, SETTINGS_SHOW_DETECTED_S, WIG ("showDetectedCheck"),
- "active", G_SETTINGS_BIND_DEFAULT);
- g_settings_bind (conf, SETTINGS_SHOW_LOCATIONS_S, WIG ("showLocationsCheck"),
- "active", G_SETTINGS_BIND_DEFAULT);
-
- /* Set up sensitivities */
- add_widget_dependency (WIG ("showDateAndMonthCheck"), WIG ("showYearCheck"));
- add_widget_dependency (WIG ("showCalendarCheck"), WIG ("calendarOptions"));
- add_widget_dependency (WIG ("showClockCheck"), WIG ("clockOptions"));
- add_widget_dependency (WIG ("showLocationsCheck"), WIG ("locationsButton"));
- add_widget_dependency (WIG ("manualTimeRadio"), WIG ("manualOptions"));
- add_polkit_dependency (polkit_button, WIG ("timeDateOptions"));
-
- /* Hacky proxy test for whether evolution-data-server is installed */
- gchar * evo_path = g_find_program_in_path ("evolution");
- gtk_widget_set_sensitive (WIG ("showEventsCheck"), (evo_path != NULL));
- g_free (evo_path);
-
- setup_time_spinners (self, WIG ("timeSpinner"), WIG ("dateSpinner"));
-
- GtkWidget * panel = WIG ("timeDatePanel");
- self->priv->auto_radio = WIG ("automaticTimeRadio");
- self->priv->tz_entry = WIG ("timezoneEntry");
-
- g_signal_connect_swapped (WIG ("locationsButton"), "clicked", G_CALLBACK (show_locations), self);
-
- /* Grab proxy for settings daemon */
- 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",
- NULL, (GAsyncReadyCallback)proxy_ready, self);
-
- /* Grab proxy for datetime service, to see if it's running. It would
- actually be more ideal to see if the indicator module itself is running,
- but that doesn't yet claim a name on the bus. Presumably the service
- would have been started by any such indicator, so this will at least tell
- us if there *was* a datetime module run this session. */
- self->priv->name_watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
- BUS_NAME,
- G_BUS_NAME_WATCHER_FLAGS_NONE,
- on_bus_name_appeared,
- on_bus_name_vanished,
- self,
- NULL);
-
-#undef WIG
-
- gtk_widget_show_all (panel);
- gtk_container_add (GTK_CONTAINER (self), panel);
-}
-
-static void
-indicator_datetime_panel_dispose (GObject * object)
-{
- IndicatorDatetimePanel * self = (IndicatorDatetimePanel *) object;
- IndicatorDatetimePanelPrivate * priv = self->priv;
-
- g_clear_object (&priv->builder);
- g_clear_object (&priv->proxy);
- g_clear_object (&priv->settings);
-
- if (priv->loc_dlg) {
- gtk_widget_destroy (priv->loc_dlg);
- priv->loc_dlg = NULL;
- }
-
- if (priv->name_watch_id != 0) {
- g_bus_unwatch_name (priv->name_watch_id);
- priv->name_watch_id = 0;
- }
-
- if (priv->save_time_id) {
- g_source_remove (priv->save_time_id);
- priv->save_time_id = 0;
- }
-
- if (priv->completion) {
- cc_timezone_completion_watch_entry (priv->completion, NULL);
- g_clear_object (&priv->completion);
- }
-
- if (priv->tz_entry) {
- gtk_widget_destroy (priv->tz_entry);
- priv->tz_entry = NULL;
- }
-
- if (priv->time_spin) {
- gtk_widget_destroy (priv->time_spin);
- priv->time_spin = NULL;
- }
-
- if (priv->date_spin) {
- gtk_widget_destroy (priv->date_spin);
- priv->date_spin = NULL;
- }
-
- G_OBJECT_CLASS (indicator_datetime_panel_parent_class)->dispose (object);
-}
-
-static void
-indicator_datetime_panel_class_finalize (IndicatorDatetimePanelClass *klass G_GNUC_UNUSED)
-{
-}
-
-static const char *
-indicator_datetime_panel_get_help_uri (CcPanel *panel G_GNUC_UNUSED)
-{
- return "help:ubuntu-help/clock";
-}
-
-static void
-indicator_datetime_panel_class_init (IndicatorDatetimePanelClass *klass)
-{
- GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
- CcPanelClass *panel_class = CC_PANEL_CLASS (klass);
-
- g_type_class_add_private (klass, sizeof (IndicatorDatetimePanelPrivate));
-
- panel_class->get_help_uri = indicator_datetime_panel_get_help_uri;
-
- gobject_class->dispose = indicator_datetime_panel_dispose;
-}
-
-void
-g_io_module_load (GIOModule *module)
-{
- bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
- bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
-
- indicator_datetime_panel_register_type (G_TYPE_MODULE (module));
- g_io_extension_point_implement (CC_SHELL_PANEL_EXTENSION_POINT,
- INDICATOR_DATETIME_TYPE_PANEL,
- "indicator-datetime", 0);
-}
-
-void
-g_io_module_unload (GIOModule *module G_GNUC_UNUSED)
-{
-}
diff --git a/po/CMakeLists.txt b/po/CMakeLists.txt
index 37a0a05..8325f6e 100644
--- a/po/CMakeLists.txt
+++ b/po/CMakeLists.txt
@@ -1,3 +1,3 @@
include (Translations)
add_translations_directory ("${GETTEXT_PACKAGE}")
-add_translations_catalog ("${GETTEXT_PACKAGE}" ../src/ ../panel)
+add_translations_catalog ("${GETTEXT_PACKAGE}" ../src/)
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 516cdc5..3faca0b 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,7 +1,5 @@
-src/service.c
-src/settings-shared.h
+src/formatter.cpp
+src/formatter-desktop.cpp
+src/menu.cpp
+src/snap.cpp
src/utils.c
-panel/datetime-prefs.c
-panel/datetime-prefs-locations.c
-[type: gettext/glade]data/datetime-dialog.ui
-data/gnome-indicator-datetime-panel.desktop.in
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 15f29ea..7b1d7df 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,45 +1,37 @@
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")
-# let service know how to launch gnome-control-center on the desktop
-if (BUILD_PANEL)
- add_definitions (-DHAVE_CCPANEL)
-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
+ clock-watcher.cpp
+ date-time.cpp
+ exporter.cpp
+ formatter.cpp
+ formatter-desktop.cpp
+ locations.cpp
+ locations-settings.cpp
+ menu.cpp
+ planner-eds.cpp
+ settings-live.cpp
+ snap.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..ccc7fcf
--- /dev/null
+++ b/src/actions-live.cpp
@@ -0,0 +1,213 @@
+/*
+ * 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
+ {
+ execute_command("gnome-control-center datetime");
+ }
+
+ 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 != nullptr)
+ {
+ 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-watcher.cpp b/src/clock-watcher.cpp
new file mode 100644
index 0000000..a2e700d
--- /dev/null
+++ b/src/clock-watcher.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright 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:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include <datetime/clock-watcher.h>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/***
+****
+***/
+
+ClockWatcherImpl::ClockWatcherImpl(const std::shared_ptr<const State>& state):
+ m_state(state)
+{
+ m_state->planner->upcoming.changed().connect([this](const std::vector<Appointment>&){
+ g_debug("ClockWatcher pulse because upcoming appointments changed");
+ pulse();
+ });
+ m_state->clock->minute_changed.connect([this](){
+ g_debug("ClockWatcher pulse because clock minute_changed");
+ pulse();
+ });
+ pulse();
+}
+
+core::Signal<const Appointment&>& ClockWatcherImpl::alarm_reached()
+{
+ return m_alarm_reached;
+}
+
+void ClockWatcherImpl::pulse()
+{
+ const auto now = m_state->clock->localtime();
+
+ for(const auto& appointment : m_state->planner->upcoming.get())
+ {
+ if (m_triggered.count(appointment.uid))
+ continue;
+ if (!DateTime::is_same_minute(now, appointment.begin))
+ continue;
+
+ m_triggered.insert(appointment.uid);
+ m_alarm_reached(appointment);
+ }
+}
+
+/***
+****
+***/
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
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..e6d99cd
--- /dev/null
+++ b/src/date-time.cpp
@@ -0,0 +1,177 @@
+/*
+ * 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;
+}
+
+DateTime DateTime::add_full(int years, int months, int days, int hours, int minutes, double seconds) const
+{
+ auto gdt = g_date_time_add_full(get(), years, months, days, hours, minutes, seconds);
+ DateTime dt(gdt);
+ 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());
+}
+
+double DateTime::seconds() const
+{
+ return g_date_time_get_seconds(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 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..31d9db6
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,99 @@
+/*
+ * 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/clock-watcher.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/snap.h>
+#include <datetime/state.h>
+#include <datetime/timezones-live.h>
+
+#include <glib/gi18n.h> // bindtextdomain()
+#include <gio/gio.h>
+
+#include <url-dispatcher.h>
+
+#include <locale.h>
+#include <cstdlib> // 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);
+
+ // 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(live_clock));
+ state->planner->time = live_clock->localtime();
+ std::shared_ptr<Actions> actions(new LiveActions(state));
+ MenuFactory factory(actions, state);
+
+ // snap decisions
+ ClockWatcherImpl clock_watcher(state);
+ Snap snap;
+ clock_watcher.alarm_reached().connect([&snap](const Appointment& appt){
+ auto snap_show = [](const Appointment& a){
+ const char* url;
+ if(!a.url.empty())
+ url = a.url.c_str();
+ else // alarm doesn't have a URl associated with it; use a fallback
+ url = "appid://com.ubuntu.clock/clock/current-user-version";
+ url_dispatch_send(url, nullptr, nullptr);
+ };
+ auto snap_dismiss = [](const Appointment&){};
+ snap(appt, snap_show, snap_dismiss);
+ });
+
+ // 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..b2562db
--- /dev/null
+++ b/src/menu.cpp
@@ -0,0 +1,572 @@
+/*
+ * 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 <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include <vector>
+
+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 ALARM_ICON_NAME "alarm-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)
+ {
+ // 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_upcoming(); // our m_upcoming is planner->upcoming() filtered by time
+ });
+ 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->clock->minute_changed.connect([this](){
+ update_upcoming(); // our m_upcoming is planner->upcoming() filtered by time
+ });
+ 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);
+ }
+
+ void update_upcoming()
+ {
+ const auto now = m_state->clock->localtime();
+ const auto next_minute = now.add_full(0,0,0,0,1,-now.seconds());
+
+ std::vector<Appointment> upcoming;
+ for(const auto& a : m_state->planner->upcoming.get())
+ if (next_minute <= a.begin)
+ upcoming.push_back(a);
+
+ if (m_upcoming != upcoming)
+ {
+ m_upcoming.swap(upcoming);
+ update_header(); // show an 'alarm' icon if there are upcoming alarms
+ update_section(Appointments); // "upcoming" is the list of Appointments we show
+ }
+ }
+
+ 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()
+ {
+ if (G_UNLIKELY(m_serialized_alarm_icon == nullptr))
+ {
+ auto i = g_themed_icon_new_with_default_fallbacks(ALARM_ICON_NAME);
+ m_serialized_alarm_icon = g_icon_serialize(i);
+ g_object_unref(i);
+ }
+
+ return m_serialized_alarm_icon;
+ }
+
+ std::vector<Appointment> m_upcoming;
+
+private:
+
+ 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]", nullptr);
+ 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_upcoming)
+ {
+ // don't show too many
+ if (n++ >= MAX_APPTS)
+ break;
+
+ // don't show duplicates
+ 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_upcoming)
+ 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..7d9416c
--- /dev/null
+++ b/src/planner-eds.cpp
@@ -0,0 +1,536 @@
+/*
+ * 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>
+
+#include <map>
+#include <set>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/****
+*****
+****/
+
+class PlannerEds::Impl
+{
+public:
+
+ Impl(PlannerEds& owner, const std::shared_ptr<Clock>& clock):
+ m_owner(owner),
+ m_clock(clock),
+ m_cancellable(g_cancellable_new())
+ {
+ e_source_registry_new(m_cancellable, on_source_registry_ready, this);
+
+ m_clock->minute_changed.connect([this](){
+ g_debug("rebuilding upcoming because the clock's minute_changed");
+ rebuild_soon(UPCOMING);
+ });
+
+ m_owner.time.changed().connect([this](const DateTime& dt) {
+ g_debug("planner's datetime property changed to %s; calling rebuild_soon()", dt.format("%F %T").c_str());
+ rebuild_soon(MONTH);
+ });
+
+ rebuild_soon(ALL);
+ }
+
+ ~Impl()
+ {
+ g_cancellable_cancel(m_cancellable);
+ g_clear_object(&m_cancellable);
+
+ while(!m_sources.empty())
+ remove_source(*m_sources.begin());
+
+ 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
+ {
+ g_signal_connect(r, "source-added", G_CALLBACK(on_source_added), gself);
+ g_signal_connect(r, "source-removed", G_CALLBACK(on_source_removed), gself);
+ g_signal_connect(r, "source-changed", G_CALLBACK(on_source_changed), gself);
+ g_signal_connect(r, "source-disabled", G_CALLBACK(on_source_disabled), gself);
+ g_signal_connect(r, "source-enabled", G_CALLBACK(on_source_enabled), gself);
+
+ auto self = static_cast<Impl*>(gself);
+ self->m_source_registry = r;
+ self->add_sources_by_extension(E_SOURCE_EXTENSION_CALENDAR);
+ self->add_sources_by_extension(E_SOURCE_EXTENSION_TASK_LIST);
+ }
+ }
+
+ void add_sources_by_extension(const char* extension)
+ {
+ auto& r = m_source_registry;
+ auto sources = e_source_registry_list_sources(r, extension);
+ for (auto l=sources; l!=nullptr; l=l->next)
+ on_source_added(r, E_SOURCE(l->data), this);
+ g_list_free_full(sources, g_object_unref);
+ }
+
+ static void on_source_added(ESourceRegistry* registry, ESource* source, gpointer gself)
+ {
+ auto self = static_cast<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<Impl*>(gself);
+ ECalClientSourceType source_type;
+
+ if (e_source_has_extension(source, E_SOURCE_EXTENSION_CALENDAR))
+ source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS;
+ else if (e_source_has_extension(source, E_SOURCE_EXTENSION_TASK_LIST))
+ source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS;
+ else
+ g_assert_not_reached();
+
+ g_debug("connecting a client to source %s", e_source_get_uid(source));
+ e_cal_client_connect(source,
+ source_type,
+ 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
+ {
+ // add the client to our collection
+ auto self = static_cast<Impl*>(gself);
+ g_debug("got a client for %s", e_cal_client_get_local_attachment_store(E_CAL_CLIENT(client)));
+ self->m_clients[e_client_get_source(client)] = E_CAL_CLIENT(client);
+
+ // now create a view for it so that we can listen for changes
+ e_cal_client_get_view (E_CAL_CLIENT(client),
+ "#t", // match all
+ self->m_cancellable,
+ on_client_view_ready,
+ self);
+
+ g_debug("client connected; calling rebuild_soon()");
+ self->rebuild_soon(ALL);
+ }
+ }
+
+ static void on_client_view_ready (GObject* client, GAsyncResult* res, gpointer gself)
+ {
+ GError* error = nullptr;
+ ECalClientView* view = nullptr;
+
+ if (e_cal_client_get_view_finish (E_CAL_CLIENT(client), res, &view, &error))
+ {
+ // add the view to our collection
+ e_cal_client_view_start(view, &error);
+ g_debug("got a view for %s", e_cal_client_get_local_attachment_store(E_CAL_CLIENT(client)));
+ auto self = static_cast<Impl*>(gself);
+ self->m_views[e_client_get_source(E_CLIENT(client))] = view;
+
+ g_signal_connect(view, "objects-added", G_CALLBACK(on_view_objects_added), self);
+ g_signal_connect(view, "objects-modified", G_CALLBACK(on_view_objects_modified), self);
+ g_signal_connect(view, "objects-removed", G_CALLBACK(on_view_objects_removed), self);
+ g_debug("view connected; calling rebuild_soon()");
+ self->rebuild_soon(ALL);
+ }
+ else if(error != nullptr)
+ {
+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning("indicator-datetime cannot get View to EDS client: %s", error->message);
+
+ g_error_free(error);
+ }
+ }
+
+ static void on_view_objects_added(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
+ {
+ g_debug("%s", G_STRFUNC);
+ static_cast<Impl*>(gself)->rebuild_soon(ALL);
+ }
+ static void on_view_objects_modified(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
+ {
+ g_debug("%s", G_STRFUNC);
+ static_cast<Impl*>(gself)->rebuild_soon(ALL);
+ }
+ static void on_view_objects_removed(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
+ {
+ g_debug("%s", G_STRFUNC);
+ static_cast<Impl*>(gself)->rebuild_soon(ALL);
+ }
+
+ static void on_source_disabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)
+ {
+ static_cast<Impl*>(gself)->disable_source(source);
+ }
+ void disable_source(ESource* source)
+ {
+ // if an ECalClientView is associated with this source, remove it
+ auto vit = m_views.find(source);
+ if (vit != m_views.end())
+ {
+ auto& view = vit->second;
+ e_cal_client_view_stop(view, nullptr);
+ const auto n_disconnected = g_signal_handlers_disconnect_by_data(view, this);
+ g_warn_if_fail(n_disconnected == 3);
+ g_object_unref(view);
+ m_views.erase(vit);
+ rebuild_soon(ALL);
+ }
+
+ // if an ECalClient is associated with this source, remove it
+ auto cit = m_clients.find(source);
+ if (cit != m_clients.end())
+ {
+ auto& client = cit->second;
+ g_object_unref(client);
+ m_clients.erase(cit);
+ rebuild_soon(ALL);
+ }
+ }
+
+ static void on_source_removed(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)
+ {
+ static_cast<Impl*>(gself)->remove_source(source);
+ }
+ void remove_source(ESource* source)
+ {
+ disable_source(source);
+
+ auto sit = m_sources.find(source);
+ if (sit != m_sources.end())
+ {
+ g_object_unref(*sit);
+ m_sources.erase(sit);
+ rebuild_soon(ALL);
+ }
+ }
+
+ static void on_source_changed(ESourceRegistry* /*registry*/, ESource* /*source*/, gpointer gself)
+ {
+ g_debug("source changed; calling rebuild_soon()");
+ static_cast<Impl*>(gself)->rebuild_soon(ALL);
+ }
+
+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 rebuild_soon(int rebuild_flags)
+ {
+ static const guint ARBITRARY_INTERVAL_SECS = 2;
+
+ m_rebuild_flags |= rebuild_flags;
+
+ if (m_rebuild_tag == 0)
+ m_rebuild_tag = g_timeout_add_seconds(ARBITRARY_INTERVAL_SECS, rebuild_now_static, this);
+ }
+
+ static gboolean rebuild_now_static(gpointer gself)
+ {
+ auto self = static_cast<Impl*>(gself);
+ const auto flags = self->m_rebuild_flags;
+ self->m_rebuild_tag = 0;
+ self->m_rebuild_flags = 0;
+ self->rebuild_now(flags);
+ return G_SOURCE_REMOVE;
+ }
+
+ void rebuild_now(int rebuild_flags)
+ {
+ if (rebuild_flags & UPCOMING)
+ rebuild_upcoming();
+
+ if (rebuild_flags & MONTH)
+ rebuild_month();
+ }
+
+ void rebuild_month()
+ {
+ const auto ref = m_owner.time.get().get();
+ auto month_begin = g_date_time_add_full(ref,
+ 0, // subtract no years
+ 0, // subtract no months
+ -(g_date_time_get_day_of_month(ref)-1),
+ -g_date_time_get_hour(ref),
+ -g_date_time_get_minute(ref),
+ -g_date_time_get_seconds(ref));
+ auto month_end = g_date_time_add_full(month_begin, 0, 1, 0, 0, 0, -0.1);
+
+ get_appointments(month_begin, month_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_date_time_unref(month_end);
+ g_date_time_unref(month_begin);
+ }
+
+ void rebuild_upcoming()
+ {
+ const auto ref = m_clock->localtime();
+ const auto begin = g_date_time_add_minutes(ref.get(),-10);
+ const auto end = g_date_time_add_months(begin,1);
+
+ get_appointments(begin, end, [this](const std::vector<Appointment>& appointments) {
+ g_debug("got %d upcoming appointments", (int)appointments.size());
+ m_owner.upcoming.set(appointments);
+ });
+
+ g_date_time_unref(end);
+ g_date_time_unref(begin);
+ }
+
+ void get_appointments(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& kv : m_clients)
+ {
+ auto& client = kv.second;
+ 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& source = kv.first;
+ 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;
+ }
+
+ PlannerEds& m_owner;
+ std::shared_ptr<Clock> m_clock;
+ std::set<ESource*> m_sources;
+ std::map<ESource*,ECalClient*> m_clients;
+ std::map<ESource*,ECalClientView*> m_views;
+ GCancellable* m_cancellable = nullptr;
+ ESourceRegistry* m_source_registry = nullptr;
+ guint m_rebuild_tag = 0;
+ guint m_rebuild_flags = 0;
+ enum { UPCOMING=(1<<0), MONTH=(1<<1), ALL=UPCOMING|MONTH };
+};
+
+PlannerEds::PlannerEds(const std::shared_ptr<Clock>& clock): p(new Impl(*this, clock)) {}
+
+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 246ea70..0000000
--- a/src/service.c
+++ /dev/null
@@ -1,2403 +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 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_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)
-{
-#ifdef HAVE_CCPANEL
- execute_command ("gnome-control-center indicator-datetime");
-#else
- execute_command ("gnome-control-center datetime");
-#endif
-}
-
-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" }
- };
-
- 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/snap.cpp b/src/snap.cpp
new file mode 100644
index 0000000..f2d075a
--- /dev/null
+++ b/src/snap.cpp
@@ -0,0 +1,256 @@
+/*
+ * Copyright 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:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include <datetime/appointment.h>
+#include <datetime/formatter.h>
+#include <datetime/snap.h>
+
+#include <canberra.h>
+#include <libnotify/notify.h>
+
+#include <glib/gi18n.h>
+#include <glib.h>
+
+#define ALARM_SOUND_FILENAME "/usr/share/sounds/ubuntu/stereo/phone-incoming-call.ogg"
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/***
+****
+***/
+
+namespace
+{
+
+/**
+*** libcanberra -- play sounds
+**/
+
+// arbitrary number, but we need a consistent id for play/cancel
+const int32_t alarm_ca_id = 1;
+
+gboolean media_cached = FALSE;
+ca_context *c_context = nullptr;
+guint timeout_tag = 0;
+
+ca_context* get_ca_context()
+{
+ if (G_UNLIKELY(c_context == nullptr))
+ {
+ int rv;
+
+ if ((rv = ca_context_create(&c_context)) != CA_SUCCESS)
+ {
+ g_warning("Failed to create canberra context: %s\n", ca_strerror(rv));
+ c_context = nullptr;
+ }
+ else
+ {
+ const char* filename = ALARM_SOUND_FILENAME;
+ rv = ca_context_cache(c_context,
+ CA_PROP_EVENT_ID, "alarm",
+ CA_PROP_MEDIA_FILENAME, filename,
+ CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
+ NULL);
+ media_cached = rv == CA_SUCCESS;
+ if (rv != CA_SUCCESS)
+ g_warning("Couldn't add '%s' to canberra cache: %s", filename, ca_strerror(rv));
+ }
+ }
+
+ return c_context;
+}
+
+void play_alarm_sound();
+
+gboolean play_alarm_sound_idle (gpointer)
+{
+ timeout_tag = 0;
+ play_alarm_sound();
+ return G_SOURCE_REMOVE;
+}
+
+void on_alarm_play_done (ca_context* /*context*/, uint32_t /*id*/, int rv, void* /*user_data*/)
+{
+ // wait one second, then play it again
+ if ((rv == CA_SUCCESS) && (timeout_tag == 0))
+ timeout_tag = g_timeout_add_seconds (1, play_alarm_sound_idle, nullptr);
+}
+
+void play_alarm_sound()
+{
+ const gchar* filename = ALARM_SOUND_FILENAME;
+ auto context = get_ca_context();
+ g_return_if_fail(context != nullptr);
+
+ ca_proplist* props = nullptr;
+ ca_proplist_create(&props);
+ if (media_cached)
+ ca_proplist_sets(props, CA_PROP_EVENT_ID, "alarm");
+ ca_proplist_sets(props, CA_PROP_MEDIA_FILENAME, filename);
+
+ const auto rv = ca_context_play_full(context, alarm_ca_id, props, on_alarm_play_done, nullptr);
+ if (rv != CA_SUCCESS)
+ g_warning("Failed to play file '%s': %s", filename, ca_strerror(rv));
+
+ g_clear_pointer(&props, ca_proplist_destroy);
+}
+
+void stop_alarm_sound()
+{
+ auto context = get_ca_context();
+ if (context != nullptr)
+ {
+ const auto rv = ca_context_cancel(context, alarm_ca_id);
+ if (rv != CA_SUCCESS)
+ g_warning("Failed to cancel alarm sound: %s", ca_strerror(rv));
+ }
+
+ if (timeout_tag != 0)
+ {
+ g_source_remove(timeout_tag);
+ timeout_tag = 0;
+ }
+}
+
+/**
+*** libnotify -- snap decisions
+**/
+
+void first_time_init()
+{
+ static bool inited = false;
+
+ if (G_UNLIKELY(!inited))
+ {
+ inited = true;
+
+ if(!notify_init("indicator-datetime-service"))
+ g_critical("libnotify initialization failed");
+ }
+}
+
+struct SnapData
+{
+ Snap::appointment_func show;
+ Snap::appointment_func dismiss;
+ Appointment appointment;
+};
+
+void on_snap_show(NotifyNotification*, gchar* /*action*/, gpointer gdata)
+{
+ stop_alarm_sound();
+ auto data = static_cast<SnapData*>(gdata);
+ data->show(data->appointment);
+}
+
+void on_snap_dismiss(NotifyNotification*, gchar* /*action*/, gpointer gdata)
+{
+ stop_alarm_sound();
+ auto data = static_cast<SnapData*>(gdata);
+ data->dismiss(data->appointment);
+}
+
+void snap_data_destroy_notify(gpointer gdata)
+{
+ delete static_cast<SnapData*>(gdata);
+}
+
+void show_snap_decision(SnapData* data)
+{
+ const Appointment& appointment = data->appointment;
+
+ const auto timestr = appointment.begin.format("%a, %X");
+ auto title = g_strdup_printf(_("Alarm %s"), timestr.c_str());
+ const auto body = appointment.summary;
+ const gchar* icon_name = "alarm-clock";
+
+ auto nn = notify_notification_new(title, body.c_str(), 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_show, data, nullptr);
+ notify_notification_add_action(nn, "dismiss", _("Dismiss"), on_snap_dismiss, data, nullptr);
+ g_object_set_data_full(G_OBJECT(nn), "snap-data", data, snap_data_destroy_notify);
+
+ GError * error = nullptr;
+ notify_notification_show(nn, &error);
+ if (error != NULL)
+ {
+ g_warning("Unable to show snap decision for '%s': %s", body.c_str(), error->message);
+ g_error_free(error);
+ data->show(data->appointment);
+ }
+
+ g_free(title);
+}
+
+/**
+***
+**/
+
+void notify(const Appointment& appointment,
+ Snap::appointment_func show,
+ Snap::appointment_func dismiss)
+{
+ auto data = new SnapData;
+ data->appointment = appointment;
+ data->show = show;
+ data->dismiss = dismiss;
+
+ play_alarm_sound();
+ show_snap_decision(data);
+}
+
+} // unnamed namespace
+
+
+/***
+****
+***/
+
+Snap::Snap()
+{
+ first_time_init();
+}
+
+Snap::~Snap()
+{
+ media_cached = false;
+ g_clear_pointer(&c_context, ca_context_destroy);
+}
+
+void Snap::operator()(const Appointment& appointment,
+ appointment_func show,
+ appointment_func dismiss)
+{
+ if (appointment.has_alarms)
+ notify(appointment, show, dismiss);
+ else
+ dismiss(appointment);
+}
+
+/***
+****
+***/
+
+} // 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..c99897a
--- /dev/null
+++ b/src/timezone-file.cpp
@@ -0,0 +1,116 @@
+/*
+ * 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>
+
+#include <cerrno>
+#include <cstdlib>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+FileTimezone::FileTimezone()
+{
+}
+
+FileTimezone::FileTimezone(const std::string& filename)
+{
+ set_filename(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::set_filename(const std::string& filename)
+{
+ clear();
+
+ auto tmp = realpath(filename.c_str(), nullptr);
+ if(tmp != nullptr)
+ {
+ m_filename = tmp;
+ free(tmp);
+ }
+ else
+ {
+ g_warning("Unable to resolve path '%s': %s", filename.c_str(), g_strerror(errno));
+ m_filename = filename; // better than nothing?
+ }
+
+ auto file = g_file_new_for_path(m_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(on_file_changed), this);
+ g_debug("%s Monitoring timezone file '%s'", G_STRLOC, m_filename.c_str());
+ }
+
+ reload();
+}
+
+void
+FileTimezone::on_file_changed(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..7d590c9 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -28,12 +28,45 @@ 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_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-clock-watcher)
+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)
+
+set (TEST_NAME manual-test-snap)
+add_executable (${TEST_NAME} ${TEST_NAME}.cpp)
add_dependencies (${TEST_NAME} libindicatordatetimeservice)
target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS})
+# 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..0c597d3
--- /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 != nullptr)
+ {
+ 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/manual-test-snap.cpp b/tests/manual-test-snap.cpp
new file mode 100644
index 0000000..51556cd
--- /dev/null
+++ b/tests/manual-test-snap.cpp
@@ -0,0 +1,63 @@
+
+/*
+ * 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/appointment.h>
+#include <datetime/snap.h>
+
+#include <glib.h>
+
+using namespace unity::indicator::datetime;
+
+/***
+****
+***/
+
+int main()
+{
+ Appointment a;
+ a.color = "green";
+ a.summary = "Alarm";
+ a.url = "alarm:///hello-world";
+ a.uid = "D4B57D50247291478ED31DED17FF0A9838DED402";
+ a.is_event = false;
+ a.is_daily = false;
+ a.has_alarms = true;
+ auto begin = g_date_time_new_local(2014,12,25,0,0,0);
+ auto end = g_date_time_add_full(begin,0,0,1,0,0,-1);
+ a.begin = begin;
+ a.end = end;
+ g_date_time_unref(end);
+ g_date_time_unref(begin);
+
+ auto loop = g_main_loop_new(nullptr, false);
+ auto show = [loop](const Appointment& appt){
+ g_message("You clicked 'show' for appt url '%s'", appt.url.c_str());
+ g_main_loop_quit(loop);
+ };
+ auto dismiss = [loop](const Appointment&){
+ g_message("You clicked 'dismiss'");
+ g_main_loop_quit(loop);
+ };
+
+ Snap snap;
+ snap(a, show, dismiss);
+ g_main_loop_run(loop);
+ return 0;
+}
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-watcher.cpp b/tests/test-clock-watcher.cpp
new file mode 100644
index 0000000..79b8485
--- /dev/null
+++ b/tests/test-clock-watcher.cpp
@@ -0,0 +1,166 @@
+/*
+ * Copyright 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:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include <datetime/clock-watcher.h>
+
+#include <gtest/gtest.h>
+
+#include "state-fixture.h"
+
+using namespace unity::indicator::datetime;
+
+class ClockWatcherFixture: public StateFixture
+{
+private:
+
+ typedef StateFixture super;
+
+protected:
+
+ std::vector<std::string> m_triggered;
+ std::unique_ptr<ClockWatcher> m_watcher;
+
+ void SetUp()
+ {
+ super::SetUp();
+
+ m_watcher.reset(new ClockWatcherImpl(m_state));
+ m_watcher->alarm_reached().connect([this](const Appointment& appt){
+ m_triggered.push_back(appt.uid);
+ });
+
+ EXPECT_TRUE(m_triggered.empty());
+ }
+
+ void TearDown()
+ {
+ m_triggered.clear();
+ m_watcher.reset();
+
+ super::TearDown();
+ }
+
+ std::vector<Appointment> build_some_appointments()
+ {
+ const auto now = m_state->clock->localtime();
+ 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; // 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 = tomorrow_begin;
+ a1.end = tomorrow_end;
+
+ auto ubermorgen_begin = g_date_time_add_days (tomorrow, 1);
+ auto ubermorgen_end = g_date_time_add_full (tomorrow_begin, 0, 0, 1, 0, 0, -1);
+
+ 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 = ubermorgen_begin;
+ a2.end = ubermorgen_end;
+
+ // cleanup
+ g_date_time_unref(ubermorgen_end);
+ g_date_time_unref(ubermorgen_begin);
+ g_date_time_unref(tomorrow_end);
+ g_date_time_unref(tomorrow_begin);
+ g_date_time_unref(tomorrow);
+
+ return std::vector<Appointment>({a1, a2});
+ }
+};
+
+/***
+****
+***/
+
+TEST_F(ClockWatcherFixture, AppointmentsChanged)
+{
+ // Add some appointments to the planner.
+ // One of these matches our state's localtime, so that should get triggered.
+ std::vector<Appointment> a = build_some_appointments();
+ a[0].begin = m_state->clock->localtime();
+ m_state->planner->upcoming.set(a);
+
+ // Confirm that it got fired
+ EXPECT_EQ(1, m_triggered.size());
+ EXPECT_EQ(a[0].uid, m_triggered[0]);
+}
+
+
+TEST_F(ClockWatcherFixture, TimeChanged)
+{
+ // Add some appointments to the planner.
+ // Neither of these match the state's localtime, so nothing should be triggered.
+ std::vector<Appointment> a = build_some_appointments();
+ m_state->planner->upcoming.set(a);
+ EXPECT_TRUE(m_triggered.empty());
+
+ // Set the state's clock to a time that matches one of the appointments.
+ // That appointment should get triggered.
+ m_mock_state->mock_clock->set_localtime(a[1].begin);
+ EXPECT_EQ(1, m_triggered.size());
+ EXPECT_EQ(a[1].uid, m_triggered[0]);
+}
+
+
+TEST_F(ClockWatcherFixture, MoreThanOne)
+{
+ const auto now = m_state->clock->localtime();
+ std::vector<Appointment> a = build_some_appointments();
+ a[0].begin = a[1].begin = now;
+ m_state->planner->upcoming.set(a);
+
+ EXPECT_EQ(2, m_triggered.size());
+ EXPECT_EQ(a[0].uid, m_triggered[0]);
+ EXPECT_EQ(a[1].uid, m_triggered[1]);
+}
+
+
+TEST_F(ClockWatcherFixture, NoDuplicates)
+{
+ // Setup: add an appointment that gets triggered.
+ const auto now = m_state->clock->localtime();
+ const std::vector<Appointment> appointments = build_some_appointments();
+ std::vector<Appointment> a;
+ a.push_back(appointments[0]);
+ a[0].begin = now;
+ m_state->planner->upcoming.set(a);
+ EXPECT_EQ(1, m_triggered.size());
+ EXPECT_EQ(a[0].uid, m_triggered[0]);
+
+ // Now change the appointment vector by adding one to it.
+ // Confirm that the ClockWatcher doesn't re-trigger a[0]
+ a.push_back(appointments[1]);
+ m_state->planner->upcoming.set(a);
+ EXPECT_EQ(1, m_triggered.size());
+ EXPECT_EQ(a[0].uid, m_triggered[0]);
+}
diff --git a/tests/test-clock.cpp b/tests/test-clock.cpp
new file mode 100644
index 0000000..a4924b3
--- /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),
+ nullptr,
+ "/org/freedesktop/login1", // object path
+ "org.freedesktop.login1.Manager", // interface
+ "PrepareForSleep", // signal name
+ g_variant_new("(b)", FALSE),
+ nullptr);
+ }
+};
+
+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(&section);
+
+ // 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(&section);
+
+ // 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(&section);
+ //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(&section);
+
+ // 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(&section);
+
+ // 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(&section);
+
+ // 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(&section);
+
+ // 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(&section);
+
+ // 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(&section);
+ 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(&section);
+ 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..1923ba1
--- /dev/null
+++ b/tests/test-planner.cpp
@@ -0,0 +1,87 @@
+/*
+ * 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 namespace unity::indicator::datetime;
+
+/***
+****
+***/
+
+typedef GlibFixture PlannerFixture;
+
+TEST_F(PlannerFixture, EDS)
+{
+ auto tmp = g_date_time_new_now_local();
+ const auto now = DateTime(tmp);
+ g_date_time_unref(tmp);
+
+ std::shared_ptr<Clock> clock(new MockClock(now));
+ PlannerEds planner(clock);
+ wait_msec(100);
+
+ planner.time.set(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..707247d
--- /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", nullptr};
+ const gchar* bstrv[] = {"America/Denver", "Europe/London London", "Europe/Berlin Berlin", nullptr};
+ 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..97f07ed
--- /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", nullptr, "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