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

Proposed by Charles Kerr on 2014-02-03
Status: Merged
Approved by: Ted Gould on 2014-02-06
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) 2014-02-03 Approve on 2014-02-06
PS Jenkins bot (community) continuous-integration Approve on 2014-02-06
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.
397. By Charles Kerr on 2014-02-05

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

398. By Charles Kerr on 2014-02-05

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

399. By Charles Kerr on 2014-02-05

add utils.c to POTFILES.in

400. By Charles Kerr on 2014-02-05

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

401. By Charles Kerr on 2014-02-05

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

402. By Charles Kerr on 2014-02-05

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

403. By Charles Kerr on 2014-02-05

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

404. By Charles Kerr on 2014-02-05

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

405. By Charles Kerr on 2014-02-06

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

Ted Gould (ted) wrote :

Okay, looks good!

review: Approve
406. By Charles Kerr on 2014-02-19

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