Merge lp:~charlesk/indicator-datetime/alarms into lp:indicator-datetime/14.04

Proposed by Charles Kerr
Status: Merged
Approved by: Ted Gould
Approved revision: 405
Merged at revision: 305
Proposed branch: lp:~charlesk/indicator-datetime/alarms
Merge into: lp:indicator-datetime/14.04
Prerequisite: lp:~charlesk/indicator-datetime/lp-1271484
Diff against target: 1743 lines (+1011/-204)
25 files modified
CMakeLists.txt (+2/-2)
debian/control (+1/-1)
include/datetime/clock-watcher.h (+72/-0)
include/datetime/date-time.h (+3/-0)
include/datetime/planner-eds.h (+3/-2)
include/datetime/snap.h (+51/-0)
include/datetime/timezone-file.h (+2/-2)
po/POTFILES.in (+2/-0)
src/CMakeLists.txt (+2/-0)
src/actions-live.cpp (+1/-1)
src/clock-watcher.cpp (+71/-0)
src/date-time.cpp (+18/-0)
src/main.cpp (+22/-9)
src/menu.cpp (+37/-66)
src/planner-eds.cpp (+216/-105)
src/snap.cpp (+256/-0)
src/timezone-file.cpp (+4/-4)
tests/CMakeLists.txt (+5/-0)
tests/geoclue-fixture.h (+1/-1)
tests/manual-test-snap.cpp (+63/-0)
tests/test-clock-watcher.cpp (+166/-0)
tests/test-clock.cpp (+2/-2)
tests/test-planner.cpp (+8/-6)
tests/test-settings.cpp (+2/-2)
tests/test-utils.cpp (+1/-1)
To merge this branch: bzr merge lp:~charlesk/indicator-datetime/alarms
Reviewer Review Type Date Requested Status
Ted Gould (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+204420@code.launchpad.net

Commit message

support for ubuntu-clock-app's alarms

Description of the change

Alarm Support for indicator-datetime:

 * add ECalClient view to monitor changes in the client's components
 * watch both calendar and task-list because ubuntu-ui-tookit uses the latter
 * add ClockWatcher + unit tests to decide when to show a snap decision
 * restore Snap decision code + manual tests to display alarms on phone
 * play a looping sound while the snap decision is being shown
 * update the header state when alarms change s.t. we sync the icon
 * use the ubuntu-mobile icon 'alarm-clock' for alarm + header icon

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
397. By Charles Kerr

bugfix: when closing the snap decision, ensure there's not a timeout waiting to loop the ringtone

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
398. By Charles Kerr

copyediting: make the Snap lambdas a little easier to read.

399. By Charles Kerr

add utils.c to POTFILES.in

400. By Charles Kerr

remove alarms from the menu once they've been shown in a snap decision.

401. By Charles Kerr

if an alarm doesn't have a URL associated with it, use 'appid://com.ubuntu.clock/clock/current-user-version' as a fallback url.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
402. By Charles Kerr

revert r400; we can't block alarms by UID because that would hide recurring alarms

403. By Charles Kerr

remove upcoming events from the menu once they're no longer upcoming.

404. By Charles Kerr

when playing a sound in canberra, don't use CA_PROP_EVENT_ID if caching failed

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
405. By Charles Kerr

another pass at removing alarms from the menu once they're no longer upcoming. This version fixes the header's icon as well.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Ted Gould (ted) wrote :

Okay, looks good!

review: Approve
406. By Charles Kerr

sync with trunk

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'CMakeLists.txt'
--- CMakeLists.txt 2014-02-10 22:59:02 +0000
+++ CMakeLists.txt 2014-02-19 15:36:23 +0000
@@ -38,10 +38,10 @@
38 libical>=0.4838 libical>=0.48
39 libecal-1.2>=3.539 libecal-1.2>=3.5
40 libedataserver-1.2>=3.540 libedataserver-1.2>=3.5
41 libcanberra>=0.12
41 libnotify>=0.7.642 libnotify>=0.7.6
42 url-dispatcher-1>=143 url-dispatcher-1>=1
43 properties-cpp>=0.0.144 properties-cpp>=0.0.1)
44 json-glib-1.0>=0.16.2)
45include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS})45include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS})
4646
47##47##
4848
=== modified file 'debian/control'
--- debian/control 2014-02-11 20:06:28 +0000
+++ debian/control 2014-02-19 15:36:23 +0000
@@ -16,13 +16,13 @@
16 libgtest-dev,16 libgtest-dev,
17 libglib2.0-dev (>= 2.35.4),17 libglib2.0-dev (>= 2.35.4),
18 libnotify-dev (>= 0.7.6),18 libnotify-dev (>= 0.7.6),
19 libcanberra-dev,
19 libido3-0.1-dev (>= 0.2.90),20 libido3-0.1-dev (>= 0.2.90),
20 libgeoclue-dev (>= 0.12.0),21 libgeoclue-dev (>= 0.12.0),
21 libecal1.2-dev (>= 3.5),22 libecal1.2-dev (>= 3.5),
22 libical-dev (>= 1.0),23 libical-dev (>= 1.0),
23 libgtk-3-dev (>= 3.1.4),24 libgtk-3-dev (>= 3.1.4),
24 libcairo2-dev (>= 1.10),25 libcairo2-dev (>= 1.10),
25 libjson-glib-dev,
26 libpolkit-gobject-1-dev,26 libpolkit-gobject-1-dev,
27 libedataserver1.2-dev (>= 3.5),27 libedataserver1.2-dev (>= 3.5),
28 libgconf2-dev (>= 2.31),28 libgconf2-dev (>= 2.31),
2929
=== added file 'include/datetime/clock-watcher.h'
--- include/datetime/clock-watcher.h 1970-01-01 00:00:00 +0000
+++ include/datetime/clock-watcher.h 2014-02-19 15:36:23 +0000
@@ -0,0 +1,72 @@
1/*
2 * Copyright 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3, as published
6 * by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranties of
10 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 * PURPOSE. See the GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors:
17 * Charles Kerr <charles.kerr@canonical.com>
18 */
19
20#ifndef INDICATOR_DATETIME_CLOCK_WATCHER_H
21#define INDICATOR_DATETIME_CLOCK_WATCHER_H
22
23#include <datetime/state.h>
24#include <datetime/appointment.h>
25
26#include <core/signal.h>
27
28#include <memory>
29#include <set>
30#include <string>
31
32namespace unity {
33namespace indicator {
34namespace datetime {
35
36
37/**
38 * \brief Watches the clock and appointments to notify when an
39 * appointment's time is reached.
40 */
41class ClockWatcher
42{
43public:
44 ClockWatcher() =default;
45 virtual ~ClockWatcher() =default;
46 virtual core::Signal<const Appointment&>& alarm_reached() = 0;
47};
48
49
50/**
51 * \brief A #ClockWatcher implementation
52 */
53class ClockWatcherImpl: public ClockWatcher
54{
55public:
56 ClockWatcherImpl(const std::shared_ptr<const State>& state);
57 ~ClockWatcherImpl() =default;
58 core::Signal<const Appointment&>& alarm_reached();
59
60private:
61 void pulse();
62 std::set<std::string> m_triggered;
63 std::shared_ptr<const State> m_state;
64 core::Signal<const Appointment&> m_alarm_reached;
65};
66
67
68} // namespace datetime
69} // namespace indicator
70} // namespace unity
71
72#endif // INDICATOR_DATETIME_CLOCK_WATCHER_H
073
=== modified file 'include/datetime/date-time.h'
--- include/datetime/date-time.h 2014-01-30 19:00:22 +0000
+++ include/datetime/date-time.h 2014-02-19 15:36:23 +0000
@@ -41,6 +41,7 @@
41 DateTime& operator=(GDateTime* in);41 DateTime& operator=(GDateTime* in);
42 DateTime& operator=(const DateTime& in);42 DateTime& operator=(const DateTime& in);
43 DateTime to_timezone(const std::string& zone) const;43 DateTime to_timezone(const std::string& zone) const;
44 DateTime add_full(int years, int months, int days, int hours, int minutes, double seconds) const;
44 void reset(GDateTime* in=nullptr);45 void reset(GDateTime* in=nullptr);
4546
46 GDateTime* get() const;47 GDateTime* get() const;
@@ -48,9 +49,11 @@
4849
49 std::string format(const std::string& fmt) const;50 std::string format(const std::string& fmt) const;
50 int day_of_month() const;51 int day_of_month() const;
52 double seconds() const;
51 int64_t to_unix() const;53 int64_t to_unix() const;
5254
53 bool operator<(const DateTime& that) const;55 bool operator<(const DateTime& that) const;
56 bool operator<=(const DateTime& that) const;
54 bool operator!=(const DateTime& that) const;57 bool operator!=(const DateTime& that) const;
55 bool operator==(const DateTime& that) const;58 bool operator==(const DateTime& that) const;
5659
5760
=== modified file 'include/datetime/planner-eds.h'
--- include/datetime/planner-eds.h 2014-01-15 05:07:10 +0000
+++ include/datetime/planner-eds.h 2014-02-19 15:36:23 +0000
@@ -20,9 +20,10 @@
20#ifndef INDICATOR_DATETIME_PLANNER_EDS_H20#ifndef INDICATOR_DATETIME_PLANNER_EDS_H
21#define INDICATOR_DATETIME_PLANNER_EDS_H21#define INDICATOR_DATETIME_PLANNER_EDS_H
2222
23#include <datetime/clock.h>
23#include <datetime/planner.h>24#include <datetime/planner.h>
2425
25#include <memory> // unique_ptr26#include <memory> // shared_ptr, unique_ptr
2627
27namespace unity {28namespace unity {
28namespace indicator {29namespace indicator {
@@ -34,7 +35,7 @@
34class PlannerEds: public Planner35class PlannerEds: public Planner
35{36{
36public:37public:
37 PlannerEds();38 PlannerEds(const std::shared_ptr<Clock>& clock);
38 virtual ~PlannerEds();39 virtual ~PlannerEds();
3940
40private:41private:
4142
=== added file 'include/datetime/snap.h'
--- include/datetime/snap.h 1970-01-01 00:00:00 +0000
+++ include/datetime/snap.h 2014-02-19 15:36:23 +0000
@@ -0,0 +1,51 @@
1/*
2 * Copyright 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3, as published
6 * by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranties of
10 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 * PURPOSE. See the GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors:
17 * Charles Kerr <charles.kerr@canonical.com>
18 */
19
20#ifndef INDICATOR_DATETIME_SNAP_H
21#define INDICATOR_DATETIME_SNAP_H
22
23#include <datetime/appointment.h>
24
25#include <memory>
26#include <functional>
27
28namespace unity {
29namespace indicator {
30namespace datetime {
31
32/**
33 * \brief Pops up Snap Decisions for appointments
34 */
35class Snap
36{
37public:
38 Snap();
39 virtual ~Snap();
40
41 typedef std::function<void(const Appointment&)> appointment_func;
42 void operator()(const Appointment& appointment,
43 appointment_func show,
44 appointment_func dismiss);
45};
46
47} // namespace datetime
48} // namespace indicator
49} // namespace unity
50
51#endif // INDICATOR_DATETIME_SNAP_H
052
=== modified file 'include/datetime/timezone-file.h'
--- include/datetime/timezone-file.h 2014-01-30 19:00:22 +0000
+++ include/datetime/timezone-file.h 2014-02-19 15:36:23 +0000
@@ -42,8 +42,8 @@
42 ~FileTimezone();42 ~FileTimezone();
4343
44private:44private:
45 void setFilename(const std::string& filename);45 void set_filename(const std::string& filename);
46 static void onFileChanged(gpointer gself);46 static void on_file_changed(gpointer gself);
47 void clear();47 void clear();
48 void reload();48 void reload();
4949
5050
=== modified file 'po/POTFILES.in'
--- po/POTFILES.in 2014-02-10 22:59:02 +0000
+++ po/POTFILES.in 2014-02-19 15:36:23 +0000
@@ -1,3 +1,5 @@
1src/formatter.cpp1src/formatter.cpp
2src/formatter-desktop.cpp2src/formatter-desktop.cpp
3src/menu.cpp3src/menu.cpp
4src/snap.cpp
5src/utils.c
46
=== modified file 'src/CMakeLists.txt'
--- src/CMakeLists.txt 2014-02-10 22:59:02 +0000
+++ src/CMakeLists.txt 2014-02-19 15:36:23 +0000
@@ -13,6 +13,7 @@
13 appointment.cpp13 appointment.cpp
14 clock.cpp14 clock.cpp
15 clock-live.cpp15 clock-live.cpp
16 clock-watcher.cpp
16 date-time.cpp17 date-time.cpp
17 exporter.cpp18 exporter.cpp
18 formatter.cpp19 formatter.cpp
@@ -22,6 +23,7 @@
22 menu.cpp23 menu.cpp
23 planner-eds.cpp24 planner-eds.cpp
24 settings-live.cpp25 settings-live.cpp
26 snap.cpp
25 timezone-file.cpp27 timezone-file.cpp
26 timezone-geoclue.cpp28 timezone-geoclue.cpp
27 timezones-live.cpp29 timezones-live.cpp
2830
=== modified file 'src/actions-live.cpp'
--- src/actions-live.cpp 2014-02-10 22:59:02 +0000
+++ src/actions-live.cpp 2014-02-19 15:36:23 +0000
@@ -156,7 +156,7 @@
156156
157 GError * err = nullptr;157 GError * err = nullptr;
158 auto proxy = g_dbus_proxy_new_for_bus_finish(res, &err);158 auto proxy = g_dbus_proxy_new_for_bus_finish(res, &err);
159 if (err != NULL)159 if (err != nullptr)
160 {160 {
161 if (!g_error_matches(err, G_IO_ERROR, G_IO_ERROR_CANCELLED))161 if (!g_error_matches(err, G_IO_ERROR, G_IO_ERROR_CANCELLED))
162 g_warning("Could not grab DBus proxy for timedated: %s", err->message);162 g_warning("Could not grab DBus proxy for timedated: %s", err->message);
163163
=== added file 'src/clock-watcher.cpp'
--- src/clock-watcher.cpp 1970-01-01 00:00:00 +0000
+++ src/clock-watcher.cpp 2014-02-19 15:36:23 +0000
@@ -0,0 +1,71 @@
1/*
2 * Copyright 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3, as published
6 * by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranties of
10 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 * PURPOSE. See the GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors:
17 * Charles Kerr <charles.kerr@canonical.com>
18 */
19
20#include <datetime/clock-watcher.h>
21
22namespace unity {
23namespace indicator {
24namespace datetime {
25
26/***
27****
28***/
29
30ClockWatcherImpl::ClockWatcherImpl(const std::shared_ptr<const State>& state):
31 m_state(state)
32{
33 m_state->planner->upcoming.changed().connect([this](const std::vector<Appointment>&){
34 g_debug("ClockWatcher pulse because upcoming appointments changed");
35 pulse();
36 });
37 m_state->clock->minute_changed.connect([this](){
38 g_debug("ClockWatcher pulse because clock minute_changed");
39 pulse();
40 });
41 pulse();
42}
43
44core::Signal<const Appointment&>& ClockWatcherImpl::alarm_reached()
45{
46 return m_alarm_reached;
47}
48
49void ClockWatcherImpl::pulse()
50{
51 const auto now = m_state->clock->localtime();
52
53 for(const auto& appointment : m_state->planner->upcoming.get())
54 {
55 if (m_triggered.count(appointment.uid))
56 continue;
57 if (!DateTime::is_same_minute(now, appointment.begin))
58 continue;
59
60 m_triggered.insert(appointment.uid);
61 m_alarm_reached(appointment);
62 }
63}
64
65/***
66****
67***/
68
69} // namespace datetime
70} // namespace indicator
71} // namespace unity
072
=== modified file 'src/date-time.cpp'
--- src/date-time.cpp 2014-01-30 19:00:22 +0000
+++ src/date-time.cpp 2014-02-19 15:36:23 +0000
@@ -69,6 +69,14 @@
69 return dt;69 return dt;
70}70}
7171
72DateTime DateTime::add_full(int years, int months, int days, int hours, int minutes, double seconds) const
73{
74 auto gdt = g_date_time_add_full(get(), years, months, days, hours, minutes, seconds);
75 DateTime dt(gdt);
76 g_date_time_unref(gdt);
77 return dt;
78}
79
72GDateTime* DateTime::get() const80GDateTime* DateTime::get() const
73{81{
74 g_assert(m_dt);82 g_assert(m_dt);
@@ -88,6 +96,11 @@
88 return g_date_time_get_day_of_month(get());96 return g_date_time_get_day_of_month(get());
89}97}
9098
99double DateTime::seconds() const
100{
101 return g_date_time_get_seconds(get());
102}
103
91int64_t DateTime::to_unix() const104int64_t DateTime::to_unix() const
92{105{
93 return g_date_time_to_unix(get());106 return g_date_time_to_unix(get());
@@ -112,6 +125,11 @@
112 return g_date_time_compare(get(), that.get()) < 0;125 return g_date_time_compare(get(), that.get()) < 0;
113}126}
114127
128bool DateTime::operator<=(const DateTime& that) const
129{
130 return g_date_time_compare(get(), that.get()) <= 0;
131}
132
115bool DateTime::operator!=(const DateTime& that) const133bool DateTime::operator!=(const DateTime& that) const
116{134{
117 // return true if this isn't set, or if it's not equal135 // return true if this isn't set, or if it's not equal
118136
=== modified file 'src/main.cpp'
--- src/main.cpp 2014-01-30 19:06:33 +0000
+++ src/main.cpp 2014-02-19 15:36:23 +0000
@@ -17,24 +17,25 @@
17 * with this program. If not, see <http://www.gnu.org/licenses/>.17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */18 */
1919
20
21
22#include <datetime/actions-live.h>20#include <datetime/actions-live.h>
23#include <datetime/clock.h>21#include <datetime/clock.h>
22#include <datetime/clock-watcher.h>
24#include <datetime/exporter.h>23#include <datetime/exporter.h>
25#include <datetime/locations-settings.h>24#include <datetime/locations-settings.h>
26#include <datetime/menu.h>25#include <datetime/menu.h>
27#include <datetime/planner-eds.h>26#include <datetime/planner-eds.h>
28#include <datetime/settings-live.h>27#include <datetime/settings-live.h>
28#include <datetime/snap.h>
29#include <datetime/state.h>29#include <datetime/state.h>
30#include <datetime/timezones-live.h>30#include <datetime/timezones-live.h>
3131
32#include <glib/gi18n.h> // bindtextdomain()32#include <glib/gi18n.h> // bindtextdomain()
33#include <gio/gio.h>33#include <gio/gio.h>
34#include <libnotify/notify.h> 34
35#include <url-dispatcher.h>
3536
36#include <locale.h>37#include <locale.h>
37#include <stdlib.h> // exit()38#include <cstdlib> // exit()
3839
39using namespace unity::indicator::datetime;40using namespace unity::indicator::datetime;
4041
@@ -50,10 +51,6 @@
50 bindtextdomain(GETTEXT_PACKAGE, GNOMELOCALEDIR);51 bindtextdomain(GETTEXT_PACKAGE, GNOMELOCALEDIR);
51 textdomain(GETTEXT_PACKAGE);52 textdomain(GETTEXT_PACKAGE);
5253
53 // init libnotify
54 if(!notify_init("indicator-datetime-service"))
55 g_critical("libnotify initialization failed");
56
57 // build the state, actions, and menufactory54 // build the state, actions, and menufactory
58 std::shared_ptr<State> state(new State);55 std::shared_ptr<State> state(new State);
59 std::shared_ptr<Settings> live_settings(new LiveSettings);56 std::shared_ptr<Settings> live_settings(new LiveSettings);
@@ -62,11 +59,27 @@
62 state->settings = live_settings;59 state->settings = live_settings;
63 state->clock = live_clock;60 state->clock = live_clock;
64 state->locations.reset(new SettingsLocations(live_settings, live_timezones));61 state->locations.reset(new SettingsLocations(live_settings, live_timezones));
65 state->planner.reset(new PlannerEds);62 state->planner.reset(new PlannerEds(live_clock));
66 state->planner->time = live_clock->localtime();63 state->planner->time = live_clock->localtime();
67 std::shared_ptr<Actions> actions(new LiveActions(state));64 std::shared_ptr<Actions> actions(new LiveActions(state));
68 MenuFactory factory(actions, state);65 MenuFactory factory(actions, state);
6966
67 // snap decisions
68 ClockWatcherImpl clock_watcher(state);
69 Snap snap;
70 clock_watcher.alarm_reached().connect([&snap](const Appointment& appt){
71 auto snap_show = [](const Appointment& a){
72 const char* url;
73 if(!a.url.empty())
74 url = a.url.c_str();
75 else // alarm doesn't have a URl associated with it; use a fallback
76 url = "appid://com.ubuntu.clock/clock/current-user-version";
77 url_dispatch_send(url, nullptr, nullptr);
78 };
79 auto snap_dismiss = [](const Appointment&){};
80 snap(appt, snap_show, snap_dismiss);
81 });
82
70 // create the menus83 // create the menus
71 std::vector<std::shared_ptr<Menu>> menus;84 std::vector<std::shared_ptr<Menu>> menus;
72 for(int i=0, n=Menu::NUM_PROFILES; i<n; i++)85 for(int i=0, n=Menu::NUM_PROFILES; i<n; i++)
7386
=== modified file 'src/menu.cpp'
--- src/menu.cpp 2014-02-05 18:22:42 +0000
+++ src/menu.cpp 2014-02-19 15:36:23 +0000
@@ -22,11 +22,11 @@
22#include <datetime/formatter.h>22#include <datetime/formatter.h>
23#include <datetime/state.h>23#include <datetime/state.h>
2424
25#include <json-glib/json-glib.h>
26
27#include <glib/gi18n.h>25#include <glib/gi18n.h>
28#include <gio/gio.h>26#include <gio/gio.h>
2927
28#include <vector>
29
30namespace unity {30namespace unity {
31namespace indicator {31namespace indicator {
32namespace datetime {32namespace datetime {
@@ -62,7 +62,7 @@
62****/62****/
6363
6464
65#define FALLBACK_ALARM_CLOCK_ICON_NAME "clock"65#define ALARM_ICON_NAME "alarm-clock"
66#define CALENDAR_ICON_NAME "calendar"66#define CALENDAR_ICON_NAME "calendar"
6767
68class MenuImpl: public Menu68class MenuImpl: public Menu
@@ -105,12 +105,15 @@
105 update_section(Appointments); // showing events got toggled105 update_section(Appointments); // showing events got toggled
106 });106 });
107 m_state->planner->upcoming.changed().connect([this](const std::vector<Appointment>&){107 m_state->planner->upcoming.changed().connect([this](const std::vector<Appointment>&){
108 update_section(Appointments); // "upcoming" is the list of Appointments we show108 update_upcoming(); // our m_upcoming is planner->upcoming() filtered by time
109 });109 });
110 m_state->clock->date_changed.connect([this](){110 m_state->clock->date_changed.connect([this](){
111 update_section(Calendar); // need to update the Date menuitem111 update_section(Calendar); // need to update the Date menuitem
112 update_section(Locations); // locations' relative time may have changed112 update_section(Locations); // locations' relative time may have changed
113 });113 });
114 m_state->clock->minute_changed.connect([this](){
115 update_upcoming(); // our m_upcoming is planner->upcoming() filtered by time
116 });
114 m_state->locations->locations.changed().connect([this](const std::vector<Location>&) {117 m_state->locations->locations.changed().connect([this](const std::vector<Location>&) {
115 update_section(Locations); // "locations" is the list of Locations we show118 update_section(Locations); // "locations" is the list of Locations we show
116 });119 });
@@ -133,6 +136,24 @@
133 g_action_group_change_action_state(action_group, action_name.c_str(), state);136 g_action_group_change_action_state(action_group, action_name.c_str(), state);
134 }137 }
135138
139 void update_upcoming()
140 {
141 const auto now = m_state->clock->localtime();
142 const auto next_minute = now.add_full(0,0,0,0,1,-now.seconds());
143
144 std::vector<Appointment> upcoming;
145 for(const auto& a : m_state->planner->upcoming.get())
146 if (next_minute <= a.begin)
147 upcoming.push_back(a);
148
149 if (m_upcoming != upcoming)
150 {
151 m_upcoming.swap(upcoming);
152 update_header(); // show an 'alarm' icon if there are upcoming alarms
153 update_section(Appointments); // "upcoming" is the list of Appointments we show
154 }
155 }
156
136 std::shared_ptr<const State> m_state;157 std::shared_ptr<const State> m_state;
137 std::shared_ptr<Actions> m_actions;158 std::shared_ptr<Actions> m_actions;
138 std::shared_ptr<const Formatter> m_formatter;159 std::shared_ptr<const Formatter> m_formatter;
@@ -141,71 +162,19 @@
141 GVariant* get_serialized_alarm_icon()162 GVariant* get_serialized_alarm_icon()
142 {163 {
143 if (G_UNLIKELY(m_serialized_alarm_icon == nullptr))164 if (G_UNLIKELY(m_serialized_alarm_icon == nullptr))
144 m_serialized_alarm_icon = create_alarm_icon();165 {
166 auto i = g_themed_icon_new_with_default_fallbacks(ALARM_ICON_NAME);
167 m_serialized_alarm_icon = g_icon_serialize(i);
168 g_object_unref(i);
169 }
145170
146 return m_serialized_alarm_icon;171 return m_serialized_alarm_icon;
147 }172 }
148173
174 std::vector<Appointment> m_upcoming;
175
149private:176private:
150177
151 /* try to get the clock app's filename from click. (/$pkgdir/$icon) */
152 static GVariant* create_alarm_icon()
153 {
154 GVariant* serialized = nullptr;
155 gchar* icon_filename = nullptr;
156 gchar* standard_error = nullptr;
157 gchar* pkgdir = nullptr;
158
159 g_spawn_command_line_sync("click pkgdir com.ubuntu.clock", &pkgdir, &standard_error, nullptr, nullptr);
160 g_clear_pointer(&standard_error, g_free);
161 if (pkgdir != nullptr)
162 {
163 gchar* manifest = nullptr;
164 g_strstrip(pkgdir);
165 g_spawn_command_line_sync("click info com.ubuntu.clock", &manifest, &standard_error, nullptr, nullptr);
166 g_clear_pointer(&standard_error, g_free);
167 if (manifest != nullptr)
168 {
169 JsonParser* parser = json_parser_new();
170 if (json_parser_load_from_data(parser, manifest, -1, nullptr))
171 {
172 JsonNode* root = json_parser_get_root(parser); /* transfer-none */
173 if ((root != nullptr) && (JSON_NODE_TYPE(root) == JSON_NODE_OBJECT))
174 {
175 JsonObject* o = json_node_get_object(root); /* transfer-none */
176 const gchar* icon_name = json_object_get_string_member(o, "icon");
177 if (icon_name != nullptr)
178 icon_filename = g_build_filename(pkgdir, icon_name, nullptr);
179 }
180 }
181 g_object_unref(parser);
182 g_free(manifest);
183 }
184 g_free(pkgdir);
185 }
186
187 if (icon_filename != nullptr)
188 {
189 GFile* file = g_file_new_for_path(icon_filename);
190 GIcon* icon = g_file_icon_new(file);
191
192 serialized = g_icon_serialize(icon);
193
194 g_object_unref(icon);
195 g_object_unref(file);
196 g_free(icon_filename);
197 }
198
199 if (serialized == nullptr)
200 {
201 auto i = g_themed_icon_new_with_default_fallbacks(FALLBACK_ALARM_CLOCK_ICON_NAME);
202 serialized = g_icon_serialize(i);
203 g_object_unref(i);
204 }
205
206 return serialized;
207 }
208
209 GVariant* get_serialized_calendar_icon()178 GVariant* get_serialized_calendar_icon()
210 {179 {
211 if (G_UNLIKELY(m_serialized_calendar_icon == nullptr))180 if (G_UNLIKELY(m_serialized_calendar_icon == nullptr))
@@ -273,7 +242,7 @@
273 // add calendar242 // add calendar
274 if (show_calendar)243 if (show_calendar)
275 {244 {
276 item = g_menu_item_new ("[calendar]", NULL);245 item = g_menu_item_new ("[calendar]", nullptr);
277 v = g_variant_new_int64(0);246 v = g_variant_new_int64(0);
278 g_menu_item_set_action_and_target_value (item, "indicator.calendar", v);247 g_menu_item_set_action_and_target_value (item, "indicator.calendar", v);
279 g_menu_item_set_attribute (item, "x-canonical-type",248 g_menu_item_set_attribute (item, "x-canonical-type",
@@ -296,11 +265,13 @@
296 const int MAX_APPTS = 5;265 const int MAX_APPTS = 5;
297 std::set<std::string> added;266 std::set<std::string> added;
298267
299 for (const auto& appt : m_state->planner->upcoming.get())268 for (const auto& appt : m_upcoming)
300 {269 {
270 // don't show too many
301 if (n++ >= MAX_APPTS)271 if (n++ >= MAX_APPTS)
302 break;272 break;
303273
274 // don't show duplicates
304 if (added.count(appt.uid))275 if (added.count(appt.uid))
305 continue;276 continue;
306277
@@ -508,7 +479,7 @@
508 {479 {
509 // are there alarms?480 // are there alarms?
510 bool has_alarms = false;481 bool has_alarms = false;
511 for(const auto& appointment : m_state->planner->upcoming.get())482 for(const auto& appointment : m_upcoming)
512 if((has_alarms = appointment.has_alarms))483 if((has_alarms = appointment.has_alarms))
513 break;484 break;
514485
515486
=== modified file 'src/planner-eds.cpp'
--- src/planner-eds.cpp 2014-01-31 00:33:14 +0000
+++ src/planner-eds.cpp 2014-02-19 15:36:23 +0000
@@ -26,6 +26,9 @@
26#include <libecal/libecal.h>26#include <libecal/libecal.h>
27#include <libedataserver/libedataserver.h>27#include <libedataserver/libedataserver.h>
2828
29#include <map>
30#include <set>
31
29namespace unity {32namespace unity {
30namespace indicator {33namespace indicator {
31namespace datetime {34namespace datetime {
@@ -34,25 +37,28 @@
34*****37*****
35****/38****/
3639
37G_DEFINE_QUARK("source-client", source_client)
38
39
40class PlannerEds::Impl40class PlannerEds::Impl
41{41{
42public:42public:
4343
44 Impl(PlannerEds& owner):44 Impl(PlannerEds& owner, const std::shared_ptr<Clock>& clock):
45 m_owner(owner),45 m_owner(owner),
46 m_clock(clock),
46 m_cancellable(g_cancellable_new())47 m_cancellable(g_cancellable_new())
47 {48 {
48 e_source_registry_new(m_cancellable, on_source_registry_ready, this);49 e_source_registry_new(m_cancellable, on_source_registry_ready, this);
4950
51 m_clock->minute_changed.connect([this](){
52 g_debug("rebuilding upcoming because the clock's minute_changed");
53 rebuild_soon(UPCOMING);
54 });
55
50 m_owner.time.changed().connect([this](const DateTime& dt) {56 m_owner.time.changed().connect([this](const DateTime& dt) {
51 g_debug("planner's datetime property changed to %s; calling rebuildSoon()", dt.format("%F %T").c_str());57 g_debug("planner's datetime property changed to %s; calling rebuild_soon()", dt.format("%F %T").c_str());
52 rebuildSoon();58 rebuild_soon(MONTH);
53 });59 });
5460
55 rebuildSoon();61 rebuild_soon(ALL);
56 }62 }
5763
58 ~Impl()64 ~Impl()
@@ -60,6 +66,9 @@
60 g_cancellable_cancel(m_cancellable);66 g_cancellable_cancel(m_cancellable);
61 g_clear_object(&m_cancellable);67 g_clear_object(&m_cancellable);
6268
69 while(!m_sources.empty())
70 remove_source(*m_sources.begin());
71
63 if (m_rebuild_tag)72 if (m_rebuild_tag)
64 g_source_remove(m_rebuild_tag);73 g_source_remove(m_rebuild_tag);
6574
@@ -83,26 +92,31 @@
83 }92 }
84 else93 else
85 {94 {
95 g_signal_connect(r, "source-added", G_CALLBACK(on_source_added), gself);
96 g_signal_connect(r, "source-removed", G_CALLBACK(on_source_removed), gself);
97 g_signal_connect(r, "source-changed", G_CALLBACK(on_source_changed), gself);
98 g_signal_connect(r, "source-disabled", G_CALLBACK(on_source_disabled), gself);
99 g_signal_connect(r, "source-enabled", G_CALLBACK(on_source_enabled), gself);
100
86 auto self = static_cast<Impl*>(gself);101 auto self = static_cast<Impl*>(gself);
87
88 g_signal_connect(r, "source-added", G_CALLBACK(on_source_added), self);
89 g_signal_connect(r, "source-removed", G_CALLBACK(on_source_removed), self);
90 g_signal_connect(r, "source-changed", G_CALLBACK(on_source_changed), self);
91 g_signal_connect(r, "source-disabled", G_CALLBACK(on_source_disabled), self);
92 g_signal_connect(r, "source-enabled", G_CALLBACK(on_source_enabled), self);
93
94 self->m_source_registry = r;102 self->m_source_registry = r;
95103 self->add_sources_by_extension(E_SOURCE_EXTENSION_CALENDAR);
96 GList* sources = e_source_registry_list_sources(r, E_SOURCE_EXTENSION_CALENDAR);104 self->add_sources_by_extension(E_SOURCE_EXTENSION_TASK_LIST);
97 for (auto l=sources; l!=nullptr; l=l->next)
98 on_source_added(r, E_SOURCE(l->data), gself);
99 g_list_free_full(sources, g_object_unref);
100 }105 }
101 }106 }
102107
108 void add_sources_by_extension(const char* extension)
109 {
110 auto& r = m_source_registry;
111 auto sources = e_source_registry_list_sources(r, extension);
112 for (auto l=sources; l!=nullptr; l=l->next)
113 on_source_added(r, E_SOURCE(l->data), this);
114 g_list_free_full(sources, g_object_unref);
115 }
116
103 static void on_source_added(ESourceRegistry* registry, ESource* source, gpointer gself)117 static void on_source_added(ESourceRegistry* registry, ESource* source, gpointer gself)
104 {118 {
105 auto self = static_cast<PlannerEds::Impl*>(gself);119 auto self = static_cast<Impl*>(gself);
106120
107 self->m_sources.insert(E_SOURCE(g_object_ref(source)));121 self->m_sources.insert(E_SOURCE(g_object_ref(source)));
108122
@@ -112,10 +126,19 @@
112126
113 static void on_source_enabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)127 static void on_source_enabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)
114 {128 {
115 auto self = static_cast<PlannerEds::Impl*>(gself);129 auto self = static_cast<Impl*>(gself);
116130 ECalClientSourceType source_type;
131
132 if (e_source_has_extension(source, E_SOURCE_EXTENSION_CALENDAR))
133 source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS;
134 else if (e_source_has_extension(source, E_SOURCE_EXTENSION_TASK_LIST))
135 source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS;
136 else
137 g_assert_not_reached();
138
139 g_debug("connecting a client to source %s", e_source_get_uid(source));
117 e_cal_client_connect(source,140 e_cal_client_connect(source,
118 E_CAL_CLIENT_SOURCE_TYPE_EVENTS,141 source_type,
119 self->m_cancellable,142 self->m_cancellable,
120 on_client_connected,143 on_client_connected,
121 gself);144 gself);
@@ -134,45 +157,118 @@
134 }157 }
135 else158 else
136 {159 {
137 // we've got a new connected ECalClient, so store it & notify clients160 // add the client to our collection
138 g_object_set_qdata_full(G_OBJECT(e_client_get_source(client)),161 auto self = static_cast<Impl*>(gself);
139 source_client_quark(),162 g_debug("got a client for %s", e_cal_client_get_local_attachment_store(E_CAL_CLIENT(client)));
140 client,163 self->m_clients[e_client_get_source(client)] = E_CAL_CLIENT(client);
141 g_object_unref);164
142165 // now create a view for it so that we can listen for changes
143 g_debug("client connected; calling rebuildSoon()");166 e_cal_client_get_view (E_CAL_CLIENT(client),
144 static_cast<Impl*>(gself)->rebuildSoon();167 "#t", // match all
145 }168 self->m_cancellable,
169 on_client_view_ready,
170 self);
171
172 g_debug("client connected; calling rebuild_soon()");
173 self->rebuild_soon(ALL);
174 }
175 }
176
177 static void on_client_view_ready (GObject* client, GAsyncResult* res, gpointer gself)
178 {
179 GError* error = nullptr;
180 ECalClientView* view = nullptr;
181
182 if (e_cal_client_get_view_finish (E_CAL_CLIENT(client), res, &view, &error))
183 {
184 // add the view to our collection
185 e_cal_client_view_start(view, &error);
186 g_debug("got a view for %s", e_cal_client_get_local_attachment_store(E_CAL_CLIENT(client)));
187 auto self = static_cast<Impl*>(gself);
188 self->m_views[e_client_get_source(E_CLIENT(client))] = view;
189
190 g_signal_connect(view, "objects-added", G_CALLBACK(on_view_objects_added), self);
191 g_signal_connect(view, "objects-modified", G_CALLBACK(on_view_objects_modified), self);
192 g_signal_connect(view, "objects-removed", G_CALLBACK(on_view_objects_removed), self);
193 g_debug("view connected; calling rebuild_soon()");
194 self->rebuild_soon(ALL);
195 }
196 else if(error != nullptr)
197 {
198 if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
199 g_warning("indicator-datetime cannot get View to EDS client: %s", error->message);
200
201 g_error_free(error);
202 }
203 }
204
205 static void on_view_objects_added(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
206 {
207 g_debug("%s", G_STRFUNC);
208 static_cast<Impl*>(gself)->rebuild_soon(ALL);
209 }
210 static void on_view_objects_modified(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
211 {
212 g_debug("%s", G_STRFUNC);
213 static_cast<Impl*>(gself)->rebuild_soon(ALL);
214 }
215 static void on_view_objects_removed(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
216 {
217 g_debug("%s", G_STRFUNC);
218 static_cast<Impl*>(gself)->rebuild_soon(ALL);
146 }219 }
147220
148 static void on_source_disabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)221 static void on_source_disabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)
149 {222 {
150 gpointer e_cal_client;223 static_cast<Impl*>(gself)->disable_source(source);
151224 }
152 // if this source has a connected ECalClient, remove it & notify clients225 void disable_source(ESource* source)
153 if ((e_cal_client = g_object_steal_qdata(G_OBJECT(source), source_client_quark())))226 {
154 {227 // if an ECalClientView is associated with this source, remove it
155 g_object_unref(e_cal_client);228 auto vit = m_views.find(source);
156229 if (vit != m_views.end())
157 g_debug("source disabled; calling rebuildSoon()");230 {
158 static_cast<Impl*>(gself)->rebuildSoon();231 auto& view = vit->second;
159 }232 e_cal_client_view_stop(view, nullptr);
160 }233 const auto n_disconnected = g_signal_handlers_disconnect_by_data(view, this);
161234 g_warn_if_fail(n_disconnected == 3);
162 static void on_source_removed(ESourceRegistry* registry, ESource* source, gpointer gself)235 g_object_unref(view);
163 {236 m_views.erase(vit);
164 auto self = static_cast<PlannerEds::Impl*>(gself);237 rebuild_soon(ALL);
165238 }
166 on_source_disabled(registry, source, gself);239
167240 // if an ECalClient is associated with this source, remove it
168 self->m_sources.erase(source);241 auto cit = m_clients.find(source);
169 g_object_unref(source);242 if (cit != m_clients.end())
243 {
244 auto& client = cit->second;
245 g_object_unref(client);
246 m_clients.erase(cit);
247 rebuild_soon(ALL);
248 }
249 }
250
251 static void on_source_removed(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)
252 {
253 static_cast<Impl*>(gself)->remove_source(source);
254 }
255 void remove_source(ESource* source)
256 {
257 disable_source(source);
258
259 auto sit = m_sources.find(source);
260 if (sit != m_sources.end())
261 {
262 g_object_unref(*sit);
263 m_sources.erase(sit);
264 rebuild_soon(ALL);
265 }
170 }266 }
171267
172 static void on_source_changed(ESourceRegistry* /*registry*/, ESource* /*source*/, gpointer gself)268 static void on_source_changed(ESourceRegistry* /*registry*/, ESource* /*source*/, gpointer gself)
173 {269 {
174 g_debug("source changed; calling rebuildSoon()");270 g_debug("source changed; calling rebuild_soon()");
175 static_cast<Impl*>(gself)->rebuildSoon();271 static_cast<Impl*>(gself)->rebuild_soon(ALL);
176 }272 }
177273
178private:274private:
@@ -196,58 +292,72 @@
196 task(task_in), client(client_in), color(color_in) {}292 task(task_in), client(client_in), color(color_in) {}
197 };293 };
198294
199 void rebuildSoon()295 void rebuild_soon(int rebuild_flags)
200 {296 {
201 const static guint ARBITRARY_INTERVAL_SECS = 2;297 static const guint ARBITRARY_INTERVAL_SECS = 2;
298
299 m_rebuild_flags |= rebuild_flags;
202300
203 if (m_rebuild_tag == 0)301 if (m_rebuild_tag == 0)
204 m_rebuild_tag = g_timeout_add_seconds(ARBITRARY_INTERVAL_SECS, rebuildNowStatic, this);302 m_rebuild_tag = g_timeout_add_seconds(ARBITRARY_INTERVAL_SECS, rebuild_now_static, this);
205 }303 }
206304
207 static gboolean rebuildNowStatic(gpointer gself)305 static gboolean rebuild_now_static(gpointer gself)
208 {306 {
209 auto self = static_cast<Impl*>(gself);307 auto self = static_cast<Impl*>(gself);
308 const auto flags = self->m_rebuild_flags;
210 self->m_rebuild_tag = 0;309 self->m_rebuild_tag = 0;
211 self->rebuildNow();310 self->m_rebuild_flags = 0;
311 self->rebuild_now(flags);
212 return G_SOURCE_REMOVE;312 return G_SOURCE_REMOVE;
213 }313 }
214314
215 void rebuildNow()315 void rebuild_now(int rebuild_flags)
216 {316 {
217 const auto calendar_date = m_owner.time.get().get();317 if (rebuild_flags & UPCOMING)
218 GDateTime* begin;318 rebuild_upcoming();
219 GDateTime* end;319
220 int y, m, d;320 if (rebuild_flags & MONTH)
221321 rebuild_month();
222 // get all the appointments in the calendar month322 }
223 g_date_time_get_ymd(calendar_date, &y, &m, &d);323
224 begin = g_date_time_new_local(y, m, 1, 0, 0, 0.1);324 void rebuild_month()
225 end = g_date_time_new_local(y, m, g_date_get_days_in_month(GDateMonth(m),GDateYear(y)), 23, 59, 59.9);325 {
226 if (begin && end)326 const auto ref = m_owner.time.get().get();
227 {327 auto month_begin = g_date_time_add_full(ref,
228 getAppointments(begin, end, [this](const std::vector<Appointment>& appointments) {328 0, // subtract no years
229 g_debug("got %d appointments in this calendar month", (int)appointments.size());329 0, // subtract no months
230 m_owner.this_month.set(appointments);330 -(g_date_time_get_day_of_month(ref)-1),
231 });331 -g_date_time_get_hour(ref),
232 }332 -g_date_time_get_minute(ref),
233 g_clear_pointer(&begin, g_date_time_unref);333 -g_date_time_get_seconds(ref));
234 g_clear_pointer(&end, g_date_time_unref);334 auto month_end = g_date_time_add_full(month_begin, 0, 1, 0, 0, 0, -0.1);
235335
236 // get the upcoming appointments336 get_appointments(month_begin, month_end, [this](const std::vector<Appointment>& appointments) {
237 begin = g_date_time_ref(calendar_date);337 g_debug("got %d appointments in this calendar month", (int)appointments.size());
238 end = g_date_time_add_months(begin, 1);338 m_owner.this_month.set(appointments);
239 if (begin && end)339 });
240 {340
241 getAppointments(begin, end, [this](const std::vector<Appointment>& appointments) {341 g_date_time_unref(month_end);
242 g_debug("got %d upcoming appointments", (int)appointments.size());342 g_date_time_unref(month_begin);
243 m_owner.upcoming.set(appointments);343 }
244 });344
245 }345 void rebuild_upcoming()
246 g_clear_pointer(&begin, g_date_time_unref);346 {
247 g_clear_pointer(&end, g_date_time_unref);347 const auto ref = m_clock->localtime();
248 }348 const auto begin = g_date_time_add_minutes(ref.get(),-10);
249349 const auto end = g_date_time_add_months(begin,1);
250 void getAppointments(GDateTime* begin_dt, GDateTime* end_dt, appointment_func func)350
351 get_appointments(begin, end, [this](const std::vector<Appointment>& appointments) {
352 g_debug("got %d upcoming appointments", (int)appointments.size());
353 m_owner.upcoming.set(appointments);
354 });
355
356 g_date_time_unref(end);
357 g_date_time_unref(begin);
358 }
359
360 void get_appointments(GDateTime* begin_dt, GDateTime* end_dt, appointment_func func)
251 {361 {
252 const auto begin = g_date_time_to_unix(begin_dt);362 const auto begin = g_date_time_to_unix(begin_dt);
253 const auto end = g_date_time_to_unix(end_dt);363 const auto end = g_date_time_to_unix(end_dt);
@@ -286,16 +396,14 @@
286 delete task;396 delete task;
287 });397 });
288398
289 for (auto& source : m_sources)399 for (auto& kv : m_clients)
290 {400 {
291 auto client = E_CAL_CLIENT(g_object_get_qdata(G_OBJECT(source), source_client_quark()));401 auto& client = kv.second;
292 if (client == nullptr)
293 continue;
294
295 if (default_timezone != nullptr)402 if (default_timezone != nullptr)
296 e_cal_client_set_default_timezone(client, default_timezone);403 e_cal_client_set_default_timezone(client, default_timezone);
297404
298 // start a new subtask to enumerate all the components in this client.405 // start a new subtask to enumerate all the components in this client.
406 auto& source = kv.first;
299 auto extension = e_source_get_extension(source, E_SOURCE_EXTENSION_CALENDAR);407 auto extension = e_source_get_extension(source, E_SOURCE_EXTENSION_CALENDAR);
300 const auto color = e_source_selectable_get_color(E_SOURCE_SELECTABLE(extension));408 const auto color = e_source_selectable_get_color(E_SOURCE_SELECTABLE(extension));
301 g_debug("calling e_cal_client_generate_instances for %p", (void*)client);409 g_debug("calling e_cal_client_generate_instances for %p", (void*)client);
@@ -407,16 +515,19 @@
407 delete subtask;515 delete subtask;
408 }516 }
409517
410private:
411
412 PlannerEds& m_owner;518 PlannerEds& m_owner;
519 std::shared_ptr<Clock> m_clock;
413 std::set<ESource*> m_sources;520 std::set<ESource*> m_sources;
414 GCancellable * m_cancellable = nullptr;521 std::map<ESource*,ECalClient*> m_clients;
415 ESourceRegistry * m_source_registry = nullptr;522 std::map<ESource*,ECalClientView*> m_views;
523 GCancellable* m_cancellable = nullptr;
524 ESourceRegistry* m_source_registry = nullptr;
416 guint m_rebuild_tag = 0;525 guint m_rebuild_tag = 0;
526 guint m_rebuild_flags = 0;
527 enum { UPCOMING=(1<<0), MONTH=(1<<1), ALL=UPCOMING|MONTH };
417};528};
418529
419PlannerEds::PlannerEds(): p(new Impl(*this)) {}530PlannerEds::PlannerEds(const std::shared_ptr<Clock>& clock): p(new Impl(*this, clock)) {}
420531
421PlannerEds::~PlannerEds() =default;532PlannerEds::~PlannerEds() =default;
422533
423534
=== added file 'src/snap.cpp'
--- src/snap.cpp 1970-01-01 00:00:00 +0000
+++ src/snap.cpp 2014-02-19 15:36:23 +0000
@@ -0,0 +1,256 @@
1/*
2 * Copyright 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3, as published
6 * by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranties of
10 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 * PURPOSE. See the GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors:
17 * Charles Kerr <charles.kerr@canonical.com>
18 */
19
20#include <datetime/appointment.h>
21#include <datetime/formatter.h>
22#include <datetime/snap.h>
23
24#include <canberra.h>
25#include <libnotify/notify.h>
26
27#include <glib/gi18n.h>
28#include <glib.h>
29
30#define ALARM_SOUND_FILENAME "/usr/share/sounds/ubuntu/stereo/phone-incoming-call.ogg"
31
32namespace unity {
33namespace indicator {
34namespace datetime {
35
36/***
37****
38***/
39
40namespace
41{
42
43/**
44*** libcanberra -- play sounds
45**/
46
47// arbitrary number, but we need a consistent id for play/cancel
48const int32_t alarm_ca_id = 1;
49
50gboolean media_cached = FALSE;
51ca_context *c_context = nullptr;
52guint timeout_tag = 0;
53
54ca_context* get_ca_context()
55{
56 if (G_UNLIKELY(c_context == nullptr))
57 {
58 int rv;
59
60 if ((rv = ca_context_create(&c_context)) != CA_SUCCESS)
61 {
62 g_warning("Failed to create canberra context: %s\n", ca_strerror(rv));
63 c_context = nullptr;
64 }
65 else
66 {
67 const char* filename = ALARM_SOUND_FILENAME;
68 rv = ca_context_cache(c_context,
69 CA_PROP_EVENT_ID, "alarm",
70 CA_PROP_MEDIA_FILENAME, filename,
71 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
72 NULL);
73 media_cached = rv == CA_SUCCESS;
74 if (rv != CA_SUCCESS)
75 g_warning("Couldn't add '%s' to canberra cache: %s", filename, ca_strerror(rv));
76 }
77 }
78
79 return c_context;
80}
81
82void play_alarm_sound();
83
84gboolean play_alarm_sound_idle (gpointer)
85{
86 timeout_tag = 0;
87 play_alarm_sound();
88 return G_SOURCE_REMOVE;
89}
90
91void on_alarm_play_done (ca_context* /*context*/, uint32_t /*id*/, int rv, void* /*user_data*/)
92{
93 // wait one second, then play it again
94 if ((rv == CA_SUCCESS) && (timeout_tag == 0))
95 timeout_tag = g_timeout_add_seconds (1, play_alarm_sound_idle, nullptr);
96}
97
98void play_alarm_sound()
99{
100 const gchar* filename = ALARM_SOUND_FILENAME;
101 auto context = get_ca_context();
102 g_return_if_fail(context != nullptr);
103
104 ca_proplist* props = nullptr;
105 ca_proplist_create(&props);
106 if (media_cached)
107 ca_proplist_sets(props, CA_PROP_EVENT_ID, "alarm");
108 ca_proplist_sets(props, CA_PROP_MEDIA_FILENAME, filename);
109
110 const auto rv = ca_context_play_full(context, alarm_ca_id, props, on_alarm_play_done, nullptr);
111 if (rv != CA_SUCCESS)
112 g_warning("Failed to play file '%s': %s", filename, ca_strerror(rv));
113
114 g_clear_pointer(&props, ca_proplist_destroy);
115}
116
117void stop_alarm_sound()
118{
119 auto context = get_ca_context();
120 if (context != nullptr)
121 {
122 const auto rv = ca_context_cancel(context, alarm_ca_id);
123 if (rv != CA_SUCCESS)
124 g_warning("Failed to cancel alarm sound: %s", ca_strerror(rv));
125 }
126
127 if (timeout_tag != 0)
128 {
129 g_source_remove(timeout_tag);
130 timeout_tag = 0;
131 }
132}
133
134/**
135*** libnotify -- snap decisions
136**/
137
138void first_time_init()
139{
140 static bool inited = false;
141
142 if (G_UNLIKELY(!inited))
143 {
144 inited = true;
145
146 if(!notify_init("indicator-datetime-service"))
147 g_critical("libnotify initialization failed");
148 }
149}
150
151struct SnapData
152{
153 Snap::appointment_func show;
154 Snap::appointment_func dismiss;
155 Appointment appointment;
156};
157
158void on_snap_show(NotifyNotification*, gchar* /*action*/, gpointer gdata)
159{
160 stop_alarm_sound();
161 auto data = static_cast<SnapData*>(gdata);
162 data->show(data->appointment);
163}
164
165void on_snap_dismiss(NotifyNotification*, gchar* /*action*/, gpointer gdata)
166{
167 stop_alarm_sound();
168 auto data = static_cast<SnapData*>(gdata);
169 data->dismiss(data->appointment);
170}
171
172void snap_data_destroy_notify(gpointer gdata)
173{
174 delete static_cast<SnapData*>(gdata);
175}
176
177void show_snap_decision(SnapData* data)
178{
179 const Appointment& appointment = data->appointment;
180
181 const auto timestr = appointment.begin.format("%a, %X");
182 auto title = g_strdup_printf(_("Alarm %s"), timestr.c_str());
183 const auto body = appointment.summary;
184 const gchar* icon_name = "alarm-clock";
185
186 auto nn = notify_notification_new(title, body.c_str(), icon_name);
187 notify_notification_set_hint_string(nn, "x-canonical-snap-decisions", "true");
188 notify_notification_set_hint_string(nn, "x-canonical-private-button-tint", "true");
189 notify_notification_add_action(nn, "show", _("Show"), on_snap_show, data, nullptr);
190 notify_notification_add_action(nn, "dismiss", _("Dismiss"), on_snap_dismiss, data, nullptr);
191 g_object_set_data_full(G_OBJECT(nn), "snap-data", data, snap_data_destroy_notify);
192
193 GError * error = nullptr;
194 notify_notification_show(nn, &error);
195 if (error != NULL)
196 {
197 g_warning("Unable to show snap decision for '%s': %s", body.c_str(), error->message);
198 g_error_free(error);
199 data->show(data->appointment);
200 }
201
202 g_free(title);
203}
204
205/**
206***
207**/
208
209void notify(const Appointment& appointment,
210 Snap::appointment_func show,
211 Snap::appointment_func dismiss)
212{
213 auto data = new SnapData;
214 data->appointment = appointment;
215 data->show = show;
216 data->dismiss = dismiss;
217
218 play_alarm_sound();
219 show_snap_decision(data);
220}
221
222} // unnamed namespace
223
224
225/***
226****
227***/
228
229Snap::Snap()
230{
231 first_time_init();
232}
233
234Snap::~Snap()
235{
236 media_cached = false;
237 g_clear_pointer(&c_context, ca_context_destroy);
238}
239
240void Snap::operator()(const Appointment& appointment,
241 appointment_func show,
242 appointment_func dismiss)
243{
244 if (appointment.has_alarms)
245 notify(appointment, show, dismiss);
246 else
247 dismiss(appointment);
248}
249
250/***
251****
252***/
253
254} // namespace datetime
255} // namespace indicator
256} // namespace unity
0257
=== modified file 'src/timezone-file.cpp'
--- src/timezone-file.cpp 2014-02-19 15:36:23 +0000
+++ src/timezone-file.cpp 2014-02-19 15:36:23 +0000
@@ -32,7 +32,7 @@
3232
33FileTimezone::FileTimezone(const std::string& filename)33FileTimezone::FileTimezone(const std::string& filename)
34{34{
35 setFilename(filename);35 set_filename(filename);
36}36}
3737
38FileTimezone::~FileTimezone()38FileTimezone::~FileTimezone()
@@ -52,7 +52,7 @@
52}52}
5353
54void54void
55FileTimezone::setFilename(const std::string& filename)55FileTimezone::set_filename(const std::string& filename)
56{56{
57 clear();57 clear();
5858
@@ -79,7 +79,7 @@
79 }79 }
80 else80 else
81 {81 {
82 m_monitor_handler_id = g_signal_connect_swapped(m_monitor, "changed", G_CALLBACK(onFileChanged), this);82 m_monitor_handler_id = g_signal_connect_swapped(m_monitor, "changed", G_CALLBACK(on_file_changed), this);
83 g_debug("%s Monitoring timezone file '%s'", G_STRLOC, m_filename.c_str());83 g_debug("%s Monitoring timezone file '%s'", G_STRLOC, m_filename.c_str());
84 }84 }
8585
@@ -87,7 +87,7 @@
87}87}
8888
89void89void
90FileTimezone::onFileChanged(gpointer gself)90FileTimezone::on_file_changed(gpointer gself)
91{91{
92 static_cast<FileTimezone*>(gself)->reload();92 static_cast<FileTimezone*>(gself)->reload();
93}93}
9494
=== modified file 'tests/CMakeLists.txt'
--- tests/CMakeLists.txt 2014-01-29 03:42:05 +0000
+++ tests/CMakeLists.txt 2014-02-19 15:36:23 +0000
@@ -42,6 +42,7 @@
42endfunction()42endfunction()
43add_test_by_name(test-actions)43add_test_by_name(test-actions)
44add_test_by_name(test-clock)44add_test_by_name(test-clock)
45add_test_by_name(test-clock-watcher)
45add_test_by_name(test-exporter)46add_test_by_name(test-exporter)
46add_test_by_name(test-formatter)47add_test_by_name(test-formatter)
47add_test_by_name(test-live-actions)48add_test_by_name(test-live-actions)
@@ -52,6 +53,10 @@
52add_test_by_name(test-timezone-file)53add_test_by_name(test-timezone-file)
53add_test_by_name(test-utils)54add_test_by_name(test-utils)
5455
56set (TEST_NAME manual-test-snap)
57add_executable (${TEST_NAME} ${TEST_NAME}.cpp)
58add_dependencies (${TEST_NAME} libindicatordatetimeservice)
59target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS})
5560
56# disabling the timezone unit tests because they require61# disabling the timezone unit tests because they require
57# https://code.launchpad.net/~ted/dbus-test-runner/multi-interface-test/+merge/19972462# https://code.launchpad.net/~ted/dbus-test-runner/multi-interface-test/+merge/199724
5863
=== modified file 'tests/geoclue-fixture.h'
--- tests/geoclue-fixture.h 2014-01-17 03:23:57 +0000
+++ tests/geoclue-fixture.h 2014-02-19 15:36:23 +0000
@@ -95,7 +95,7 @@
9595
96 // I've looked and can't find where this extra ref is coming from.96 // I've looked and can't find where this extra ref is coming from.
97 // is there an unbalanced ref to the bus in the test harness?!97 // is there an unbalanced ref to the bus in the test harness?!
98 while (bus != NULL)98 while (bus != nullptr)
99 {99 {
100 g_object_unref (bus);100 g_object_unref (bus);
101 wait_msec (1000);101 wait_msec (1000);
102102
=== added file 'tests/manual-test-snap.cpp'
--- tests/manual-test-snap.cpp 1970-01-01 00:00:00 +0000
+++ tests/manual-test-snap.cpp 2014-02-19 15:36:23 +0000
@@ -0,0 +1,63 @@
1
2/*
3 * Copyright 2013 Canonical Ltd.
4 *
5 * Authors:
6 * Charles Kerr <charles.kerr@canonical.com>
7 *
8 * This program is free software: you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License version 3, as published
10 * by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranties of
14 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#include <datetime/appointment.h>
22#include <datetime/snap.h>
23
24#include <glib.h>
25
26using namespace unity::indicator::datetime;
27
28/***
29****
30***/
31
32int main()
33{
34 Appointment a;
35 a.color = "green";
36 a.summary = "Alarm";
37 a.url = "alarm:///hello-world";
38 a.uid = "D4B57D50247291478ED31DED17FF0A9838DED402";
39 a.is_event = false;
40 a.is_daily = false;
41 a.has_alarms = true;
42 auto begin = g_date_time_new_local(2014,12,25,0,0,0);
43 auto end = g_date_time_add_full(begin,0,0,1,0,0,-1);
44 a.begin = begin;
45 a.end = end;
46 g_date_time_unref(end);
47 g_date_time_unref(begin);
48
49 auto loop = g_main_loop_new(nullptr, false);
50 auto show = [loop](const Appointment& appt){
51 g_message("You clicked 'show' for appt url '%s'", appt.url.c_str());
52 g_main_loop_quit(loop);
53 };
54 auto dismiss = [loop](const Appointment&){
55 g_message("You clicked 'dismiss'");
56 g_main_loop_quit(loop);
57 };
58
59 Snap snap;
60 snap(a, show, dismiss);
61 g_main_loop_run(loop);
62 return 0;
63}
064
=== added file 'tests/test-clock-watcher.cpp'
--- tests/test-clock-watcher.cpp 1970-01-01 00:00:00 +0000
+++ tests/test-clock-watcher.cpp 2014-02-19 15:36:23 +0000
@@ -0,0 +1,166 @@
1/*
2 * Copyright 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3, as published
6 * by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranties of
10 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 * PURPOSE. See the GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors:
17 * Charles Kerr <charles.kerr@canonical.com>
18 */
19
20#include <datetime/clock-watcher.h>
21
22#include <gtest/gtest.h>
23
24#include "state-fixture.h"
25
26using namespace unity::indicator::datetime;
27
28class ClockWatcherFixture: public StateFixture
29{
30private:
31
32 typedef StateFixture super;
33
34protected:
35
36 std::vector<std::string> m_triggered;
37 std::unique_ptr<ClockWatcher> m_watcher;
38
39 void SetUp()
40 {
41 super::SetUp();
42
43 m_watcher.reset(new ClockWatcherImpl(m_state));
44 m_watcher->alarm_reached().connect([this](const Appointment& appt){
45 m_triggered.push_back(appt.uid);
46 });
47
48 EXPECT_TRUE(m_triggered.empty());
49 }
50
51 void TearDown()
52 {
53 m_triggered.clear();
54 m_watcher.reset();
55
56 super::TearDown();
57 }
58
59 std::vector<Appointment> build_some_appointments()
60 {
61 const auto now = m_state->clock->localtime();
62 auto tomorrow = g_date_time_add_days (now.get(), 1);
63 auto tomorrow_begin = g_date_time_add_full (tomorrow, 0, 0, 0,
64 -g_date_time_get_hour(tomorrow),
65 -g_date_time_get_minute(tomorrow),
66 -g_date_time_get_seconds(tomorrow));
67 auto tomorrow_end = g_date_time_add_full (tomorrow_begin, 0, 0, 1, 0, 0, -1);
68
69 Appointment a1; // an alarm clock appointment
70 a1.color = "red";
71 a1.summary = "Alarm";
72 a1.summary = "http://www.example.com/";
73 a1.uid = "example";
74 a1.has_alarms = true;
75 a1.begin = tomorrow_begin;
76 a1.end = tomorrow_end;
77
78 auto ubermorgen_begin = g_date_time_add_days (tomorrow, 1);
79 auto ubermorgen_end = g_date_time_add_full (tomorrow_begin, 0, 0, 1, 0, 0, -1);
80
81 Appointment a2; // a non-alarm appointment
82 a2.color = "green";
83 a2.summary = "Other Text";
84 a2.summary = "http://www.monkey.com/";
85 a2.uid = "monkey";
86 a2.has_alarms = false;
87 a2.begin = ubermorgen_begin;
88 a2.end = ubermorgen_end;
89
90 // cleanup
91 g_date_time_unref(ubermorgen_end);
92 g_date_time_unref(ubermorgen_begin);
93 g_date_time_unref(tomorrow_end);
94 g_date_time_unref(tomorrow_begin);
95 g_date_time_unref(tomorrow);
96
97 return std::vector<Appointment>({a1, a2});
98 }
99};
100
101/***
102****
103***/
104
105TEST_F(ClockWatcherFixture, AppointmentsChanged)
106{
107 // Add some appointments to the planner.
108 // One of these matches our state's localtime, so that should get triggered.
109 std::vector<Appointment> a = build_some_appointments();
110 a[0].begin = m_state->clock->localtime();
111 m_state->planner->upcoming.set(a);
112
113 // Confirm that it got fired
114 EXPECT_EQ(1, m_triggered.size());
115 EXPECT_EQ(a[0].uid, m_triggered[0]);
116}
117
118
119TEST_F(ClockWatcherFixture, TimeChanged)
120{
121 // Add some appointments to the planner.
122 // Neither of these match the state's localtime, so nothing should be triggered.
123 std::vector<Appointment> a = build_some_appointments();
124 m_state->planner->upcoming.set(a);
125 EXPECT_TRUE(m_triggered.empty());
126
127 // Set the state's clock to a time that matches one of the appointments.
128 // That appointment should get triggered.
129 m_mock_state->mock_clock->set_localtime(a[1].begin);
130 EXPECT_EQ(1, m_triggered.size());
131 EXPECT_EQ(a[1].uid, m_triggered[0]);
132}
133
134
135TEST_F(ClockWatcherFixture, MoreThanOne)
136{
137 const auto now = m_state->clock->localtime();
138 std::vector<Appointment> a = build_some_appointments();
139 a[0].begin = a[1].begin = now;
140 m_state->planner->upcoming.set(a);
141
142 EXPECT_EQ(2, m_triggered.size());
143 EXPECT_EQ(a[0].uid, m_triggered[0]);
144 EXPECT_EQ(a[1].uid, m_triggered[1]);
145}
146
147
148TEST_F(ClockWatcherFixture, NoDuplicates)
149{
150 // Setup: add an appointment that gets triggered.
151 const auto now = m_state->clock->localtime();
152 const std::vector<Appointment> appointments = build_some_appointments();
153 std::vector<Appointment> a;
154 a.push_back(appointments[0]);
155 a[0].begin = now;
156 m_state->planner->upcoming.set(a);
157 EXPECT_EQ(1, m_triggered.size());
158 EXPECT_EQ(a[0].uid, m_triggered[0]);
159
160 // Now change the appointment vector by adding one to it.
161 // Confirm that the ClockWatcher doesn't re-trigger a[0]
162 a.push_back(appointments[1]);
163 m_state->planner->upcoming.set(a);
164 EXPECT_EQ(1, m_triggered.size());
165 EXPECT_EQ(a[0].uid, m_triggered[0]);
166}
0167
=== modified file 'tests/test-clock.cpp'
--- tests/test-clock.cpp 2014-01-31 00:33:14 +0000
+++ tests/test-clock.cpp 2014-02-19 15:36:23 +0000
@@ -37,12 +37,12 @@
37 void emitPrepareForSleep()37 void emitPrepareForSleep()
38 {38 {
39 g_dbus_connection_emit_signal(g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr),39 g_dbus_connection_emit_signal(g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr),
40 NULL,40 nullptr,
41 "/org/freedesktop/login1", // object path41 "/org/freedesktop/login1", // object path
42 "org.freedesktop.login1.Manager", // interface42 "org.freedesktop.login1.Manager", // interface
43 "PrepareForSleep", // signal name43 "PrepareForSleep", // signal name
44 g_variant_new("(b)", FALSE),44 g_variant_new("(b)", FALSE),
45 NULL);45 nullptr);
46 }46 }
47};47};
4848
4949
=== modified file 'tests/test-planner.cpp'
--- tests/test-planner.cpp 2014-01-31 00:33:14 +0000
+++ tests/test-planner.cpp 2014-02-19 15:36:23 +0000
@@ -28,9 +28,7 @@
28#include <langinfo.h>28#include <langinfo.h>
29#include <locale.h>29#include <locale.h>
3030
31using unity::indicator::datetime::Appointment;31using namespace unity::indicator::datetime;
32using unity::indicator::datetime::DateTime;
33using unity::indicator::datetime::PlannerEds;
3432
35/***33/***
36****34****
@@ -40,11 +38,15 @@
4038
41TEST_F(PlannerFixture, EDS)39TEST_F(PlannerFixture, EDS)
42{40{
43 PlannerEds planner;41 auto tmp = g_date_time_new_now_local();
42 const auto now = DateTime(tmp);
43 g_date_time_unref(tmp);
44
45 std::shared_ptr<Clock> clock(new MockClock(now));
46 PlannerEds planner(clock);
44 wait_msec(100);47 wait_msec(100);
4548
46 auto now = g_date_time_new_now_local();49 planner.time.set(now);
47 planner.time.set(DateTime(now));
48 wait_msec(2500);50 wait_msec(2500);
4951
50 std::vector<Appointment> this_month = planner.this_month.get();52 std::vector<Appointment> this_month = planner.this_month.get();
5153
=== modified file 'tests/test-settings.cpp'
--- tests/test-settings.cpp 2014-01-31 00:40:13 +0000
+++ tests/test-settings.cpp 2014-02-19 15:36:23 +0000
@@ -167,8 +167,8 @@
167{167{
168 const auto key = SETTINGS_LOCATIONS_S;168 const auto key = SETTINGS_LOCATIONS_S;
169169
170 const gchar* astrv[] = {"America/Los_Angeles Oakland", "America/Chicago Oklahoma City", "Europe/London London", NULL};170 const gchar* astrv[] = {"America/Los_Angeles Oakland", "America/Chicago Oklahoma City", "Europe/London London", nullptr};
171 const gchar* bstrv[] = {"America/Denver", "Europe/London London", "Europe/Berlin Berlin", NULL};171 const gchar* bstrv[] = {"America/Denver", "Europe/London London", "Europe/Berlin Berlin", nullptr};
172 const std::vector<std::string> av = strv_to_vector(astrv);172 const std::vector<std::string> av = strv_to_vector(astrv);
173 const std::vector<std::string> bv = strv_to_vector(bstrv);173 const std::vector<std::string> bv = strv_to_vector(bstrv);
174 174
175175
=== modified file 'tests/test-utils.cpp'
--- tests/test-utils.cpp 2014-01-27 07:26:02 +0000
+++ tests/test-utils.cpp 2014-02-19 15:36:23 +0000
@@ -59,7 +59,7 @@
59 const char* location;59 const char* location;
60 const char* expected_name;60 const char* expected_name;
61 } beautify_timezone_test_cases[] = {61 } beautify_timezone_test_cases[] = {
62 { "America/Chicago", NULL, "Chicago" },62 { "America/Chicago", nullptr, "Chicago" },
63 { "America/Chicago", "America/Chicago", "Chicago" },63 { "America/Chicago", "America/Chicago", "Chicago" },
64 { "America/Chicago", "America/Chigago Chicago", "Chicago" },64 { "America/Chicago", "America/Chigago Chicago", "Chicago" },
65 { "America/Chicago", "America/Chicago Oklahoma City", "Oklahoma City" },65 { "America/Chicago", "America/Chicago Oklahoma City", "Oklahoma City" },

Subscribers

People subscribed via source and target branches