Merge lp:~charlesk/indicator-datetime/lp-1318997-customizable-alarm-sounds into lp:indicator-datetime/13.10
- lp-1318997-customizable-alarm-sounds
- Merge into trunk.14.10
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Ted Gould | ||||
Approved revision: | 357 | ||||
Merged at revision: | 350 | ||||
Proposed branch: | lp:~charlesk/indicator-datetime/lp-1318997-customizable-alarm-sounds | ||||
Merge into: | lp:indicator-datetime/13.10 | ||||
Diff against target: |
1150 lines (+633/-246) 12 files modified
data/com.canonical.indicator.datetime.gschema.xml (+29/-0) include/datetime/appointment.h (+1/-0) include/datetime/settings-live.h (+3/-0) include/datetime/settings-shared.h (+13/-0) include/datetime/settings.h (+3/-0) include/datetime/snap.h (+8/-1) src/engine-eds.cpp (+14/-4) src/main.cpp (+1/-1) src/settings-live.cpp (+38/-0) src/snap.cpp (+442/-236) tests/manual-test-snap.cpp (+27/-4) tests/test-settings.cpp (+54/-0) |
||||
To merge this branch: | bzr merge lp:~charlesk/indicator-datetime/lp-1318997-customizable-alarm-sounds | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ted Gould (community) | Approve | ||
PS Jenkins bot (community) | continuous-integration | Approve | |
Review via email: mp+224231@code.launchpad.net |
Commit message
Add the ability to have per-alarm custom sounds.
Description of the change
Add the ability to have customizable alarm sounds, durations, and volume levels.
(1) Add support in the GSettings schema for setting alarm volume, duration, and default sound (to be used if an alarm doesn't have a sound set, or if the one set isn't usable).
(2) Refactor snap.cpp to allow for per-alarm resources -- since these weren't configurable before, the same resources were hardcoded and reused across all alarms. I've moved this logic into two new private snap.cpp classes, Sound (to manage an alarm's canberra resources and handle looping & duration) and Popup (to manage an alarm's NotifyNotification resources). These do what they say on the tin, so even though the file's diff is largish the ideas are straightforward.
(3) If a per-alarm sound is set in the EDS event, use it instead of the default sound.
(4) Sync the unit tests to match these changes.
PS Jenkins bot (ps-jenkins) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:354
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 355. By Charles Kerr
-
copyedit the new snap decision: fix linewraps, give some variables/methods clearer names, better grouping of related methods, etc.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:355
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Ted Gould (ted) : | # |
- 356. By Charles Kerr
-
in snap.cpp, replace Sound::Properties with a SoundBuilder class to make the pattern use better.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:356
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 357. By Charles Kerr
-
make get_gain_level() a little easier to read.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:357
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Ted Gould (ted) : | # |
Preview Diff
1 | === modified file 'data/com.canonical.indicator.datetime.gschema.xml' |
2 | --- data/com.canonical.indicator.datetime.gschema.xml 2013-10-30 22:29:43 +0000 |
3 | +++ data/com.canonical.indicator.datetime.gschema.xml 2014-06-27 14:06:11 +0000 |
4 | @@ -5,6 +5,13 @@ |
5 | <value nick="24-hour" value="2" /> |
6 | <value nick="custom" value="3" /> |
7 | </enum> |
8 | + <enum id="alarm-volume-enum"> |
9 | + <value nick="very-quiet" value="0" /> |
10 | + <value nick="quiet" value="1" /> |
11 | + <value nick="normal" value="2" /> |
12 | + <value nick="loud" value="3" /> |
13 | + <value nick="very-loud" value="4" /> |
14 | + </enum> |
15 | <schema id="com.canonical.indicator.datetime" path="/com/canonical/indicator/datetime/" gettext-domain="indicator-datetime"> |
16 | <key name="show-clock" type="b"> |
17 | <default>true</default> |
18 | @@ -123,5 +130,27 @@ |
19 | Some timezones can be known by many different cities or names. This setting describes how the current zone prefers to be named. Format is "TIMEZONE NAME" (e.g. "America/New_York Boston" to name the New_York zone Boston). |
20 | </description> |
21 | </key> |
22 | + <key name="alarm-default-sound" type="s"> |
23 | + <default>'/usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg'</default> |
24 | + <summary>The alarm's default sound file.</summary> |
25 | + <description> |
26 | + If an alarm doesn't specify its own sound file, this file will be used as the fallback sound. |
27 | + </description> |
28 | + </key> |
29 | + <key name="alarm-default-volume" enum="alarm-volume-enum"> |
30 | + <default>'normal'</default> |
31 | + <summary>The alarm's default volume level.</summary> |
32 | + <description> |
33 | + The volume at which alarms will be played. |
34 | + </description> |
35 | + </key> |
36 | + <key name="alarm-duration-minutes" type="i"> |
37 | + <range min="1" max="60"/> |
38 | + <default>30</default> |
39 | + <summary>The alarm's duration.</summary> |
40 | + <description> |
41 | + How long the alarm's sound will be looped if its snap decision is not dismissed by the user. |
42 | + </description> |
43 | + </key> |
44 | </schema> |
45 | </schemalist> |
46 | |
47 | === modified file 'include/datetime/appointment.h' |
48 | --- include/datetime/appointment.h 2014-04-15 15:40:31 +0000 |
49 | +++ include/datetime/appointment.h 2014-06-27 14:06:11 +0000 |
50 | @@ -39,6 +39,7 @@ |
51 | std::string summary; |
52 | std::string url; |
53 | std::string uid; |
54 | + std::string audio_url; |
55 | bool has_alarms = false; |
56 | DateTime begin; |
57 | DateTime end; |
58 | |
59 | === modified file 'include/datetime/settings-live.h' |
60 | --- include/datetime/settings-live.h 2014-01-16 21:10:39 +0000 |
61 | +++ include/datetime/settings-live.h 2014-06-27 14:06:11 +0000 |
62 | @@ -55,6 +55,9 @@ |
63 | void update_show_year(); |
64 | void update_time_format_mode(); |
65 | void update_timezone_name(); |
66 | + void update_alarm_sound(); |
67 | + void update_alarm_volume(); |
68 | + void update_alarm_duration(); |
69 | |
70 | GSettings* m_settings; |
71 | |
72 | |
73 | === modified file 'include/datetime/settings-shared.h' |
74 | --- include/datetime/settings-shared.h 2014-01-15 05:07:10 +0000 |
75 | +++ include/datetime/settings-shared.h 2014-06-27 14:06:11 +0000 |
76 | @@ -30,6 +30,16 @@ |
77 | } |
78 | TimeFormatMode; |
79 | |
80 | +typedef enum |
81 | +{ |
82 | + ALARM_VOLUME_VERY_QUIET, |
83 | + ALARM_VOLUME_QUIET, |
84 | + ALARM_VOLUME_NORMAL, |
85 | + ALARM_VOLUME_LOUD, |
86 | + ALARM_VOLUME_VERY_LOUD |
87 | +} |
88 | +AlarmVolume; |
89 | + |
90 | #define SETTINGS_INTERFACE "com.canonical.indicator.datetime" |
91 | #define SETTINGS_SHOW_CLOCK_S "show-clock" |
92 | #define SETTINGS_TIME_FORMAT_S "time-format" |
93 | @@ -45,5 +55,8 @@ |
94 | #define SETTINGS_SHOW_DETECTED_S "show-auto-detected-location" |
95 | #define SETTINGS_LOCATIONS_S "locations" |
96 | #define SETTINGS_TIMEZONE_NAME_S "timezone-name" |
97 | +#define SETTINGS_ALARM_SOUND_S "alarm-default-sound" |
98 | +#define SETTINGS_ALARM_VOLUME_S "alarm-default-volume" |
99 | +#define SETTINGS_ALARM_DURATION_S "alarm-duration-minutes" |
100 | |
101 | #endif // INDICATOR_DATETIME_SETTINGS_SHARED |
102 | |
103 | === modified file 'include/datetime/settings.h' |
104 | --- include/datetime/settings.h 2014-01-22 16:03:57 +0000 |
105 | +++ include/datetime/settings.h 2014-06-27 14:06:11 +0000 |
106 | @@ -56,6 +56,9 @@ |
107 | core::Property<bool> show_year; |
108 | core::Property<TimeFormatMode> time_format_mode; |
109 | core::Property<std::string> timezone_name; |
110 | + core::Property<std::string> alarm_sound; |
111 | + core::Property<AlarmVolume> alarm_volume; |
112 | + core::Property<int> alarm_duration; |
113 | }; |
114 | |
115 | } // namespace datetime |
116 | |
117 | === modified file 'include/datetime/snap.h' |
118 | --- include/datetime/snap.h 2014-02-04 19:00:22 +0000 |
119 | +++ include/datetime/snap.h 2014-06-27 14:06:11 +0000 |
120 | @@ -21,6 +21,8 @@ |
121 | #define INDICATOR_DATETIME_SNAP_H |
122 | |
123 | #include <datetime/appointment.h> |
124 | +#include <datetime/clock.h> |
125 | +#include <datetime/settings.h> |
126 | |
127 | #include <memory> |
128 | #include <functional> |
129 | @@ -35,13 +37,18 @@ |
130 | class Snap |
131 | { |
132 | public: |
133 | - Snap(); |
134 | + Snap(const std::shared_ptr<Clock>& clock, |
135 | + const std::shared_ptr<const Settings>& settings); |
136 | virtual ~Snap(); |
137 | |
138 | typedef std::function<void(const Appointment&)> appointment_func; |
139 | void operator()(const Appointment& appointment, |
140 | appointment_func show, |
141 | appointment_func dismiss); |
142 | + |
143 | +private: |
144 | + const std::shared_ptr<Clock> m_clock; |
145 | + const std::shared_ptr<const Settings> m_settings; |
146 | }; |
147 | |
148 | } // namespace datetime |
149 | |
150 | === modified file 'src/engine-eds.cpp' |
151 | --- src/engine-eds.cpp 2014-06-10 14:02:17 +0000 |
152 | +++ src/engine-eds.cpp 2014-06-27 14:06:11 +0000 |
153 | @@ -443,8 +443,9 @@ |
154 | appointment.color = subtask->color; |
155 | appointment.uid = uid; |
156 | |
157 | - // if the component has display alarms that have a url, |
158 | - // use the first one as our Appointment.url |
159 | + // Look through all of this component's alarms |
160 | + // for DISPLAY or AUDIO url attachments. |
161 | + // If we find any, use them for appointment.url and audio_sound |
162 | auto alarm_uids = e_cal_component_get_alarm_uids(component); |
163 | appointment.has_alarms = alarm_uids != nullptr; |
164 | for(auto walk=alarm_uids; appointment.url.empty() && walk!=nullptr; walk=walk->next) |
165 | @@ -453,7 +454,7 @@ |
166 | |
167 | ECalComponentAlarmAction action; |
168 | e_cal_component_alarm_get_action(alarm, &action); |
169 | - if (action == E_CAL_COMPONENT_ALARM_DISPLAY) |
170 | + if ((action == E_CAL_COMPONENT_ALARM_DISPLAY) || (action == E_CAL_COMPONENT_ALARM_AUDIO)) |
171 | { |
172 | icalattach* attach = nullptr; |
173 | e_cal_component_alarm_get_attach(alarm, &attach); |
174 | @@ -463,7 +464,16 @@ |
175 | { |
176 | const char* url = icalattach_get_url(attach); |
177 | if (url != nullptr) |
178 | - appointment.url = url; |
179 | + { |
180 | + if ((action == E_CAL_COMPONENT_ALARM_DISPLAY) && appointment.url.empty()) |
181 | + { |
182 | + appointment.url = url; |
183 | + } |
184 | + else if ((action == E_CAL_COMPONENT_ALARM_AUDIO) && appointment.audio_url.empty()) |
185 | + { |
186 | + appointment.audio_url = url; |
187 | + } |
188 | + } |
189 | } |
190 | |
191 | icalattach_unref(attach); |
192 | |
193 | === modified file 'src/main.cpp' |
194 | --- src/main.cpp 2014-06-11 04:06:46 +0000 |
195 | +++ src/main.cpp 2014-06-27 14:06:11 +0000 |
196 | @@ -141,7 +141,7 @@ |
197 | MenuFactory factory(actions, state); |
198 | |
199 | // set up the snap decisions |
200 | - Snap snap; |
201 | + Snap snap (state->clock, state->settings); |
202 | auto alarm_queue = create_simple_alarm_queue(state->clock, engine, timezone); |
203 | alarm_queue->alarm_reached().connect([&snap](const Appointment& appt){ |
204 | auto snap_show = [](const Appointment& a){ |
205 | |
206 | === modified file 'src/settings-live.cpp' |
207 | --- src/settings-live.cpp 2014-01-16 21:10:39 +0000 |
208 | +++ src/settings-live.cpp 2014-06-27 14:06:11 +0000 |
209 | @@ -52,6 +52,9 @@ |
210 | update_show_year(); |
211 | update_time_format_mode(); |
212 | update_timezone_name(); |
213 | + update_alarm_sound(); |
214 | + update_alarm_volume(); |
215 | + update_alarm_duration(); |
216 | |
217 | // now listen for clients to change the properties s.t. we can sync update GSettings |
218 | |
219 | @@ -115,6 +118,18 @@ |
220 | timezone_name.changed().connect([this](const std::string& value){ |
221 | g_settings_set_string(m_settings, SETTINGS_TIMEZONE_NAME_S, value.c_str()); |
222 | }); |
223 | + |
224 | + alarm_sound.changed().connect([this](const std::string& value){ |
225 | + g_settings_set_string(m_settings, SETTINGS_ALARM_SOUND_S, value.c_str()); |
226 | + }); |
227 | + |
228 | + alarm_volume.changed().connect([this](AlarmVolume value){ |
229 | + g_settings_set_enum(m_settings, SETTINGS_ALARM_VOLUME_S, gint(value)); |
230 | + }); |
231 | + |
232 | + alarm_duration.changed().connect([this](int value){ |
233 | + g_settings_set_int(m_settings, SETTINGS_ALARM_DURATION_S, value); |
234 | + }); |
235 | } |
236 | |
237 | /*** |
238 | @@ -205,6 +220,23 @@ |
239 | g_free(val); |
240 | } |
241 | |
242 | +void LiveSettings::update_alarm_sound() |
243 | +{ |
244 | + auto val = g_settings_get_string(m_settings, SETTINGS_ALARM_SOUND_S); |
245 | + alarm_sound.set(val); |
246 | + g_free(val); |
247 | +} |
248 | + |
249 | +void LiveSettings::update_alarm_volume() |
250 | +{ |
251 | + alarm_volume.set((AlarmVolume)g_settings_get_enum(m_settings, SETTINGS_ALARM_VOLUME_S)); |
252 | +} |
253 | + |
254 | +void LiveSettings::update_alarm_duration() |
255 | +{ |
256 | + alarm_duration.set(g_settings_get_int(m_settings, SETTINGS_ALARM_DURATION_S)); |
257 | +} |
258 | + |
259 | /*** |
260 | **** |
261 | ***/ |
262 | @@ -246,6 +278,12 @@ |
263 | update_show_detected_locations(); |
264 | else if (key == SETTINGS_TIMEZONE_NAME_S) |
265 | update_timezone_name(); |
266 | + else if (key == SETTINGS_ALARM_SOUND_S) |
267 | + update_alarm_sound(); |
268 | + else if (key == SETTINGS_ALARM_VOLUME_S) |
269 | + update_alarm_volume(); |
270 | + else if (key == SETTINGS_ALARM_DURATION_S) |
271 | + update_alarm_duration(); |
272 | } |
273 | |
274 | /*** |
275 | |
276 | === modified file 'src/snap.cpp' |
277 | --- src/snap.cpp 2014-05-28 07:35:09 +0000 |
278 | +++ src/snap.cpp 2014-06-27 14:06:11 +0000 |
279 | @@ -21,6 +21,8 @@ |
280 | #include <datetime/formatter.h> |
281 | #include <datetime/snap.h> |
282 | |
283 | +#include <core/signal.h> |
284 | + |
285 | #include <canberra.h> |
286 | #include <libnotify/notify.h> |
287 | |
288 | @@ -30,8 +32,6 @@ |
289 | #include <set> |
290 | #include <string> |
291 | |
292 | -#define ALARM_SOUND_FILENAME "/usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg" |
293 | - |
294 | namespace unity { |
295 | namespace indicator { |
296 | namespace datetime { |
297 | @@ -43,266 +43,472 @@ |
298 | namespace |
299 | { |
300 | |
301 | -/** |
302 | -*** libcanberra -- play sounds |
303 | -**/ |
304 | - |
305 | -// arbitrary number, but we need a consistent id for play/cancel |
306 | -const int32_t alarm_ca_id = 1; |
307 | - |
308 | -ca_context *c_context = nullptr; |
309 | -guint timeout_tag = 0; |
310 | - |
311 | -ca_context* get_ca_context() |
312 | -{ |
313 | - if (G_UNLIKELY(c_context == nullptr)) |
314 | - { |
315 | - int rv; |
316 | - |
317 | - if ((rv = ca_context_create(&c_context)) != CA_SUCCESS) |
318 | - { |
319 | - g_warning("Failed to create canberra context: %s\n", ca_strerror(rv)); |
320 | - c_context = nullptr; |
321 | - } |
322 | - } |
323 | - |
324 | - return c_context; |
325 | -} |
326 | - |
327 | -void play_alarm_sound(); |
328 | - |
329 | -gboolean play_alarm_sound_idle (gpointer) |
330 | -{ |
331 | - timeout_tag = 0; |
332 | - play_alarm_sound(); |
333 | - return G_SOURCE_REMOVE; |
334 | -} |
335 | - |
336 | -void on_alarm_play_done (ca_context* /*context*/, uint32_t /*id*/, int rv, void* /*user_data*/) |
337 | -{ |
338 | - // wait one second, then play it again |
339 | - if ((rv == CA_SUCCESS) && (timeout_tag == 0)) |
340 | - timeout_tag = g_timeout_add_seconds (1, play_alarm_sound_idle, nullptr); |
341 | -} |
342 | - |
343 | -void play_alarm_sound() |
344 | -{ |
345 | - const gchar* filename = ALARM_SOUND_FILENAME; |
346 | - auto context = get_ca_context(); |
347 | - g_return_if_fail(context != nullptr); |
348 | - |
349 | - ca_proplist* props = nullptr; |
350 | - ca_proplist_create(&props); |
351 | - ca_proplist_sets(props, CA_PROP_MEDIA_FILENAME, filename); |
352 | - |
353 | - const auto rv = ca_context_play_full(context, alarm_ca_id, props, on_alarm_play_done, nullptr); |
354 | - if (rv != CA_SUCCESS) |
355 | - g_warning("Failed to play file '%s': %s", filename, ca_strerror(rv)); |
356 | - |
357 | - g_clear_pointer(&props, ca_proplist_destroy); |
358 | -} |
359 | - |
360 | -void stop_alarm_sound() |
361 | -{ |
362 | - auto context = get_ca_context(); |
363 | - if (context != nullptr) |
364 | - { |
365 | - const auto rv = ca_context_cancel(context, alarm_ca_id); |
366 | +/** |
367 | + * Plays a sound, possibly looping. |
368 | + */ |
369 | +class Sound |
370 | +{ |
371 | + typedef Sound Self; |
372 | + |
373 | +public: |
374 | + |
375 | + Sound(const std::shared_ptr<Clock>& clock, |
376 | + const std::string& filename, |
377 | + AlarmVolume volume, |
378 | + int duration_minutes, |
379 | + bool loop): |
380 | + m_clock(clock), |
381 | + m_filename(filename), |
382 | + m_volume(volume), |
383 | + m_duration_minutes(duration_minutes), |
384 | + m_loop(loop), |
385 | + m_canberra_id(get_next_canberra_id()), |
386 | + m_loop_end_time(clock->localtime().add_full(0, 0, 0, 0, duration_minutes, 0.0)) |
387 | + { |
388 | + if (m_loop) |
389 | + { |
390 | + g_debug("Looping '%s' until cutoff time %s", |
391 | + m_filename.c_str(), |
392 | + m_loop_end_time.format("%F %T").c_str()); |
393 | + } |
394 | + else |
395 | + { |
396 | + g_debug("Playing '%s' once", m_filename.c_str()); |
397 | + } |
398 | + |
399 | + const auto rv = ca_context_create(&m_context); |
400 | + if (rv == CA_SUCCESS) |
401 | + { |
402 | + play(); |
403 | + } |
404 | + else |
405 | + { |
406 | + g_warning("Failed to create canberra context: %s", ca_strerror(rv)); |
407 | + m_context = nullptr; |
408 | + } |
409 | + } |
410 | + |
411 | + ~Sound() |
412 | + { |
413 | + stop(); |
414 | + |
415 | + g_clear_pointer(&m_context, ca_context_destroy); |
416 | + } |
417 | + |
418 | +private: |
419 | + |
420 | + void stop() |
421 | + { |
422 | + if (m_context != nullptr) |
423 | + { |
424 | + const auto rv = ca_context_cancel(m_context, m_canberra_id); |
425 | + if (rv != CA_SUCCESS) |
426 | + g_warning("Failed to cancel alarm sound: %s", ca_strerror(rv)); |
427 | + } |
428 | + |
429 | + if (m_loop_tag != 0) |
430 | + { |
431 | + g_source_remove(m_loop_tag); |
432 | + m_loop_tag = 0; |
433 | + } |
434 | + } |
435 | + |
436 | + void play() |
437 | + { |
438 | + auto context = m_context; |
439 | + g_return_if_fail(context != nullptr); |
440 | + |
441 | + const auto filename = m_filename.c_str(); |
442 | + const float gain = get_gain_level(m_volume); |
443 | + |
444 | + ca_proplist* props = nullptr; |
445 | + ca_proplist_create(&props); |
446 | + ca_proplist_sets(props, CA_PROP_MEDIA_FILENAME, filename); |
447 | + ca_proplist_setf(props, CA_PROP_CANBERRA_VOLUME, "%f", gain); |
448 | + const auto rv = ca_context_play_full(context, m_canberra_id, props, |
449 | + on_done_playing, this); |
450 | if (rv != CA_SUCCESS) |
451 | - g_warning("Failed to cancel alarm sound: %s", ca_strerror(rv)); |
452 | - } |
453 | - |
454 | - if (timeout_tag != 0) |
455 | - { |
456 | - g_source_remove(timeout_tag); |
457 | - timeout_tag = 0; |
458 | - } |
459 | -} |
460 | + g_warning("Unable to play '%s': %s", filename, ca_strerror(rv)); |
461 | + |
462 | + g_clear_pointer(&props, ca_proplist_destroy); |
463 | + } |
464 | + |
465 | + static float get_gain_level(const AlarmVolume volume) |
466 | + { |
467 | + /* These values aren't set in stone -- |
468 | + arrived at from from manual tests on Nexus 4 */ |
469 | + switch (volume) |
470 | + { |
471 | + case ALARM_VOLUME_VERY_QUIET: return -8; |
472 | + case ALARM_VOLUME_QUIET: return -4; |
473 | + case ALARM_VOLUME_LOUD: return 4; |
474 | + case ALARM_VOLUME_VERY_LOUD: return 8; |
475 | + default: return 0; |
476 | + } |
477 | + } |
478 | + |
479 | + static void on_done_playing(ca_context*, uint32_t, int rv, void* gself) |
480 | + { |
481 | + // if we still need to loop, wait a second, then play it again |
482 | + |
483 | + if (rv == CA_SUCCESS) |
484 | + { |
485 | + auto self = static_cast<Self*>(gself); |
486 | + if ((self->m_loop_tag == 0) && |
487 | + (self->m_loop) && |
488 | + (self->m_clock->localtime() < self->m_loop_end_time)) |
489 | + { |
490 | + self->m_loop_tag = g_timeout_add_seconds(1, play_idle, self); |
491 | + } |
492 | + } |
493 | + } |
494 | + |
495 | + static gboolean play_idle(gpointer gself) |
496 | + { |
497 | + auto self = static_cast<Self*>(gself); |
498 | + self->m_loop_tag = 0; |
499 | + self->play(); |
500 | + return G_SOURCE_REMOVE; |
501 | + } |
502 | + |
503 | + /*** |
504 | + **** |
505 | + ***/ |
506 | + |
507 | + static int32_t get_next_canberra_id() |
508 | + { |
509 | + static int32_t next_canberra_id = 1; |
510 | + return next_canberra_id++; |
511 | + } |
512 | + |
513 | + const std::shared_ptr<Clock> m_clock; |
514 | + const std::string m_filename; |
515 | + const AlarmVolume m_volume; |
516 | + const int m_duration_minutes; |
517 | + const bool m_loop; |
518 | + const int32_t m_canberra_id; |
519 | + const DateTime m_loop_end_time; |
520 | + ca_context* m_context = nullptr; |
521 | + guint m_loop_tag = 0; |
522 | +}; |
523 | + |
524 | +class SoundBuilder |
525 | +{ |
526 | +public: |
527 | + void set_clock(const std::shared_ptr<Clock>& c) {m_clock = c;} |
528 | + void set_filename(const std::string& s) {m_filename = s;} |
529 | + void set_volume(const AlarmVolume v) {m_volume = v;} |
530 | + void set_duration_minutes(int i) {m_duration_minutes=i;} |
531 | + void set_looping(bool b) {m_looping=b;} |
532 | + |
533 | + Sound* operator()() { |
534 | + return new Sound (m_clock, |
535 | + m_filename, |
536 | + m_volume, |
537 | + m_duration_minutes, |
538 | + m_looping); |
539 | + } |
540 | + |
541 | +private: |
542 | + std::shared_ptr<Clock> m_clock; |
543 | + std::string m_filename; |
544 | + AlarmVolume m_volume = ALARM_VOLUME_NORMAL; |
545 | + int m_duration_minutes = 30; |
546 | + bool m_looping = true; |
547 | +}; |
548 | + |
549 | +/** |
550 | + * A popup notification (with optional sound) |
551 | + * that emits a Response signal when done. |
552 | + */ |
553 | +class Popup |
554 | +{ |
555 | +public: |
556 | + |
557 | + Popup(const Appointment& appointment, const SoundBuilder& sound_builder): |
558 | + m_appointment(appointment), |
559 | + m_interactive(get_interactive()), |
560 | + m_sound_builder(sound_builder) |
561 | + { |
562 | + // ensure notify_init() is called once |
563 | + // before we start popping up dialogs |
564 | + static bool m_nn_inited = false; |
565 | + if (G_UNLIKELY(!m_nn_inited)) |
566 | + { |
567 | + if(!notify_init("indicator-datetime-service")) |
568 | + g_critical("libnotify initialization failed"); |
569 | + |
570 | + m_nn_inited = true; |
571 | + } |
572 | + |
573 | + show(); |
574 | + } |
575 | + |
576 | + ~Popup() |
577 | + { |
578 | + if (m_nn != nullptr) |
579 | + { |
580 | + notify_notification_clear_actions(m_nn); |
581 | + g_signal_handlers_disconnect_by_data(m_nn, this); |
582 | + g_clear_object(&m_nn); |
583 | + } |
584 | + } |
585 | + |
586 | + typedef enum |
587 | + { |
588 | + RESPONSE_SHOW, |
589 | + RESPONSE_DISMISS, |
590 | + RESPONSE_CLOSE |
591 | + } |
592 | + Response; |
593 | + |
594 | + core::Signal<Response>& response() { return m_response; } |
595 | + |
596 | +private: |
597 | + |
598 | + void show() |
599 | + { |
600 | + const Appointment& appointment = m_appointment; |
601 | + |
602 | + /// strftime(3) format string for an alarm's snap decision |
603 | + const auto timestr = appointment.begin.format(_("%a, %X")); |
604 | + auto title = g_strdup_printf(_("Alarm %s"), timestr.c_str()); |
605 | + const auto body = appointment.summary; |
606 | + const gchar* icon_name = "alarm-clock"; |
607 | + |
608 | + m_nn = notify_notification_new(title, body.c_str(), icon_name); |
609 | + if (m_interactive) |
610 | + { |
611 | + notify_notification_set_hint_string(m_nn, |
612 | + "x-canonical-snap-decisions", |
613 | + "true"); |
614 | + notify_notification_set_hint_string(m_nn, |
615 | + "x-canonical-private-button-tint", |
616 | + "true"); |
617 | + /// alarm popup dialog's button to show the active alarm |
618 | + notify_notification_add_action(m_nn, "show", _("Show"), |
619 | + on_snap_show, this, nullptr); |
620 | + /// alarm popup dialog's button to shut up the alarm |
621 | + notify_notification_add_action(m_nn, "dismiss", _("Dismiss"), |
622 | + on_snap_dismiss, this, nullptr); |
623 | + g_signal_connect(m_nn, "closed", G_CALLBACK(on_snap_closed), this); |
624 | + } |
625 | + |
626 | + bool shown = true; |
627 | + GError* error = nullptr; |
628 | + notify_notification_show(m_nn, &error); |
629 | + if (error != NULL) |
630 | + { |
631 | + g_critical("Unable to show snap decision for '%s': %s", |
632 | + body.c_str(), error->message); |
633 | + g_error_free(error); |
634 | + shown = false; |
635 | + } |
636 | + |
637 | + // Loop the sound *only* if we're prompting the user for a response. |
638 | + // Otherwise, just play the sound once. |
639 | + m_sound_builder.set_looping (shown && m_interactive); |
640 | + m_sound.reset (m_sound_builder()); |
641 | + |
642 | + // if showing the notification didn't work, |
643 | + // treat it as if the user clicked the 'show' button |
644 | + if (!shown) |
645 | + { |
646 | + on_snap_show(nullptr, nullptr, this); |
647 | + on_snap_dismiss(nullptr, nullptr, this); |
648 | + } |
649 | + |
650 | + g_free(title); |
651 | + } |
652 | + |
653 | + // user clicked 'show' |
654 | + static void on_snap_show(NotifyNotification*, gchar*, gpointer gself) |
655 | + { |
656 | + auto self = static_cast<Self*>(gself); |
657 | + self->m_response_value = RESPONSE_SHOW; |
658 | + self->m_sound.reset(); |
659 | + } |
660 | + |
661 | + // user clicked 'dismiss' |
662 | + static void on_snap_dismiss(NotifyNotification*, gchar*, gpointer gself) |
663 | + { |
664 | + auto self = static_cast<Self*>(gself); |
665 | + self->m_response_value = RESPONSE_DISMISS; |
666 | + self->m_sound.reset(); |
667 | + } |
668 | + |
669 | + // the popup was closed |
670 | + static void on_snap_closed(NotifyNotification*, gpointer gself) |
671 | + { |
672 | + auto self = static_cast<Self*>(gself); |
673 | + self->m_sound.reset(); |
674 | + self->m_response(self->m_response_value); |
675 | + } |
676 | + |
677 | + /*** |
678 | + **** Interactive |
679 | + ***/ |
680 | + |
681 | + static std::set<std::string> get_server_caps() |
682 | + { |
683 | + std::set<std::string> caps_set; |
684 | + auto caps_gl = notify_get_server_caps(); |
685 | + std::string caps_str; |
686 | + for(auto l=caps_gl; l!=nullptr; l=l->next) |
687 | + { |
688 | + caps_set.insert((const char*)l->data); |
689 | + |
690 | + caps_str += (const char*) l->data;; |
691 | + if (l->next != nullptr) |
692 | + caps_str += ", "; |
693 | + } |
694 | + g_debug("%s notify_get_server() returned [%s]", G_STRFUNC, caps_str.c_str()); |
695 | + g_list_free_full(caps_gl, g_free); |
696 | + return caps_set; |
697 | + } |
698 | + |
699 | + static bool get_interactive() |
700 | + { |
701 | + static bool interactive; |
702 | + static bool inited = false; |
703 | + |
704 | + if (G_UNLIKELY(!inited)) |
705 | + { |
706 | + interactive = get_server_caps().count("actions") != 0; |
707 | + inited = true; |
708 | + } |
709 | + |
710 | + return interactive; |
711 | + } |
712 | + |
713 | + /*** |
714 | + **** |
715 | + ***/ |
716 | + |
717 | + typedef Popup Self; |
718 | + |
719 | + const Appointment m_appointment; |
720 | + const bool m_interactive; |
721 | + SoundBuilder m_sound_builder; |
722 | + std::unique_ptr<Sound> m_sound; |
723 | + core::Signal<Response> m_response; |
724 | + Response m_response_value = RESPONSE_CLOSE; |
725 | + NotifyNotification* m_nn = nullptr; |
726 | +}; |
727 | |
728 | /** |
729 | *** libnotify -- snap decisions |
730 | **/ |
731 | |
732 | -void first_time_init() |
733 | -{ |
734 | - static bool inited = false; |
735 | - |
736 | - if (G_UNLIKELY(!inited)) |
737 | - { |
738 | - inited = true; |
739 | - |
740 | - if(!notify_init("indicator-datetime-service")) |
741 | - g_critical("libnotify initialization failed"); |
742 | - } |
743 | -} |
744 | - |
745 | -struct SnapData |
746 | -{ |
747 | - Snap::appointment_func show; |
748 | - Snap::appointment_func dismiss; |
749 | - Appointment appointment; |
750 | -}; |
751 | - |
752 | -void on_snap_show(NotifyNotification*, gchar* /*action*/, gpointer gdata) |
753 | -{ |
754 | - stop_alarm_sound(); |
755 | - auto data = static_cast<SnapData*>(gdata); |
756 | - data->show(data->appointment); |
757 | -} |
758 | - |
759 | -void on_snap_dismiss(NotifyNotification*, gchar* /*action*/, gpointer gdata) |
760 | -{ |
761 | - stop_alarm_sound(); |
762 | - auto data = static_cast<SnapData*>(gdata); |
763 | - data->dismiss(data->appointment); |
764 | -} |
765 | - |
766 | -void on_snap_closed(NotifyNotification*, gpointer) |
767 | -{ |
768 | - stop_alarm_sound(); |
769 | -} |
770 | - |
771 | -void snap_data_destroy_notify(gpointer gdata) |
772 | -{ |
773 | - delete static_cast<SnapData*>(gdata); |
774 | -} |
775 | - |
776 | -std::set<std::string> get_server_caps() |
777 | -{ |
778 | - std::set<std::string> caps_set; |
779 | - auto caps_gl = notify_get_server_caps(); |
780 | - std::string caps_str; |
781 | - for(auto l=caps_gl; l!=nullptr; l=l->next) |
782 | - { |
783 | - caps_set.insert((const char*)l->data); |
784 | - |
785 | - caps_str += (const char*) l->data;; |
786 | - if (l->next != nullptr) |
787 | - caps_str += ", "; |
788 | - } |
789 | - g_debug ("%s notify_get_server() returned [%s]", G_STRFUNC, caps_str.c_str()); |
790 | - g_list_free_full(caps_gl, g_free); |
791 | - return caps_set; |
792 | -} |
793 | - |
794 | -typedef enum |
795 | -{ |
796 | - // just a bubble... no actions, no audio |
797 | - NOTIFY_MODE_BUBBLE, |
798 | - |
799 | - // a snap decision popup dialog + audio |
800 | - NOTIFY_MODE_SNAP |
801 | -} |
802 | -NotifyMode; |
803 | - |
804 | -NotifyMode get_notify_mode() |
805 | -{ |
806 | - static NotifyMode mode; |
807 | - static bool mode_inited = false; |
808 | - |
809 | - if (G_UNLIKELY(!mode_inited)) |
810 | - { |
811 | - const auto caps = get_server_caps(); |
812 | - |
813 | - if (caps.count("actions")) |
814 | - mode = NOTIFY_MODE_SNAP; |
815 | - else |
816 | - mode = NOTIFY_MODE_BUBBLE; |
817 | - |
818 | - mode_inited = true; |
819 | - } |
820 | - |
821 | - return mode; |
822 | -} |
823 | - |
824 | -bool show_notification (SnapData* data, NotifyMode mode) |
825 | -{ |
826 | - const Appointment& appointment = data->appointment; |
827 | - |
828 | - const auto timestr = appointment.begin.format("%a, %X"); |
829 | - auto title = g_strdup_printf(_("Alarm %s"), timestr.c_str()); |
830 | - const auto body = appointment.summary; |
831 | - const gchar* icon_name = "alarm-clock"; |
832 | - |
833 | - auto nn = notify_notification_new(title, body.c_str(), icon_name); |
834 | - if (mode == NOTIFY_MODE_SNAP) |
835 | - { |
836 | - notify_notification_set_hint_string(nn, "x-canonical-snap-decisions", "true"); |
837 | - notify_notification_set_hint_string(nn, "x-canonical-private-button-tint", "true"); |
838 | - /* text for the alarm popup dialog's button to show the active alarm */ |
839 | - notify_notification_add_action(nn, "show", _("Show"), on_snap_show, data, nullptr); |
840 | - /* text for the alarm popup dialog's button to shut up the alarm */ |
841 | - notify_notification_add_action(nn, "dismiss", _("Dismiss"), on_snap_dismiss, data, nullptr); |
842 | - g_signal_connect(G_OBJECT(nn), "closed", G_CALLBACK(on_snap_closed), data); |
843 | - } |
844 | - g_object_set_data_full(G_OBJECT(nn), "snap-data", data, snap_data_destroy_notify); |
845 | - |
846 | - bool shown = true; |
847 | - GError * error = nullptr; |
848 | - notify_notification_show(nn, &error); |
849 | - if (error != NULL) |
850 | - { |
851 | - g_critical("Unable to show snap decision for '%s': %s", body.c_str(), error->message); |
852 | - g_error_free(error); |
853 | - data->show(data->appointment); |
854 | - shown = false; |
855 | - } |
856 | - |
857 | - g_free(title); |
858 | - return shown; |
859 | -} |
860 | - |
861 | -/** |
862 | -*** |
863 | -**/ |
864 | - |
865 | -void notify(const Appointment& appointment, |
866 | - Snap::appointment_func show, |
867 | - Snap::appointment_func dismiss) |
868 | -{ |
869 | - auto data = new SnapData; |
870 | - data->appointment = appointment; |
871 | - data->show = show; |
872 | - data->dismiss = dismiss; |
873 | - |
874 | - switch (get_notify_mode()) |
875 | - { |
876 | - case NOTIFY_MODE_BUBBLE: |
877 | - show_notification(data, NOTIFY_MODE_BUBBLE); |
878 | - break; |
879 | - |
880 | - default: |
881 | - if (show_notification(data, NOTIFY_MODE_SNAP)) |
882 | - play_alarm_sound(); |
883 | - break; |
884 | - } |
885 | +std::string get_local_filename (const std::string& str) |
886 | +{ |
887 | + std::string ret; |
888 | + |
889 | + if (!str.empty()) |
890 | + { |
891 | + GFile* files[] = { g_file_new_for_path(str.c_str()), |
892 | + g_file_new_for_uri(str.c_str()) }; |
893 | + |
894 | + for(auto& file : files) |
895 | + { |
896 | + if (g_file_is_native(file) && g_file_query_exists(file, nullptr)) |
897 | + { |
898 | + char* tmp = g_file_get_path(file); |
899 | + if (tmp != nullptr) |
900 | + { |
901 | + ret = tmp; |
902 | + g_free(tmp); |
903 | + break; |
904 | + } |
905 | + } |
906 | + } |
907 | + |
908 | + for(auto& file : files) |
909 | + g_object_unref(file); |
910 | + } |
911 | + |
912 | + return ret; |
913 | +} |
914 | + |
915 | +std::string get_alarm_sound(const Appointment& appointment, |
916 | + const std::shared_ptr<const Settings>& settings) |
917 | +{ |
918 | + const char* FALLBACK {"/usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg"}; |
919 | + |
920 | + const std::string candidates[] = { appointment.audio_url, |
921 | + settings->alarm_sound.get(), |
922 | + FALLBACK }; |
923 | + |
924 | + std::string alarm_sound; |
925 | + |
926 | + for(const auto& candidate : candidates) |
927 | + { |
928 | + alarm_sound = get_local_filename(candidate); |
929 | + |
930 | + if (!alarm_sound.empty()) |
931 | + break; |
932 | + } |
933 | + |
934 | + g_debug("%s: Appointment \"%s\" using alarm sound \"%s\"", |
935 | + G_STRFUNC, appointment.summary.c_str(), alarm_sound.c_str()); |
936 | + |
937 | + return alarm_sound; |
938 | } |
939 | |
940 | } // unnamed namespace |
941 | |
942 | - |
943 | /*** |
944 | **** |
945 | ***/ |
946 | |
947 | -Snap::Snap() |
948 | +Snap::Snap(const std::shared_ptr<Clock>& clock, |
949 | + const std::shared_ptr<const Settings>& settings): |
950 | + m_clock(clock), |
951 | + m_settings(settings) |
952 | { |
953 | - first_time_init(); |
954 | } |
955 | |
956 | Snap::~Snap() |
957 | { |
958 | - g_clear_pointer(&c_context, ca_context_destroy); |
959 | } |
960 | |
961 | void Snap::operator()(const Appointment& appointment, |
962 | appointment_func show, |
963 | appointment_func dismiss) |
964 | { |
965 | - if (appointment.has_alarms) |
966 | - notify(appointment, show, dismiss); |
967 | - else |
968 | + if (!appointment.has_alarms) |
969 | + { |
970 | dismiss(appointment); |
971 | + return; |
972 | + } |
973 | + |
974 | + // create a popup... |
975 | + SoundBuilder sound_builder; |
976 | + sound_builder.set_filename(get_alarm_sound(appointment, m_settings)); |
977 | + sound_builder.set_volume(m_settings->alarm_volume.get()); |
978 | + sound_builder.set_clock(m_clock); |
979 | + sound_builder.set_duration_minutes(m_settings->alarm_duration.get()); |
980 | + auto popup = new Popup(appointment, sound_builder); |
981 | + |
982 | + // listen for it to finish... |
983 | + popup->response().connect([appointment, |
984 | + show, |
985 | + dismiss, |
986 | + popup](Popup::Response response){ |
987 | + |
988 | + // we can't delete the Popup inside its response() signal handler, |
989 | + // so push that to an idle func |
990 | + g_idle_add([](gpointer gdata){ |
991 | + delete static_cast<Popup*>(gdata); |
992 | + return G_SOURCE_REMOVE; |
993 | + }, popup); |
994 | + |
995 | + // maybe notify the client code that the popup's done |
996 | + if (response == Popup::RESPONSE_SHOW) |
997 | + show(appointment); |
998 | + else if (response == Popup::RESPONSE_DISMISS) |
999 | + dismiss(appointment); |
1000 | + }); |
1001 | } |
1002 | |
1003 | /*** |
1004 | |
1005 | === modified file 'tests/manual-test-snap.cpp' |
1006 | --- tests/manual-test-snap.cpp 2014-04-15 15:40:31 +0000 |
1007 | +++ tests/manual-test-snap.cpp 2014-06-27 14:06:11 +0000 |
1008 | @@ -19,16 +19,30 @@ |
1009 | */ |
1010 | |
1011 | #include <datetime/appointment.h> |
1012 | +#include <datetime/settings-live.h> |
1013 | #include <datetime/snap.h> |
1014 | +#include <datetime/timezones-live.h> |
1015 | |
1016 | #include <glib.h> |
1017 | |
1018 | using namespace unity::indicator::datetime; |
1019 | |
1020 | +#define TIMEZONE_FILE ("/etc/timezone") |
1021 | + |
1022 | + |
1023 | /*** |
1024 | **** |
1025 | ***/ |
1026 | |
1027 | +namespace |
1028 | +{ |
1029 | + gboolean quit_idle (gpointer gloop) |
1030 | + { |
1031 | + g_main_loop_quit(static_cast<GMainLoop*>(gloop)); |
1032 | + return G_SOURCE_REMOVE; |
1033 | + }; |
1034 | +} |
1035 | + |
1036 | int main() |
1037 | { |
1038 | Appointment a; |
1039 | @@ -47,15 +61,24 @@ |
1040 | auto loop = g_main_loop_new(nullptr, false); |
1041 | auto show = [loop](const Appointment& appt){ |
1042 | g_message("You clicked 'show' for appt url '%s'", appt.url.c_str()); |
1043 | - g_main_loop_quit(loop); |
1044 | + g_idle_add(quit_idle, loop); |
1045 | }; |
1046 | auto dismiss = [loop](const Appointment&){ |
1047 | g_message("You clicked 'dismiss'"); |
1048 | - g_main_loop_quit(loop); |
1049 | + g_idle_add(quit_idle, loop); |
1050 | }; |
1051 | - |
1052 | - Snap snap; |
1053 | + |
1054 | + // only use local, temporary settings |
1055 | + g_assert(g_setenv("GSETTINGS_SCHEMA_DIR", SCHEMA_DIR, true)); |
1056 | + g_assert(g_setenv("GSETTINGS_BACKEND", "memory", true)); |
1057 | + g_debug("SCHEMA_DIR is %s", SCHEMA_DIR); |
1058 | + |
1059 | + auto settings = std::make_shared<LiveSettings>(); |
1060 | + auto timezones = std::make_shared<LiveTimezones>(settings, TIMEZONE_FILE); |
1061 | + auto clock = std::make_shared<LiveClock>(timezones); |
1062 | + Snap snap (clock, settings); |
1063 | snap(a, show, dismiss); |
1064 | g_main_loop_run(loop); |
1065 | + g_main_loop_unref(loop); |
1066 | return 0; |
1067 | } |
1068 | |
1069 | === modified file 'tests/test-settings.cpp' |
1070 | --- tests/test-settings.cpp 2014-02-02 21:29:29 +0000 |
1071 | +++ tests/test-settings.cpp 2014-06-27 14:06:11 +0000 |
1072 | @@ -100,6 +100,29 @@ |
1073 | EXPECT_EQ(str, tmp); |
1074 | g_clear_pointer(&tmp, g_free); |
1075 | } |
1076 | + |
1077 | + void TestIntProperty(core::Property<int>& property, const gchar* key) |
1078 | + { |
1079 | + EXPECT_EQ(g_settings_get_int(m_gsettings, key), property.get()); |
1080 | + |
1081 | + int expected_values[] = { 1, 2, 3 }; |
1082 | + |
1083 | + // modify GSettings and confirm that the new value is propagated |
1084 | + for(const int& expected_value : expected_values) |
1085 | + { |
1086 | + g_settings_set_int(m_gsettings, key, expected_value); |
1087 | + EXPECT_EQ(expected_value, property.get()); |
1088 | + EXPECT_EQ(expected_value, g_settings_get_int(m_gsettings, key)); |
1089 | + } |
1090 | + |
1091 | + // modify the property and confirm that the new value is propagated |
1092 | + for(const int& expected_value : expected_values) |
1093 | + { |
1094 | + property.set(expected_value); |
1095 | + EXPECT_EQ(expected_value, property.get()); |
1096 | + EXPECT_EQ(expected_value, g_settings_get_int(m_gsettings, key)); |
1097 | + } |
1098 | + } |
1099 | }; |
1100 | |
1101 | /*** |
1102 | @@ -125,10 +148,16 @@ |
1103 | TestBoolProperty(m_settings->show_year, SETTINGS_SHOW_YEAR_S); |
1104 | } |
1105 | |
1106 | +TEST_F(SettingsFixture, IntProperties) |
1107 | +{ |
1108 | + TestIntProperty(m_settings->alarm_duration, SETTINGS_ALARM_DURATION_S); |
1109 | +} |
1110 | + |
1111 | TEST_F(SettingsFixture, StringProperties) |
1112 | { |
1113 | TestStringProperty(m_settings->custom_time_format, SETTINGS_CUSTOM_TIME_FORMAT_S); |
1114 | TestStringProperty(m_settings->timezone_name, SETTINGS_TIMEZONE_NAME_S); |
1115 | + TestStringProperty(m_settings->alarm_sound, SETTINGS_ALARM_SOUND_S); |
1116 | } |
1117 | |
1118 | TEST_F(SettingsFixture, TimeFormatMode) |
1119 | @@ -152,6 +181,31 @@ |
1120 | } |
1121 | } |
1122 | |
1123 | +TEST_F(SettingsFixture, AlarmVolume) |
1124 | +{ |
1125 | + const auto key = SETTINGS_ALARM_VOLUME_S; |
1126 | + const AlarmVolume volumes[] = { ALARM_VOLUME_VERY_QUIET, |
1127 | + ALARM_VOLUME_QUIET, |
1128 | + ALARM_VOLUME_NORMAL, |
1129 | + ALARM_VOLUME_LOUD, |
1130 | + ALARM_VOLUME_VERY_LOUD }; |
1131 | + |
1132 | + for(const auto& val : volumes) |
1133 | + { |
1134 | + g_settings_set_enum(m_gsettings, key, val); |
1135 | + EXPECT_EQ(val, m_settings->alarm_volume.get()); |
1136 | + EXPECT_EQ(val, g_settings_get_enum(m_gsettings, key)); |
1137 | + } |
1138 | + |
1139 | + for(const auto& val : volumes) |
1140 | + { |
1141 | + m_settings->alarm_volume.set(val); |
1142 | + EXPECT_EQ(val, m_settings->alarm_volume.get()); |
1143 | + EXPECT_EQ(val, g_settings_get_enum(m_gsettings, key)); |
1144 | + } |
1145 | +} |
1146 | + |
1147 | + |
1148 | namespace |
1149 | { |
1150 | std::vector<std::string> strv_to_vector(const gchar** strv) |
PASSED: Continuous integration, rev:352 jenkins. qa.ubuntu. com/job/ indicator- datetime- ci/234/ jenkins. qa.ubuntu. com/job/ indicator- datetime- utopic- amd64-ci/ 8 jenkins. qa.ubuntu. com/job/ indicator- datetime- utopic- armhf-ci/ 8 jenkins. qa.ubuntu. com/job/ indicator- datetime- utopic- armhf-ci/ 8/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/234/ rebuild
http://