Merge lp:~charlesk/indicator-datetime/alarms into lp:indicator-datetime/14.04
- alarms
- Merge into trunk.14.04
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 |
Related bugs: |
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
PS Jenkins bot (ps-jenkins) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:396
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 397. By Charles Kerr
-
bugfix: when closing the snap decision, ensure there's not a timeout waiting to loop the ringtone
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:397
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 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.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:401
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 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
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:403
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:404
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 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.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:405
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 406. By Charles Kerr
-
sync with trunk
Preview Diff
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" }, |
PASSED: Continuous integration, rev:388 jenkins. qa.ubuntu. com/job/ indicator- datetime- ci/173/ jenkins. qa.ubuntu. com/job/ indicator- datetime- trusty- amd64-ci/ 53 jenkins. qa.ubuntu. com/job/ indicator- datetime- trusty- armhf-ci/ 53 jenkins. qa.ubuntu. com/job/ indicator- datetime- trusty- armhf-ci/ 53/artifact/ work/output/ *zip*/output. zip
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/indicator- datetime- ci/173/ rebuild
http://