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
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2014-02-10 22:59:02 +0000
3+++ CMakeLists.txt 2014-02-19 15:36:23 +0000
4@@ -38,10 +38,10 @@
5 libical>=0.48
6 libecal-1.2>=3.5
7 libedataserver-1.2>=3.5
8+ libcanberra>=0.12
9 libnotify>=0.7.6
10 url-dispatcher-1>=1
11- properties-cpp>=0.0.1
12- json-glib-1.0>=0.16.2)
13+ properties-cpp>=0.0.1)
14 include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS})
15
16 ##
17
18=== modified file 'debian/control'
19--- debian/control 2014-02-11 20:06:28 +0000
20+++ debian/control 2014-02-19 15:36:23 +0000
21@@ -16,13 +16,13 @@
22 libgtest-dev,
23 libglib2.0-dev (>= 2.35.4),
24 libnotify-dev (>= 0.7.6),
25+ libcanberra-dev,
26 libido3-0.1-dev (>= 0.2.90),
27 libgeoclue-dev (>= 0.12.0),
28 libecal1.2-dev (>= 3.5),
29 libical-dev (>= 1.0),
30 libgtk-3-dev (>= 3.1.4),
31 libcairo2-dev (>= 1.10),
32- libjson-glib-dev,
33 libpolkit-gobject-1-dev,
34 libedataserver1.2-dev (>= 3.5),
35 libgconf2-dev (>= 2.31),
36
37=== added file 'include/datetime/clock-watcher.h'
38--- include/datetime/clock-watcher.h 1970-01-01 00:00:00 +0000
39+++ include/datetime/clock-watcher.h 2014-02-19 15:36:23 +0000
40@@ -0,0 +1,72 @@
41+/*
42+ * Copyright 2014 Canonical Ltd.
43+ *
44+ * This program is free software: you can redistribute it and/or modify it
45+ * under the terms of the GNU General Public License version 3, as published
46+ * by the Free Software Foundation.
47+ *
48+ * This program is distributed in the hope that it will be useful, but
49+ * WITHOUT ANY WARRANTY; without even the implied warranties of
50+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
51+ * PURPOSE. See the GNU General Public License for more details.
52+ *
53+ * You should have received a copy of the GNU General Public License along
54+ * with this program. If not, see <http://www.gnu.org/licenses/>.
55+ *
56+ * Authors:
57+ * Charles Kerr <charles.kerr@canonical.com>
58+ */
59+
60+#ifndef INDICATOR_DATETIME_CLOCK_WATCHER_H
61+#define INDICATOR_DATETIME_CLOCK_WATCHER_H
62+
63+#include <datetime/state.h>
64+#include <datetime/appointment.h>
65+
66+#include <core/signal.h>
67+
68+#include <memory>
69+#include <set>
70+#include <string>
71+
72+namespace unity {
73+namespace indicator {
74+namespace datetime {
75+
76+
77+/**
78+ * \brief Watches the clock and appointments to notify when an
79+ * appointment's time is reached.
80+ */
81+class ClockWatcher
82+{
83+public:
84+ ClockWatcher() =default;
85+ virtual ~ClockWatcher() =default;
86+ virtual core::Signal<const Appointment&>& alarm_reached() = 0;
87+};
88+
89+
90+/**
91+ * \brief A #ClockWatcher implementation
92+ */
93+class ClockWatcherImpl: public ClockWatcher
94+{
95+public:
96+ ClockWatcherImpl(const std::shared_ptr<const State>& state);
97+ ~ClockWatcherImpl() =default;
98+ core::Signal<const Appointment&>& alarm_reached();
99+
100+private:
101+ void pulse();
102+ std::set<std::string> m_triggered;
103+ std::shared_ptr<const State> m_state;
104+ core::Signal<const Appointment&> m_alarm_reached;
105+};
106+
107+
108+} // namespace datetime
109+} // namespace indicator
110+} // namespace unity
111+
112+#endif // INDICATOR_DATETIME_CLOCK_WATCHER_H
113
114=== modified file 'include/datetime/date-time.h'
115--- include/datetime/date-time.h 2014-01-30 19:00:22 +0000
116+++ include/datetime/date-time.h 2014-02-19 15:36:23 +0000
117@@ -41,6 +41,7 @@
118 DateTime& operator=(GDateTime* in);
119 DateTime& operator=(const DateTime& in);
120 DateTime to_timezone(const std::string& zone) const;
121+ DateTime add_full(int years, int months, int days, int hours, int minutes, double seconds) const;
122 void reset(GDateTime* in=nullptr);
123
124 GDateTime* get() const;
125@@ -48,9 +49,11 @@
126
127 std::string format(const std::string& fmt) const;
128 int day_of_month() const;
129+ double seconds() const;
130 int64_t to_unix() const;
131
132 bool operator<(const DateTime& that) const;
133+ bool operator<=(const DateTime& that) const;
134 bool operator!=(const DateTime& that) const;
135 bool operator==(const DateTime& that) const;
136
137
138=== modified file 'include/datetime/planner-eds.h'
139--- include/datetime/planner-eds.h 2014-01-15 05:07:10 +0000
140+++ include/datetime/planner-eds.h 2014-02-19 15:36:23 +0000
141@@ -20,9 +20,10 @@
142 #ifndef INDICATOR_DATETIME_PLANNER_EDS_H
143 #define INDICATOR_DATETIME_PLANNER_EDS_H
144
145+#include <datetime/clock.h>
146 #include <datetime/planner.h>
147
148-#include <memory> // unique_ptr
149+#include <memory> // shared_ptr, unique_ptr
150
151 namespace unity {
152 namespace indicator {
153@@ -34,7 +35,7 @@
154 class PlannerEds: public Planner
155 {
156 public:
157- PlannerEds();
158+ PlannerEds(const std::shared_ptr<Clock>& clock);
159 virtual ~PlannerEds();
160
161 private:
162
163=== added file 'include/datetime/snap.h'
164--- include/datetime/snap.h 1970-01-01 00:00:00 +0000
165+++ include/datetime/snap.h 2014-02-19 15:36:23 +0000
166@@ -0,0 +1,51 @@
167+/*
168+ * Copyright 2014 Canonical Ltd.
169+ *
170+ * This program is free software: you can redistribute it and/or modify it
171+ * under the terms of the GNU General Public License version 3, as published
172+ * by the Free Software Foundation.
173+ *
174+ * This program is distributed in the hope that it will be useful, but
175+ * WITHOUT ANY WARRANTY; without even the implied warranties of
176+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
177+ * PURPOSE. See the GNU General Public License for more details.
178+ *
179+ * You should have received a copy of the GNU General Public License along
180+ * with this program. If not, see <http://www.gnu.org/licenses/>.
181+ *
182+ * Authors:
183+ * Charles Kerr <charles.kerr@canonical.com>
184+ */
185+
186+#ifndef INDICATOR_DATETIME_SNAP_H
187+#define INDICATOR_DATETIME_SNAP_H
188+
189+#include <datetime/appointment.h>
190+
191+#include <memory>
192+#include <functional>
193+
194+namespace unity {
195+namespace indicator {
196+namespace datetime {
197+
198+/**
199+ * \brief Pops up Snap Decisions for appointments
200+ */
201+class Snap
202+{
203+public:
204+ Snap();
205+ virtual ~Snap();
206+
207+ typedef std::function<void(const Appointment&)> appointment_func;
208+ void operator()(const Appointment& appointment,
209+ appointment_func show,
210+ appointment_func dismiss);
211+};
212+
213+} // namespace datetime
214+} // namespace indicator
215+} // namespace unity
216+
217+#endif // INDICATOR_DATETIME_SNAP_H
218
219=== modified file 'include/datetime/timezone-file.h'
220--- include/datetime/timezone-file.h 2014-01-30 19:00:22 +0000
221+++ include/datetime/timezone-file.h 2014-02-19 15:36:23 +0000
222@@ -42,8 +42,8 @@
223 ~FileTimezone();
224
225 private:
226- void setFilename(const std::string& filename);
227- static void onFileChanged(gpointer gself);
228+ void set_filename(const std::string& filename);
229+ static void on_file_changed(gpointer gself);
230 void clear();
231 void reload();
232
233
234=== modified file 'po/POTFILES.in'
235--- po/POTFILES.in 2014-02-10 22:59:02 +0000
236+++ po/POTFILES.in 2014-02-19 15:36:23 +0000
237@@ -1,3 +1,5 @@
238 src/formatter.cpp
239 src/formatter-desktop.cpp
240 src/menu.cpp
241+src/snap.cpp
242+src/utils.c
243
244=== modified file 'src/CMakeLists.txt'
245--- src/CMakeLists.txt 2014-02-10 22:59:02 +0000
246+++ src/CMakeLists.txt 2014-02-19 15:36:23 +0000
247@@ -13,6 +13,7 @@
248 appointment.cpp
249 clock.cpp
250 clock-live.cpp
251+ clock-watcher.cpp
252 date-time.cpp
253 exporter.cpp
254 formatter.cpp
255@@ -22,6 +23,7 @@
256 menu.cpp
257 planner-eds.cpp
258 settings-live.cpp
259+ snap.cpp
260 timezone-file.cpp
261 timezone-geoclue.cpp
262 timezones-live.cpp
263
264=== modified file 'src/actions-live.cpp'
265--- src/actions-live.cpp 2014-02-10 22:59:02 +0000
266+++ src/actions-live.cpp 2014-02-19 15:36:23 +0000
267@@ -156,7 +156,7 @@
268
269 GError * err = nullptr;
270 auto proxy = g_dbus_proxy_new_for_bus_finish(res, &err);
271- if (err != NULL)
272+ if (err != nullptr)
273 {
274 if (!g_error_matches(err, G_IO_ERROR, G_IO_ERROR_CANCELLED))
275 g_warning("Could not grab DBus proxy for timedated: %s", err->message);
276
277=== added file 'src/clock-watcher.cpp'
278--- src/clock-watcher.cpp 1970-01-01 00:00:00 +0000
279+++ src/clock-watcher.cpp 2014-02-19 15:36:23 +0000
280@@ -0,0 +1,71 @@
281+/*
282+ * Copyright 2014 Canonical Ltd.
283+ *
284+ * This program is free software: you can redistribute it and/or modify it
285+ * under the terms of the GNU General Public License version 3, as published
286+ * by the Free Software Foundation.
287+ *
288+ * This program is distributed in the hope that it will be useful, but
289+ * WITHOUT ANY WARRANTY; without even the implied warranties of
290+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
291+ * PURPOSE. See the GNU General Public License for more details.
292+ *
293+ * You should have received a copy of the GNU General Public License along
294+ * with this program. If not, see <http://www.gnu.org/licenses/>.
295+ *
296+ * Authors:
297+ * Charles Kerr <charles.kerr@canonical.com>
298+ */
299+
300+#include <datetime/clock-watcher.h>
301+
302+namespace unity {
303+namespace indicator {
304+namespace datetime {
305+
306+/***
307+****
308+***/
309+
310+ClockWatcherImpl::ClockWatcherImpl(const std::shared_ptr<const State>& state):
311+ m_state(state)
312+{
313+ m_state->planner->upcoming.changed().connect([this](const std::vector<Appointment>&){
314+ g_debug("ClockWatcher pulse because upcoming appointments changed");
315+ pulse();
316+ });
317+ m_state->clock->minute_changed.connect([this](){
318+ g_debug("ClockWatcher pulse because clock minute_changed");
319+ pulse();
320+ });
321+ pulse();
322+}
323+
324+core::Signal<const Appointment&>& ClockWatcherImpl::alarm_reached()
325+{
326+ return m_alarm_reached;
327+}
328+
329+void ClockWatcherImpl::pulse()
330+{
331+ const auto now = m_state->clock->localtime();
332+
333+ for(const auto& appointment : m_state->planner->upcoming.get())
334+ {
335+ if (m_triggered.count(appointment.uid))
336+ continue;
337+ if (!DateTime::is_same_minute(now, appointment.begin))
338+ continue;
339+
340+ m_triggered.insert(appointment.uid);
341+ m_alarm_reached(appointment);
342+ }
343+}
344+
345+/***
346+****
347+***/
348+
349+} // namespace datetime
350+} // namespace indicator
351+} // namespace unity
352
353=== modified file 'src/date-time.cpp'
354--- src/date-time.cpp 2014-01-30 19:00:22 +0000
355+++ src/date-time.cpp 2014-02-19 15:36:23 +0000
356@@ -69,6 +69,14 @@
357 return dt;
358 }
359
360+DateTime DateTime::add_full(int years, int months, int days, int hours, int minutes, double seconds) const
361+{
362+ auto gdt = g_date_time_add_full(get(), years, months, days, hours, minutes, seconds);
363+ DateTime dt(gdt);
364+ g_date_time_unref(gdt);
365+ return dt;
366+}
367+
368 GDateTime* DateTime::get() const
369 {
370 g_assert(m_dt);
371@@ -88,6 +96,11 @@
372 return g_date_time_get_day_of_month(get());
373 }
374
375+double DateTime::seconds() const
376+{
377+ return g_date_time_get_seconds(get());
378+}
379+
380 int64_t DateTime::to_unix() const
381 {
382 return g_date_time_to_unix(get());
383@@ -112,6 +125,11 @@
384 return g_date_time_compare(get(), that.get()) < 0;
385 }
386
387+bool DateTime::operator<=(const DateTime& that) const
388+{
389+ return g_date_time_compare(get(), that.get()) <= 0;
390+}
391+
392 bool DateTime::operator!=(const DateTime& that) const
393 {
394 // return true if this isn't set, or if it's not equal
395
396=== modified file 'src/main.cpp'
397--- src/main.cpp 2014-01-30 19:06:33 +0000
398+++ src/main.cpp 2014-02-19 15:36:23 +0000
399@@ -17,24 +17,25 @@
400 * with this program. If not, see <http://www.gnu.org/licenses/>.
401 */
402
403-
404-
405 #include <datetime/actions-live.h>
406 #include <datetime/clock.h>
407+#include <datetime/clock-watcher.h>
408 #include <datetime/exporter.h>
409 #include <datetime/locations-settings.h>
410 #include <datetime/menu.h>
411 #include <datetime/planner-eds.h>
412 #include <datetime/settings-live.h>
413+#include <datetime/snap.h>
414 #include <datetime/state.h>
415 #include <datetime/timezones-live.h>
416
417 #include <glib/gi18n.h> // bindtextdomain()
418 #include <gio/gio.h>
419-#include <libnotify/notify.h>
420+
421+#include <url-dispatcher.h>
422
423 #include <locale.h>
424-#include <stdlib.h> // exit()
425+#include <cstdlib> // exit()
426
427 using namespace unity::indicator::datetime;
428
429@@ -50,10 +51,6 @@
430 bindtextdomain(GETTEXT_PACKAGE, GNOMELOCALEDIR);
431 textdomain(GETTEXT_PACKAGE);
432
433- // init libnotify
434- if(!notify_init("indicator-datetime-service"))
435- g_critical("libnotify initialization failed");
436-
437 // build the state, actions, and menufactory
438 std::shared_ptr<State> state(new State);
439 std::shared_ptr<Settings> live_settings(new LiveSettings);
440@@ -62,11 +59,27 @@
441 state->settings = live_settings;
442 state->clock = live_clock;
443 state->locations.reset(new SettingsLocations(live_settings, live_timezones));
444- state->planner.reset(new PlannerEds);
445+ state->planner.reset(new PlannerEds(live_clock));
446 state->planner->time = live_clock->localtime();
447 std::shared_ptr<Actions> actions(new LiveActions(state));
448 MenuFactory factory(actions, state);
449
450+ // snap decisions
451+ ClockWatcherImpl clock_watcher(state);
452+ Snap snap;
453+ clock_watcher.alarm_reached().connect([&snap](const Appointment& appt){
454+ auto snap_show = [](const Appointment& a){
455+ const char* url;
456+ if(!a.url.empty())
457+ url = a.url.c_str();
458+ else // alarm doesn't have a URl associated with it; use a fallback
459+ url = "appid://com.ubuntu.clock/clock/current-user-version";
460+ url_dispatch_send(url, nullptr, nullptr);
461+ };
462+ auto snap_dismiss = [](const Appointment&){};
463+ snap(appt, snap_show, snap_dismiss);
464+ });
465+
466 // create the menus
467 std::vector<std::shared_ptr<Menu>> menus;
468 for(int i=0, n=Menu::NUM_PROFILES; i<n; i++)
469
470=== modified file 'src/menu.cpp'
471--- src/menu.cpp 2014-02-05 18:22:42 +0000
472+++ src/menu.cpp 2014-02-19 15:36:23 +0000
473@@ -22,11 +22,11 @@
474 #include <datetime/formatter.h>
475 #include <datetime/state.h>
476
477-#include <json-glib/json-glib.h>
478-
479 #include <glib/gi18n.h>
480 #include <gio/gio.h>
481
482+#include <vector>
483+
484 namespace unity {
485 namespace indicator {
486 namespace datetime {
487@@ -62,7 +62,7 @@
488 ****/
489
490
491-#define FALLBACK_ALARM_CLOCK_ICON_NAME "clock"
492+#define ALARM_ICON_NAME "alarm-clock"
493 #define CALENDAR_ICON_NAME "calendar"
494
495 class MenuImpl: public Menu
496@@ -105,12 +105,15 @@
497 update_section(Appointments); // showing events got toggled
498 });
499 m_state->planner->upcoming.changed().connect([this](const std::vector<Appointment>&){
500- update_section(Appointments); // "upcoming" is the list of Appointments we show
501+ update_upcoming(); // our m_upcoming is planner->upcoming() filtered by time
502 });
503 m_state->clock->date_changed.connect([this](){
504 update_section(Calendar); // need to update the Date menuitem
505 update_section(Locations); // locations' relative time may have changed
506 });
507+ m_state->clock->minute_changed.connect([this](){
508+ update_upcoming(); // our m_upcoming is planner->upcoming() filtered by time
509+ });
510 m_state->locations->locations.changed().connect([this](const std::vector<Location>&) {
511 update_section(Locations); // "locations" is the list of Locations we show
512 });
513@@ -133,6 +136,24 @@
514 g_action_group_change_action_state(action_group, action_name.c_str(), state);
515 }
516
517+ void update_upcoming()
518+ {
519+ const auto now = m_state->clock->localtime();
520+ const auto next_minute = now.add_full(0,0,0,0,1,-now.seconds());
521+
522+ std::vector<Appointment> upcoming;
523+ for(const auto& a : m_state->planner->upcoming.get())
524+ if (next_minute <= a.begin)
525+ upcoming.push_back(a);
526+
527+ if (m_upcoming != upcoming)
528+ {
529+ m_upcoming.swap(upcoming);
530+ update_header(); // show an 'alarm' icon if there are upcoming alarms
531+ update_section(Appointments); // "upcoming" is the list of Appointments we show
532+ }
533+ }
534+
535 std::shared_ptr<const State> m_state;
536 std::shared_ptr<Actions> m_actions;
537 std::shared_ptr<const Formatter> m_formatter;
538@@ -141,71 +162,19 @@
539 GVariant* get_serialized_alarm_icon()
540 {
541 if (G_UNLIKELY(m_serialized_alarm_icon == nullptr))
542- m_serialized_alarm_icon = create_alarm_icon();
543+ {
544+ auto i = g_themed_icon_new_with_default_fallbacks(ALARM_ICON_NAME);
545+ m_serialized_alarm_icon = g_icon_serialize(i);
546+ g_object_unref(i);
547+ }
548
549 return m_serialized_alarm_icon;
550 }
551
552+ std::vector<Appointment> m_upcoming;
553+
554 private:
555
556- /* try to get the clock app's filename from click. (/$pkgdir/$icon) */
557- static GVariant* create_alarm_icon()
558- {
559- GVariant* serialized = nullptr;
560- gchar* icon_filename = nullptr;
561- gchar* standard_error = nullptr;
562- gchar* pkgdir = nullptr;
563-
564- g_spawn_command_line_sync("click pkgdir com.ubuntu.clock", &pkgdir, &standard_error, nullptr, nullptr);
565- g_clear_pointer(&standard_error, g_free);
566- if (pkgdir != nullptr)
567- {
568- gchar* manifest = nullptr;
569- g_strstrip(pkgdir);
570- g_spawn_command_line_sync("click info com.ubuntu.clock", &manifest, &standard_error, nullptr, nullptr);
571- g_clear_pointer(&standard_error, g_free);
572- if (manifest != nullptr)
573- {
574- JsonParser* parser = json_parser_new();
575- if (json_parser_load_from_data(parser, manifest, -1, nullptr))
576- {
577- JsonNode* root = json_parser_get_root(parser); /* transfer-none */
578- if ((root != nullptr) && (JSON_NODE_TYPE(root) == JSON_NODE_OBJECT))
579- {
580- JsonObject* o = json_node_get_object(root); /* transfer-none */
581- const gchar* icon_name = json_object_get_string_member(o, "icon");
582- if (icon_name != nullptr)
583- icon_filename = g_build_filename(pkgdir, icon_name, nullptr);
584- }
585- }
586- g_object_unref(parser);
587- g_free(manifest);
588- }
589- g_free(pkgdir);
590- }
591-
592- if (icon_filename != nullptr)
593- {
594- GFile* file = g_file_new_for_path(icon_filename);
595- GIcon* icon = g_file_icon_new(file);
596-
597- serialized = g_icon_serialize(icon);
598-
599- g_object_unref(icon);
600- g_object_unref(file);
601- g_free(icon_filename);
602- }
603-
604- if (serialized == nullptr)
605- {
606- auto i = g_themed_icon_new_with_default_fallbacks(FALLBACK_ALARM_CLOCK_ICON_NAME);
607- serialized = g_icon_serialize(i);
608- g_object_unref(i);
609- }
610-
611- return serialized;
612- }
613-
614 GVariant* get_serialized_calendar_icon()
615 {
616 if (G_UNLIKELY(m_serialized_calendar_icon == nullptr))
617@@ -273,7 +242,7 @@
618 // add calendar
619 if (show_calendar)
620 {
621- item = g_menu_item_new ("[calendar]", NULL);
622+ item = g_menu_item_new ("[calendar]", nullptr);
623 v = g_variant_new_int64(0);
624 g_menu_item_set_action_and_target_value (item, "indicator.calendar", v);
625 g_menu_item_set_attribute (item, "x-canonical-type",
626@@ -296,11 +265,13 @@
627 const int MAX_APPTS = 5;
628 std::set<std::string> added;
629
630- for (const auto& appt : m_state->planner->upcoming.get())
631+ for (const auto& appt : m_upcoming)
632 {
633+ // don't show too many
634 if (n++ >= MAX_APPTS)
635 break;
636
637+ // don't show duplicates
638 if (added.count(appt.uid))
639 continue;
640
641@@ -508,7 +479,7 @@
642 {
643 // are there alarms?
644 bool has_alarms = false;
645- for(const auto& appointment : m_state->planner->upcoming.get())
646+ for(const auto& appointment : m_upcoming)
647 if((has_alarms = appointment.has_alarms))
648 break;
649
650
651=== modified file 'src/planner-eds.cpp'
652--- src/planner-eds.cpp 2014-01-31 00:33:14 +0000
653+++ src/planner-eds.cpp 2014-02-19 15:36:23 +0000
654@@ -26,6 +26,9 @@
655 #include <libecal/libecal.h>
656 #include <libedataserver/libedataserver.h>
657
658+#include <map>
659+#include <set>
660+
661 namespace unity {
662 namespace indicator {
663 namespace datetime {
664@@ -34,25 +37,28 @@
665 *****
666 ****/
667
668-G_DEFINE_QUARK("source-client", source_client)
669-
670-
671 class PlannerEds::Impl
672 {
673 public:
674
675- Impl(PlannerEds& owner):
676+ Impl(PlannerEds& owner, const std::shared_ptr<Clock>& clock):
677 m_owner(owner),
678+ m_clock(clock),
679 m_cancellable(g_cancellable_new())
680 {
681 e_source_registry_new(m_cancellable, on_source_registry_ready, this);
682
683+ m_clock->minute_changed.connect([this](){
684+ g_debug("rebuilding upcoming because the clock's minute_changed");
685+ rebuild_soon(UPCOMING);
686+ });
687+
688 m_owner.time.changed().connect([this](const DateTime& dt) {
689- g_debug("planner's datetime property changed to %s; calling rebuildSoon()", dt.format("%F %T").c_str());
690- rebuildSoon();
691+ g_debug("planner's datetime property changed to %s; calling rebuild_soon()", dt.format("%F %T").c_str());
692+ rebuild_soon(MONTH);
693 });
694
695- rebuildSoon();
696+ rebuild_soon(ALL);
697 }
698
699 ~Impl()
700@@ -60,6 +66,9 @@
701 g_cancellable_cancel(m_cancellable);
702 g_clear_object(&m_cancellable);
703
704+ while(!m_sources.empty())
705+ remove_source(*m_sources.begin());
706+
707 if (m_rebuild_tag)
708 g_source_remove(m_rebuild_tag);
709
710@@ -83,26 +92,31 @@
711 }
712 else
713 {
714+ g_signal_connect(r, "source-added", G_CALLBACK(on_source_added), gself);
715+ g_signal_connect(r, "source-removed", G_CALLBACK(on_source_removed), gself);
716+ g_signal_connect(r, "source-changed", G_CALLBACK(on_source_changed), gself);
717+ g_signal_connect(r, "source-disabled", G_CALLBACK(on_source_disabled), gself);
718+ g_signal_connect(r, "source-enabled", G_CALLBACK(on_source_enabled), gself);
719+
720 auto self = static_cast<Impl*>(gself);
721-
722- g_signal_connect(r, "source-added", G_CALLBACK(on_source_added), self);
723- g_signal_connect(r, "source-removed", G_CALLBACK(on_source_removed), self);
724- g_signal_connect(r, "source-changed", G_CALLBACK(on_source_changed), self);
725- g_signal_connect(r, "source-disabled", G_CALLBACK(on_source_disabled), self);
726- g_signal_connect(r, "source-enabled", G_CALLBACK(on_source_enabled), self);
727-
728 self->m_source_registry = r;
729-
730- GList* sources = e_source_registry_list_sources(r, E_SOURCE_EXTENSION_CALENDAR);
731- for (auto l=sources; l!=nullptr; l=l->next)
732- on_source_added(r, E_SOURCE(l->data), gself);
733- g_list_free_full(sources, g_object_unref);
734+ self->add_sources_by_extension(E_SOURCE_EXTENSION_CALENDAR);
735+ self->add_sources_by_extension(E_SOURCE_EXTENSION_TASK_LIST);
736 }
737 }
738
739+ void add_sources_by_extension(const char* extension)
740+ {
741+ auto& r = m_source_registry;
742+ auto sources = e_source_registry_list_sources(r, extension);
743+ for (auto l=sources; l!=nullptr; l=l->next)
744+ on_source_added(r, E_SOURCE(l->data), this);
745+ g_list_free_full(sources, g_object_unref);
746+ }
747+
748 static void on_source_added(ESourceRegistry* registry, ESource* source, gpointer gself)
749 {
750- auto self = static_cast<PlannerEds::Impl*>(gself);
751+ auto self = static_cast<Impl*>(gself);
752
753 self->m_sources.insert(E_SOURCE(g_object_ref(source)));
754
755@@ -112,10 +126,19 @@
756
757 static void on_source_enabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)
758 {
759- auto self = static_cast<PlannerEds::Impl*>(gself);
760-
761+ auto self = static_cast<Impl*>(gself);
762+ ECalClientSourceType source_type;
763+
764+ if (e_source_has_extension(source, E_SOURCE_EXTENSION_CALENDAR))
765+ source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS;
766+ else if (e_source_has_extension(source, E_SOURCE_EXTENSION_TASK_LIST))
767+ source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS;
768+ else
769+ g_assert_not_reached();
770+
771+ g_debug("connecting a client to source %s", e_source_get_uid(source));
772 e_cal_client_connect(source,
773- E_CAL_CLIENT_SOURCE_TYPE_EVENTS,
774+ source_type,
775 self->m_cancellable,
776 on_client_connected,
777 gself);
778@@ -134,45 +157,118 @@
779 }
780 else
781 {
782- // we've got a new connected ECalClient, so store it & notify clients
783- g_object_set_qdata_full(G_OBJECT(e_client_get_source(client)),
784- source_client_quark(),
785- client,
786- g_object_unref);
787-
788- g_debug("client connected; calling rebuildSoon()");
789- static_cast<Impl*>(gself)->rebuildSoon();
790- }
791+ // add the client to our collection
792+ auto self = static_cast<Impl*>(gself);
793+ g_debug("got a client for %s", e_cal_client_get_local_attachment_store(E_CAL_CLIENT(client)));
794+ self->m_clients[e_client_get_source(client)] = E_CAL_CLIENT(client);
795+
796+ // now create a view for it so that we can listen for changes
797+ e_cal_client_get_view (E_CAL_CLIENT(client),
798+ "#t", // match all
799+ self->m_cancellable,
800+ on_client_view_ready,
801+ self);
802+
803+ g_debug("client connected; calling rebuild_soon()");
804+ self->rebuild_soon(ALL);
805+ }
806+ }
807+
808+ static void on_client_view_ready (GObject* client, GAsyncResult* res, gpointer gself)
809+ {
810+ GError* error = nullptr;
811+ ECalClientView* view = nullptr;
812+
813+ if (e_cal_client_get_view_finish (E_CAL_CLIENT(client), res, &view, &error))
814+ {
815+ // add the view to our collection
816+ e_cal_client_view_start(view, &error);
817+ g_debug("got a view for %s", e_cal_client_get_local_attachment_store(E_CAL_CLIENT(client)));
818+ auto self = static_cast<Impl*>(gself);
819+ self->m_views[e_client_get_source(E_CLIENT(client))] = view;
820+
821+ g_signal_connect(view, "objects-added", G_CALLBACK(on_view_objects_added), self);
822+ g_signal_connect(view, "objects-modified", G_CALLBACK(on_view_objects_modified), self);
823+ g_signal_connect(view, "objects-removed", G_CALLBACK(on_view_objects_removed), self);
824+ g_debug("view connected; calling rebuild_soon()");
825+ self->rebuild_soon(ALL);
826+ }
827+ else if(error != nullptr)
828+ {
829+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
830+ g_warning("indicator-datetime cannot get View to EDS client: %s", error->message);
831+
832+ g_error_free(error);
833+ }
834+ }
835+
836+ static void on_view_objects_added(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
837+ {
838+ g_debug("%s", G_STRFUNC);
839+ static_cast<Impl*>(gself)->rebuild_soon(ALL);
840+ }
841+ static void on_view_objects_modified(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
842+ {
843+ g_debug("%s", G_STRFUNC);
844+ static_cast<Impl*>(gself)->rebuild_soon(ALL);
845+ }
846+ static void on_view_objects_removed(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
847+ {
848+ g_debug("%s", G_STRFUNC);
849+ static_cast<Impl*>(gself)->rebuild_soon(ALL);
850 }
851
852 static void on_source_disabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)
853 {
854- gpointer e_cal_client;
855-
856- // if this source has a connected ECalClient, remove it & notify clients
857- if ((e_cal_client = g_object_steal_qdata(G_OBJECT(source), source_client_quark())))
858- {
859- g_object_unref(e_cal_client);
860-
861- g_debug("source disabled; calling rebuildSoon()");
862- static_cast<Impl*>(gself)->rebuildSoon();
863- }
864- }
865-
866- static void on_source_removed(ESourceRegistry* registry, ESource* source, gpointer gself)
867- {
868- auto self = static_cast<PlannerEds::Impl*>(gself);
869-
870- on_source_disabled(registry, source, gself);
871-
872- self->m_sources.erase(source);
873- g_object_unref(source);
874+ static_cast<Impl*>(gself)->disable_source(source);
875+ }
876+ void disable_source(ESource* source)
877+ {
878+ // if an ECalClientView is associated with this source, remove it
879+ auto vit = m_views.find(source);
880+ if (vit != m_views.end())
881+ {
882+ auto& view = vit->second;
883+ e_cal_client_view_stop(view, nullptr);
884+ const auto n_disconnected = g_signal_handlers_disconnect_by_data(view, this);
885+ g_warn_if_fail(n_disconnected == 3);
886+ g_object_unref(view);
887+ m_views.erase(vit);
888+ rebuild_soon(ALL);
889+ }
890+
891+ // if an ECalClient is associated with this source, remove it
892+ auto cit = m_clients.find(source);
893+ if (cit != m_clients.end())
894+ {
895+ auto& client = cit->second;
896+ g_object_unref(client);
897+ m_clients.erase(cit);
898+ rebuild_soon(ALL);
899+ }
900+ }
901+
902+ static void on_source_removed(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)
903+ {
904+ static_cast<Impl*>(gself)->remove_source(source);
905+ }
906+ void remove_source(ESource* source)
907+ {
908+ disable_source(source);
909+
910+ auto sit = m_sources.find(source);
911+ if (sit != m_sources.end())
912+ {
913+ g_object_unref(*sit);
914+ m_sources.erase(sit);
915+ rebuild_soon(ALL);
916+ }
917 }
918
919 static void on_source_changed(ESourceRegistry* /*registry*/, ESource* /*source*/, gpointer gself)
920 {
921- g_debug("source changed; calling rebuildSoon()");
922- static_cast<Impl*>(gself)->rebuildSoon();
923+ g_debug("source changed; calling rebuild_soon()");
924+ static_cast<Impl*>(gself)->rebuild_soon(ALL);
925 }
926
927 private:
928@@ -196,58 +292,72 @@
929 task(task_in), client(client_in), color(color_in) {}
930 };
931
932- void rebuildSoon()
933+ void rebuild_soon(int rebuild_flags)
934 {
935- const static guint ARBITRARY_INTERVAL_SECS = 2;
936+ static const guint ARBITRARY_INTERVAL_SECS = 2;
937+
938+ m_rebuild_flags |= rebuild_flags;
939
940 if (m_rebuild_tag == 0)
941- m_rebuild_tag = g_timeout_add_seconds(ARBITRARY_INTERVAL_SECS, rebuildNowStatic, this);
942+ m_rebuild_tag = g_timeout_add_seconds(ARBITRARY_INTERVAL_SECS, rebuild_now_static, this);
943 }
944
945- static gboolean rebuildNowStatic(gpointer gself)
946+ static gboolean rebuild_now_static(gpointer gself)
947 {
948 auto self = static_cast<Impl*>(gself);
949+ const auto flags = self->m_rebuild_flags;
950 self->m_rebuild_tag = 0;
951- self->rebuildNow();
952+ self->m_rebuild_flags = 0;
953+ self->rebuild_now(flags);
954 return G_SOURCE_REMOVE;
955 }
956
957- void rebuildNow()
958- {
959- const auto calendar_date = m_owner.time.get().get();
960- GDateTime* begin;
961- GDateTime* end;
962- int y, m, d;
963-
964- // get all the appointments in the calendar month
965- g_date_time_get_ymd(calendar_date, &y, &m, &d);
966- begin = g_date_time_new_local(y, m, 1, 0, 0, 0.1);
967- end = g_date_time_new_local(y, m, g_date_get_days_in_month(GDateMonth(m),GDateYear(y)), 23, 59, 59.9);
968- if (begin && end)
969- {
970- getAppointments(begin, end, [this](const std::vector<Appointment>& appointments) {
971- g_debug("got %d appointments in this calendar month", (int)appointments.size());
972- m_owner.this_month.set(appointments);
973- });
974- }
975- g_clear_pointer(&begin, g_date_time_unref);
976- g_clear_pointer(&end, g_date_time_unref);
977-
978- // get the upcoming appointments
979- begin = g_date_time_ref(calendar_date);
980- end = g_date_time_add_months(begin, 1);
981- if (begin && end)
982- {
983- getAppointments(begin, end, [this](const std::vector<Appointment>& appointments) {
984- g_debug("got %d upcoming appointments", (int)appointments.size());
985- m_owner.upcoming.set(appointments);
986- });
987- }
988- g_clear_pointer(&begin, g_date_time_unref);
989- g_clear_pointer(&end, g_date_time_unref);
990- }
991-
992- void getAppointments(GDateTime* begin_dt, GDateTime* end_dt, appointment_func func)
993+ void rebuild_now(int rebuild_flags)
994+ {
995+ if (rebuild_flags & UPCOMING)
996+ rebuild_upcoming();
997+
998+ if (rebuild_flags & MONTH)
999+ rebuild_month();
1000+ }
1001+
1002+ void rebuild_month()
1003+ {
1004+ const auto ref = m_owner.time.get().get();
1005+ auto month_begin = g_date_time_add_full(ref,
1006+ 0, // subtract no years
1007+ 0, // subtract no months
1008+ -(g_date_time_get_day_of_month(ref)-1),
1009+ -g_date_time_get_hour(ref),
1010+ -g_date_time_get_minute(ref),
1011+ -g_date_time_get_seconds(ref));
1012+ auto month_end = g_date_time_add_full(month_begin, 0, 1, 0, 0, 0, -0.1);
1013+
1014+ get_appointments(month_begin, month_end, [this](const std::vector<Appointment>& appointments) {
1015+ g_debug("got %d appointments in this calendar month", (int)appointments.size());
1016+ m_owner.this_month.set(appointments);
1017+ });
1018+
1019+ g_date_time_unref(month_end);
1020+ g_date_time_unref(month_begin);
1021+ }
1022+
1023+ void rebuild_upcoming()
1024+ {
1025+ const auto ref = m_clock->localtime();
1026+ const auto begin = g_date_time_add_minutes(ref.get(),-10);
1027+ const auto end = g_date_time_add_months(begin,1);
1028+
1029+ get_appointments(begin, end, [this](const std::vector<Appointment>& appointments) {
1030+ g_debug("got %d upcoming appointments", (int)appointments.size());
1031+ m_owner.upcoming.set(appointments);
1032+ });
1033+
1034+ g_date_time_unref(end);
1035+ g_date_time_unref(begin);
1036+ }
1037+
1038+ void get_appointments(GDateTime* begin_dt, GDateTime* end_dt, appointment_func func)
1039 {
1040 const auto begin = g_date_time_to_unix(begin_dt);
1041 const auto end = g_date_time_to_unix(end_dt);
1042@@ -286,16 +396,14 @@
1043 delete task;
1044 });
1045
1046- for (auto& source : m_sources)
1047+ for (auto& kv : m_clients)
1048 {
1049- auto client = E_CAL_CLIENT(g_object_get_qdata(G_OBJECT(source), source_client_quark()));
1050- if (client == nullptr)
1051- continue;
1052-
1053+ auto& client = kv.second;
1054 if (default_timezone != nullptr)
1055 e_cal_client_set_default_timezone(client, default_timezone);
1056
1057 // start a new subtask to enumerate all the components in this client.
1058+ auto& source = kv.first;
1059 auto extension = e_source_get_extension(source, E_SOURCE_EXTENSION_CALENDAR);
1060 const auto color = e_source_selectable_get_color(E_SOURCE_SELECTABLE(extension));
1061 g_debug("calling e_cal_client_generate_instances for %p", (void*)client);
1062@@ -407,16 +515,19 @@
1063 delete subtask;
1064 }
1065
1066-private:
1067-
1068 PlannerEds& m_owner;
1069+ std::shared_ptr<Clock> m_clock;
1070 std::set<ESource*> m_sources;
1071- GCancellable * m_cancellable = nullptr;
1072- ESourceRegistry * m_source_registry = nullptr;
1073+ std::map<ESource*,ECalClient*> m_clients;
1074+ std::map<ESource*,ECalClientView*> m_views;
1075+ GCancellable* m_cancellable = nullptr;
1076+ ESourceRegistry* m_source_registry = nullptr;
1077 guint m_rebuild_tag = 0;
1078+ guint m_rebuild_flags = 0;
1079+ enum { UPCOMING=(1<<0), MONTH=(1<<1), ALL=UPCOMING|MONTH };
1080 };
1081
1082-PlannerEds::PlannerEds(): p(new Impl(*this)) {}
1083+PlannerEds::PlannerEds(const std::shared_ptr<Clock>& clock): p(new Impl(*this, clock)) {}
1084
1085 PlannerEds::~PlannerEds() =default;
1086
1087
1088=== added file 'src/snap.cpp'
1089--- src/snap.cpp 1970-01-01 00:00:00 +0000
1090+++ src/snap.cpp 2014-02-19 15:36:23 +0000
1091@@ -0,0 +1,256 @@
1092+/*
1093+ * Copyright 2014 Canonical Ltd.
1094+ *
1095+ * This program is free software: you can redistribute it and/or modify it
1096+ * under the terms of the GNU General Public License version 3, as published
1097+ * by the Free Software Foundation.
1098+ *
1099+ * This program is distributed in the hope that it will be useful, but
1100+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1101+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1102+ * PURPOSE. See the GNU General Public License for more details.
1103+ *
1104+ * You should have received a copy of the GNU General Public License along
1105+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1106+ *
1107+ * Authors:
1108+ * Charles Kerr <charles.kerr@canonical.com>
1109+ */
1110+
1111+#include <datetime/appointment.h>
1112+#include <datetime/formatter.h>
1113+#include <datetime/snap.h>
1114+
1115+#include <canberra.h>
1116+#include <libnotify/notify.h>
1117+
1118+#include <glib/gi18n.h>
1119+#include <glib.h>
1120+
1121+#define ALARM_SOUND_FILENAME "/usr/share/sounds/ubuntu/stereo/phone-incoming-call.ogg"
1122+
1123+namespace unity {
1124+namespace indicator {
1125+namespace datetime {
1126+
1127+/***
1128+****
1129+***/
1130+
1131+namespace
1132+{
1133+
1134+/**
1135+*** libcanberra -- play sounds
1136+**/
1137+
1138+// arbitrary number, but we need a consistent id for play/cancel
1139+const int32_t alarm_ca_id = 1;
1140+
1141+gboolean media_cached = FALSE;
1142+ca_context *c_context = nullptr;
1143+guint timeout_tag = 0;
1144+
1145+ca_context* get_ca_context()
1146+{
1147+ if (G_UNLIKELY(c_context == nullptr))
1148+ {
1149+ int rv;
1150+
1151+ if ((rv = ca_context_create(&c_context)) != CA_SUCCESS)
1152+ {
1153+ g_warning("Failed to create canberra context: %s\n", ca_strerror(rv));
1154+ c_context = nullptr;
1155+ }
1156+ else
1157+ {
1158+ const char* filename = ALARM_SOUND_FILENAME;
1159+ rv = ca_context_cache(c_context,
1160+ CA_PROP_EVENT_ID, "alarm",
1161+ CA_PROP_MEDIA_FILENAME, filename,
1162+ CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
1163+ NULL);
1164+ media_cached = rv == CA_SUCCESS;
1165+ if (rv != CA_SUCCESS)
1166+ g_warning("Couldn't add '%s' to canberra cache: %s", filename, ca_strerror(rv));
1167+ }
1168+ }
1169+
1170+ return c_context;
1171+}
1172+
1173+void play_alarm_sound();
1174+
1175+gboolean play_alarm_sound_idle (gpointer)
1176+{
1177+ timeout_tag = 0;
1178+ play_alarm_sound();
1179+ return G_SOURCE_REMOVE;
1180+}
1181+
1182+void on_alarm_play_done (ca_context* /*context*/, uint32_t /*id*/, int rv, void* /*user_data*/)
1183+{
1184+ // wait one second, then play it again
1185+ if ((rv == CA_SUCCESS) && (timeout_tag == 0))
1186+ timeout_tag = g_timeout_add_seconds (1, play_alarm_sound_idle, nullptr);
1187+}
1188+
1189+void play_alarm_sound()
1190+{
1191+ const gchar* filename = ALARM_SOUND_FILENAME;
1192+ auto context = get_ca_context();
1193+ g_return_if_fail(context != nullptr);
1194+
1195+ ca_proplist* props = nullptr;
1196+ ca_proplist_create(&props);
1197+ if (media_cached)
1198+ ca_proplist_sets(props, CA_PROP_EVENT_ID, "alarm");
1199+ ca_proplist_sets(props, CA_PROP_MEDIA_FILENAME, filename);
1200+
1201+ const auto rv = ca_context_play_full(context, alarm_ca_id, props, on_alarm_play_done, nullptr);
1202+ if (rv != CA_SUCCESS)
1203+ g_warning("Failed to play file '%s': %s", filename, ca_strerror(rv));
1204+
1205+ g_clear_pointer(&props, ca_proplist_destroy);
1206+}
1207+
1208+void stop_alarm_sound()
1209+{
1210+ auto context = get_ca_context();
1211+ if (context != nullptr)
1212+ {
1213+ const auto rv = ca_context_cancel(context, alarm_ca_id);
1214+ if (rv != CA_SUCCESS)
1215+ g_warning("Failed to cancel alarm sound: %s", ca_strerror(rv));
1216+ }
1217+
1218+ if (timeout_tag != 0)
1219+ {
1220+ g_source_remove(timeout_tag);
1221+ timeout_tag = 0;
1222+ }
1223+}
1224+
1225+/**
1226+*** libnotify -- snap decisions
1227+**/
1228+
1229+void first_time_init()
1230+{
1231+ static bool inited = false;
1232+
1233+ if (G_UNLIKELY(!inited))
1234+ {
1235+ inited = true;
1236+
1237+ if(!notify_init("indicator-datetime-service"))
1238+ g_critical("libnotify initialization failed");
1239+ }
1240+}
1241+
1242+struct SnapData
1243+{
1244+ Snap::appointment_func show;
1245+ Snap::appointment_func dismiss;
1246+ Appointment appointment;
1247+};
1248+
1249+void on_snap_show(NotifyNotification*, gchar* /*action*/, gpointer gdata)
1250+{
1251+ stop_alarm_sound();
1252+ auto data = static_cast<SnapData*>(gdata);
1253+ data->show(data->appointment);
1254+}
1255+
1256+void on_snap_dismiss(NotifyNotification*, gchar* /*action*/, gpointer gdata)
1257+{
1258+ stop_alarm_sound();
1259+ auto data = static_cast<SnapData*>(gdata);
1260+ data->dismiss(data->appointment);
1261+}
1262+
1263+void snap_data_destroy_notify(gpointer gdata)
1264+{
1265+ delete static_cast<SnapData*>(gdata);
1266+}
1267+
1268+void show_snap_decision(SnapData* data)
1269+{
1270+ const Appointment& appointment = data->appointment;
1271+
1272+ const auto timestr = appointment.begin.format("%a, %X");
1273+ auto title = g_strdup_printf(_("Alarm %s"), timestr.c_str());
1274+ const auto body = appointment.summary;
1275+ const gchar* icon_name = "alarm-clock";
1276+
1277+ auto nn = notify_notification_new(title, body.c_str(), icon_name);
1278+ notify_notification_set_hint_string(nn, "x-canonical-snap-decisions", "true");
1279+ notify_notification_set_hint_string(nn, "x-canonical-private-button-tint", "true");
1280+ notify_notification_add_action(nn, "show", _("Show"), on_snap_show, data, nullptr);
1281+ notify_notification_add_action(nn, "dismiss", _("Dismiss"), on_snap_dismiss, data, nullptr);
1282+ g_object_set_data_full(G_OBJECT(nn), "snap-data", data, snap_data_destroy_notify);
1283+
1284+ GError * error = nullptr;
1285+ notify_notification_show(nn, &error);
1286+ if (error != NULL)
1287+ {
1288+ g_warning("Unable to show snap decision for '%s': %s", body.c_str(), error->message);
1289+ g_error_free(error);
1290+ data->show(data->appointment);
1291+ }
1292+
1293+ g_free(title);
1294+}
1295+
1296+/**
1297+***
1298+**/
1299+
1300+void notify(const Appointment& appointment,
1301+ Snap::appointment_func show,
1302+ Snap::appointment_func dismiss)
1303+{
1304+ auto data = new SnapData;
1305+ data->appointment = appointment;
1306+ data->show = show;
1307+ data->dismiss = dismiss;
1308+
1309+ play_alarm_sound();
1310+ show_snap_decision(data);
1311+}
1312+
1313+} // unnamed namespace
1314+
1315+
1316+/***
1317+****
1318+***/
1319+
1320+Snap::Snap()
1321+{
1322+ first_time_init();
1323+}
1324+
1325+Snap::~Snap()
1326+{
1327+ media_cached = false;
1328+ g_clear_pointer(&c_context, ca_context_destroy);
1329+}
1330+
1331+void Snap::operator()(const Appointment& appointment,
1332+ appointment_func show,
1333+ appointment_func dismiss)
1334+{
1335+ if (appointment.has_alarms)
1336+ notify(appointment, show, dismiss);
1337+ else
1338+ dismiss(appointment);
1339+}
1340+
1341+/***
1342+****
1343+***/
1344+
1345+} // namespace datetime
1346+} // namespace indicator
1347+} // namespace unity
1348
1349=== modified file 'src/timezone-file.cpp'
1350--- src/timezone-file.cpp 2014-02-19 15:36:23 +0000
1351+++ src/timezone-file.cpp 2014-02-19 15:36:23 +0000
1352@@ -32,7 +32,7 @@
1353
1354 FileTimezone::FileTimezone(const std::string& filename)
1355 {
1356- setFilename(filename);
1357+ set_filename(filename);
1358 }
1359
1360 FileTimezone::~FileTimezone()
1361@@ -52,7 +52,7 @@
1362 }
1363
1364 void
1365-FileTimezone::setFilename(const std::string& filename)
1366+FileTimezone::set_filename(const std::string& filename)
1367 {
1368 clear();
1369
1370@@ -79,7 +79,7 @@
1371 }
1372 else
1373 {
1374- m_monitor_handler_id = g_signal_connect_swapped(m_monitor, "changed", G_CALLBACK(onFileChanged), this);
1375+ m_monitor_handler_id = g_signal_connect_swapped(m_monitor, "changed", G_CALLBACK(on_file_changed), this);
1376 g_debug("%s Monitoring timezone file '%s'", G_STRLOC, m_filename.c_str());
1377 }
1378
1379@@ -87,7 +87,7 @@
1380 }
1381
1382 void
1383-FileTimezone::onFileChanged(gpointer gself)
1384+FileTimezone::on_file_changed(gpointer gself)
1385 {
1386 static_cast<FileTimezone*>(gself)->reload();
1387 }
1388
1389=== modified file 'tests/CMakeLists.txt'
1390--- tests/CMakeLists.txt 2014-01-29 03:42:05 +0000
1391+++ tests/CMakeLists.txt 2014-02-19 15:36:23 +0000
1392@@ -42,6 +42,7 @@
1393 endfunction()
1394 add_test_by_name(test-actions)
1395 add_test_by_name(test-clock)
1396+add_test_by_name(test-clock-watcher)
1397 add_test_by_name(test-exporter)
1398 add_test_by_name(test-formatter)
1399 add_test_by_name(test-live-actions)
1400@@ -52,6 +53,10 @@
1401 add_test_by_name(test-timezone-file)
1402 add_test_by_name(test-utils)
1403
1404+set (TEST_NAME manual-test-snap)
1405+add_executable (${TEST_NAME} ${TEST_NAME}.cpp)
1406+add_dependencies (${TEST_NAME} libindicatordatetimeservice)
1407+target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS})
1408
1409 # disabling the timezone unit tests because they require
1410 # https://code.launchpad.net/~ted/dbus-test-runner/multi-interface-test/+merge/199724
1411
1412=== modified file 'tests/geoclue-fixture.h'
1413--- tests/geoclue-fixture.h 2014-01-17 03:23:57 +0000
1414+++ tests/geoclue-fixture.h 2014-02-19 15:36:23 +0000
1415@@ -95,7 +95,7 @@
1416
1417 // I've looked and can't find where this extra ref is coming from.
1418 // is there an unbalanced ref to the bus in the test harness?!
1419- while (bus != NULL)
1420+ while (bus != nullptr)
1421 {
1422 g_object_unref (bus);
1423 wait_msec (1000);
1424
1425=== added file 'tests/manual-test-snap.cpp'
1426--- tests/manual-test-snap.cpp 1970-01-01 00:00:00 +0000
1427+++ tests/manual-test-snap.cpp 2014-02-19 15:36:23 +0000
1428@@ -0,0 +1,63 @@
1429+
1430+/*
1431+ * Copyright 2013 Canonical Ltd.
1432+ *
1433+ * Authors:
1434+ * Charles Kerr <charles.kerr@canonical.com>
1435+ *
1436+ * This program is free software: you can redistribute it and/or modify it
1437+ * under the terms of the GNU General Public License version 3, as published
1438+ * by the Free Software Foundation.
1439+ *
1440+ * This program is distributed in the hope that it will be useful, but
1441+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1442+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1443+ * PURPOSE. See the GNU General Public License for more details.
1444+ *
1445+ * You should have received a copy of the GNU General Public License along
1446+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1447+ */
1448+
1449+#include <datetime/appointment.h>
1450+#include <datetime/snap.h>
1451+
1452+#include <glib.h>
1453+
1454+using namespace unity::indicator::datetime;
1455+
1456+/***
1457+****
1458+***/
1459+
1460+int main()
1461+{
1462+ Appointment a;
1463+ a.color = "green";
1464+ a.summary = "Alarm";
1465+ a.url = "alarm:///hello-world";
1466+ a.uid = "D4B57D50247291478ED31DED17FF0A9838DED402";
1467+ a.is_event = false;
1468+ a.is_daily = false;
1469+ a.has_alarms = true;
1470+ auto begin = g_date_time_new_local(2014,12,25,0,0,0);
1471+ auto end = g_date_time_add_full(begin,0,0,1,0,0,-1);
1472+ a.begin = begin;
1473+ a.end = end;
1474+ g_date_time_unref(end);
1475+ g_date_time_unref(begin);
1476+
1477+ auto loop = g_main_loop_new(nullptr, false);
1478+ auto show = [loop](const Appointment& appt){
1479+ g_message("You clicked 'show' for appt url '%s'", appt.url.c_str());
1480+ g_main_loop_quit(loop);
1481+ };
1482+ auto dismiss = [loop](const Appointment&){
1483+ g_message("You clicked 'dismiss'");
1484+ g_main_loop_quit(loop);
1485+ };
1486+
1487+ Snap snap;
1488+ snap(a, show, dismiss);
1489+ g_main_loop_run(loop);
1490+ return 0;
1491+}
1492
1493=== added file 'tests/test-clock-watcher.cpp'
1494--- tests/test-clock-watcher.cpp 1970-01-01 00:00:00 +0000
1495+++ tests/test-clock-watcher.cpp 2014-02-19 15:36:23 +0000
1496@@ -0,0 +1,166 @@
1497+/*
1498+ * Copyright 2014 Canonical Ltd.
1499+ *
1500+ * This program is free software: you can redistribute it and/or modify it
1501+ * under the terms of the GNU General Public License version 3, as published
1502+ * by the Free Software Foundation.
1503+ *
1504+ * This program is distributed in the hope that it will be useful, but
1505+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1506+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1507+ * PURPOSE. See the GNU General Public License for more details.
1508+ *
1509+ * You should have received a copy of the GNU General Public License along
1510+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1511+ *
1512+ * Authors:
1513+ * Charles Kerr <charles.kerr@canonical.com>
1514+ */
1515+
1516+#include <datetime/clock-watcher.h>
1517+
1518+#include <gtest/gtest.h>
1519+
1520+#include "state-fixture.h"
1521+
1522+using namespace unity::indicator::datetime;
1523+
1524+class ClockWatcherFixture: public StateFixture
1525+{
1526+private:
1527+
1528+ typedef StateFixture super;
1529+
1530+protected:
1531+
1532+ std::vector<std::string> m_triggered;
1533+ std::unique_ptr<ClockWatcher> m_watcher;
1534+
1535+ void SetUp()
1536+ {
1537+ super::SetUp();
1538+
1539+ m_watcher.reset(new ClockWatcherImpl(m_state));
1540+ m_watcher->alarm_reached().connect([this](const Appointment& appt){
1541+ m_triggered.push_back(appt.uid);
1542+ });
1543+
1544+ EXPECT_TRUE(m_triggered.empty());
1545+ }
1546+
1547+ void TearDown()
1548+ {
1549+ m_triggered.clear();
1550+ m_watcher.reset();
1551+
1552+ super::TearDown();
1553+ }
1554+
1555+ std::vector<Appointment> build_some_appointments()
1556+ {
1557+ const auto now = m_state->clock->localtime();
1558+ auto tomorrow = g_date_time_add_days (now.get(), 1);
1559+ auto tomorrow_begin = g_date_time_add_full (tomorrow, 0, 0, 0,
1560+ -g_date_time_get_hour(tomorrow),
1561+ -g_date_time_get_minute(tomorrow),
1562+ -g_date_time_get_seconds(tomorrow));
1563+ auto tomorrow_end = g_date_time_add_full (tomorrow_begin, 0, 0, 1, 0, 0, -1);
1564+
1565+ Appointment a1; // an alarm clock appointment
1566+ a1.color = "red";
1567+ a1.summary = "Alarm";
1568+ a1.summary = "http://www.example.com/";
1569+ a1.uid = "example";
1570+ a1.has_alarms = true;
1571+ a1.begin = tomorrow_begin;
1572+ a1.end = tomorrow_end;
1573+
1574+ auto ubermorgen_begin = g_date_time_add_days (tomorrow, 1);
1575+ auto ubermorgen_end = g_date_time_add_full (tomorrow_begin, 0, 0, 1, 0, 0, -1);
1576+
1577+ Appointment a2; // a non-alarm appointment
1578+ a2.color = "green";
1579+ a2.summary = "Other Text";
1580+ a2.summary = "http://www.monkey.com/";
1581+ a2.uid = "monkey";
1582+ a2.has_alarms = false;
1583+ a2.begin = ubermorgen_begin;
1584+ a2.end = ubermorgen_end;
1585+
1586+ // cleanup
1587+ g_date_time_unref(ubermorgen_end);
1588+ g_date_time_unref(ubermorgen_begin);
1589+ g_date_time_unref(tomorrow_end);
1590+ g_date_time_unref(tomorrow_begin);
1591+ g_date_time_unref(tomorrow);
1592+
1593+ return std::vector<Appointment>({a1, a2});
1594+ }
1595+};
1596+
1597+/***
1598+****
1599+***/
1600+
1601+TEST_F(ClockWatcherFixture, AppointmentsChanged)
1602+{
1603+ // Add some appointments to the planner.
1604+ // One of these matches our state's localtime, so that should get triggered.
1605+ std::vector<Appointment> a = build_some_appointments();
1606+ a[0].begin = m_state->clock->localtime();
1607+ m_state->planner->upcoming.set(a);
1608+
1609+ // Confirm that it got fired
1610+ EXPECT_EQ(1, m_triggered.size());
1611+ EXPECT_EQ(a[0].uid, m_triggered[0]);
1612+}
1613+
1614+
1615+TEST_F(ClockWatcherFixture, TimeChanged)
1616+{
1617+ // Add some appointments to the planner.
1618+ // Neither of these match the state's localtime, so nothing should be triggered.
1619+ std::vector<Appointment> a = build_some_appointments();
1620+ m_state->planner->upcoming.set(a);
1621+ EXPECT_TRUE(m_triggered.empty());
1622+
1623+ // Set the state's clock to a time that matches one of the appointments.
1624+ // That appointment should get triggered.
1625+ m_mock_state->mock_clock->set_localtime(a[1].begin);
1626+ EXPECT_EQ(1, m_triggered.size());
1627+ EXPECT_EQ(a[1].uid, m_triggered[0]);
1628+}
1629+
1630+
1631+TEST_F(ClockWatcherFixture, MoreThanOne)
1632+{
1633+ const auto now = m_state->clock->localtime();
1634+ std::vector<Appointment> a = build_some_appointments();
1635+ a[0].begin = a[1].begin = now;
1636+ m_state->planner->upcoming.set(a);
1637+
1638+ EXPECT_EQ(2, m_triggered.size());
1639+ EXPECT_EQ(a[0].uid, m_triggered[0]);
1640+ EXPECT_EQ(a[1].uid, m_triggered[1]);
1641+}
1642+
1643+
1644+TEST_F(ClockWatcherFixture, NoDuplicates)
1645+{
1646+ // Setup: add an appointment that gets triggered.
1647+ const auto now = m_state->clock->localtime();
1648+ const std::vector<Appointment> appointments = build_some_appointments();
1649+ std::vector<Appointment> a;
1650+ a.push_back(appointments[0]);
1651+ a[0].begin = now;
1652+ m_state->planner->upcoming.set(a);
1653+ EXPECT_EQ(1, m_triggered.size());
1654+ EXPECT_EQ(a[0].uid, m_triggered[0]);
1655+
1656+ // Now change the appointment vector by adding one to it.
1657+ // Confirm that the ClockWatcher doesn't re-trigger a[0]
1658+ a.push_back(appointments[1]);
1659+ m_state->planner->upcoming.set(a);
1660+ EXPECT_EQ(1, m_triggered.size());
1661+ EXPECT_EQ(a[0].uid, m_triggered[0]);
1662+}
1663
1664=== modified file 'tests/test-clock.cpp'
1665--- tests/test-clock.cpp 2014-01-31 00:33:14 +0000
1666+++ tests/test-clock.cpp 2014-02-19 15:36:23 +0000
1667@@ -37,12 +37,12 @@
1668 void emitPrepareForSleep()
1669 {
1670 g_dbus_connection_emit_signal(g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr),
1671- NULL,
1672+ nullptr,
1673 "/org/freedesktop/login1", // object path
1674 "org.freedesktop.login1.Manager", // interface
1675 "PrepareForSleep", // signal name
1676 g_variant_new("(b)", FALSE),
1677- NULL);
1678+ nullptr);
1679 }
1680 };
1681
1682
1683=== modified file 'tests/test-planner.cpp'
1684--- tests/test-planner.cpp 2014-01-31 00:33:14 +0000
1685+++ tests/test-planner.cpp 2014-02-19 15:36:23 +0000
1686@@ -28,9 +28,7 @@
1687 #include <langinfo.h>
1688 #include <locale.h>
1689
1690-using unity::indicator::datetime::Appointment;
1691-using unity::indicator::datetime::DateTime;
1692-using unity::indicator::datetime::PlannerEds;
1693+using namespace unity::indicator::datetime;
1694
1695 /***
1696 ****
1697@@ -40,11 +38,15 @@
1698
1699 TEST_F(PlannerFixture, EDS)
1700 {
1701- PlannerEds planner;
1702+ auto tmp = g_date_time_new_now_local();
1703+ const auto now = DateTime(tmp);
1704+ g_date_time_unref(tmp);
1705+
1706+ std::shared_ptr<Clock> clock(new MockClock(now));
1707+ PlannerEds planner(clock);
1708 wait_msec(100);
1709
1710- auto now = g_date_time_new_now_local();
1711- planner.time.set(DateTime(now));
1712+ planner.time.set(now);
1713 wait_msec(2500);
1714
1715 std::vector<Appointment> this_month = planner.this_month.get();
1716
1717=== modified file 'tests/test-settings.cpp'
1718--- tests/test-settings.cpp 2014-01-31 00:40:13 +0000
1719+++ tests/test-settings.cpp 2014-02-19 15:36:23 +0000
1720@@ -167,8 +167,8 @@
1721 {
1722 const auto key = SETTINGS_LOCATIONS_S;
1723
1724- const gchar* astrv[] = {"America/Los_Angeles Oakland", "America/Chicago Oklahoma City", "Europe/London London", NULL};
1725- const gchar* bstrv[] = {"America/Denver", "Europe/London London", "Europe/Berlin Berlin", NULL};
1726+ const gchar* astrv[] = {"America/Los_Angeles Oakland", "America/Chicago Oklahoma City", "Europe/London London", nullptr};
1727+ const gchar* bstrv[] = {"America/Denver", "Europe/London London", "Europe/Berlin Berlin", nullptr};
1728 const std::vector<std::string> av = strv_to_vector(astrv);
1729 const std::vector<std::string> bv = strv_to_vector(bstrv);
1730
1731
1732=== modified file 'tests/test-utils.cpp'
1733--- tests/test-utils.cpp 2014-01-27 07:26:02 +0000
1734+++ tests/test-utils.cpp 2014-02-19 15:36:23 +0000
1735@@ -59,7 +59,7 @@
1736 const char* location;
1737 const char* expected_name;
1738 } beautify_timezone_test_cases[] = {
1739- { "America/Chicago", NULL, "Chicago" },
1740+ { "America/Chicago", nullptr, "Chicago" },
1741 { "America/Chicago", "America/Chicago", "Chicago" },
1742 { "America/Chicago", "America/Chigago Chicago", "Chicago" },
1743 { "America/Chicago", "America/Chicago Oklahoma City", "Oklahoma City" },

Subscribers

People subscribed via source and target branches