Merge lp:~charlesk/indicator-datetime/lp-1233176 into lp:~indicator-applet-developers/indicator-datetime/trunk.13.10
- lp-1233176
- Merge into trunk.13.10
Status: | Merged |
---|---|
Approved by: | Ted Gould |
Approved revision: | 289 |
Merged at revision: | 277 |
Proposed branch: | lp:~charlesk/indicator-datetime/lp-1233176 |
Merge into: | lp:~indicator-applet-developers/indicator-datetime/trunk.13.10 |
Diff against target: |
1389 lines (+861/-107) 13 files modified
README (+7/-1) configure.ac (+2/-0) debian/control (+1/-0) src/Makefile.am (+2/-0) src/main.c (+45/-4) src/planner-eds.c (+179/-70) src/planner-eds.h (+0/-2) src/planner-mock.c (+178/-0) src/planner-mock.h (+58/-0) src/planner.c (+20/-0) src/planner.h (+4/-2) src/service.c (+359/-27) src/service.h (+6/-1) |
To merge this branch: | bzr merge lp:~charlesk/indicator-datetime/lp-1233176 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot (community) | continuous-integration | Approve | |
Ted Gould (community) | Approve | ||
Review via email: mp+190009@code.launchpad.net |
Commit message
Description of the change
== Changes to planner-eds:
The get-appointments GTask has a new task subtype for pulling an ECalComponent's uris asynchronously.
When get_appointments() is called, create one GTask. We add subtasks to it for each client we know of for calling e_cal_client_
== Testing changes:
Make "planner" a property in IndicatorDateti
Add a mechanism for testing snap decisions without an EDS backend.
== Service changes:
Every time the appointment list changes, walk through it to find the alarm that will occur the soonest. Set a timer to wake up at that time. When the timer is reached, pop up a snap decision for each alarm set to that time. If the user clicks "OK", dispatch the URL associated with that alarm.
Made the appointment menuitems clickable, they now dispatch the appointment's URL.
PS Jenkins bot (ps-jenkins) wrote : | # |
Ted Gould (ted) wrote : | # |
I'm worried a bit about the alarm implementation, it seems like just checking for alarms every minute is a not as eloquent as we can be. We know when the next alarm is, we should wait that amount of time.
I haven't seen the final designs here, but I'm a bit surprised that we're launching the app when the alarm goes off. This will cause a focus change. Which means that you can potentially have your work interrupted by an alarm. It seems to me that there should be a notification with the option to ignore or open the URL.
Charles Kerr (charlesk) wrote : | # |
> We know when the next alarm is, we should wait that amount of time.
That's a great point. Fixed in r271 - r272
> I'm a bit surprised that we're launching the app when the alarm goes off.
What we planned in the meeting was keeping indicator-datetime as much of a pass-through as possible wrt understanding what's getting dispatched. What do you suggest we do here?
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:274
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Nekhelesh Ramananthan (nik90) wrote : | # |
> I'm a bit surprised that we're launching the app when the alarm goes off.
>
> What we planned in the meeting was keeping indicator-datetime as much of a
> pass-through as possible wrt understanding what's getting dispatched. What do
> you suggest we do here?
When an alarm is triggered, the indicator date-time should only create a snap decision as shown in https:/
The clock app is opened only when the user clicks on one of the alarms in the indicator-date-time
Matthew Paul Thomas (mpt) wrote : | # |
> When an alarm is triggered, the indicator date-time should only create a snap decision
I'm surprised that indicator-datetime is doing *anything* with alarms other than listing them. What if I install an alternative calendar app? What if I install a sleep app with an alarm that goes off within a half-hour window, such that indicator-datetime can't display its exact time in advance? What if we decide to redesign the status bar such that indicator-datetime is no longer used? None of that should affect whether alarms go off. That should be up to the app, not a menu that happens to (but doesn't inevitably) display upcoming alarms.
> as shown in https:/
We don't know your Gmail password. ;-)
Ted Gould (ted) wrote : | # |
On Mon, 2013-10-14 at 09:28 +0000, Matthew Paul Thomas wrote:
> > When an alarm is triggered, the indicator date-time should only
> create a snap decision
>
>
>
> I'm surprised that indicator-datetime is doing *anything* with alarms
> other than listing them. What if I install an alternative calendar
> app? What if I install a sleep app with an alarm that goes off within
> a half-hour window, such that indicator-datetime can't display its
> exact time in advance? What if we decide to redesign the status bar
> such that indicator-datetime is no longer used? None of that should
> affect whether alarms go off. That should be up to the app, not a menu
> that happens to (but doesn't inevitably) display upcoming alarms.
Apps are not allowed to run in the background, and we don't want them
to. What we're allowing is them to add events to a shared calendar, and
then having a service that looks at that shared calendar and prompts the
user if they want to act at that time. That service happens to be
indicator-datetime today as it already has to have that information to
display it in the indicator. There is no technical reason it has to be
indicator-datetime in the future, just a convenience for implementation
today. If the user does interact with the snap decision or click on the
item in the indicator at that time we are invoking the app to do what it
wishes.
Nekhelesh Ramananthan (nik90) wrote : | # |
@mpt, the indicator date-time is the only one monitoring the EDS service. Any application that creates an alarm will save it to the EDS backend which the date-time indicator will then pick up. In the Ubuntu Touch, the clock app and the calendar are the 2 apps which do this at the moment. So it doesn't matter if the indicator date-time design is changed, it will still continue to monitor the EDS and trigger the snap decision when appropriate.
> > When an alarm is triggered, the indicator date-time should only create a
> snap decision
>
> I'm surprised that indicator-datetime is doing *anything* with alarms other
> than listing them. What if I install an alternative calendar app? What if I
> install a sleep app with an alarm that goes off within a half-hour window,
> such that indicator-datetime can't display its exact time in advance? What if
> we decide to redesign the status bar such that indicator-datetime is no longer
> used? None of that should affect whether alarms go off. That should be up to
> the app, not a menu that happens to (but doesn't inevitably) display upcoming
> alarms.
>
Ooops sry, didnt realise that..The mockup given to use by the designers can be seen at http://
> > as shown in https:/
> We don't know your Gmail password. ;-)
Nekhelesh Ramananthan (nik90) wrote : | # |
Charles can we get this in asap before the freeze. This is critical to alarm functionality.
Charles Kerr (charlesk) wrote : | # |
So after several iterations:
1. Single-action snap decisions apparently aren't supported by the Unity notification server, so they never appear.
2. Zero-action notifications do show up; however, the expiration period isn't supported and so it unconditionally vanishes after a few seconds whether the user clicks it or not.
So, for this iteration I've gone with a two-button popup, "OK" and "Cancel", where Cancel is a no-op and OK dispatches the appointment's URL.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:287
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 288. By Charles Kerr
-
change ok/cancel buttons to the slightly-
more-informativ e show/dismiss - 289. By Charles Kerr
-
in the alarm snap decision, add x-canonical-
private- button- tint hint to highlight the 'Show' button
Ted Gould (ted) : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:289
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Preview Diff
1 | === modified file 'README' |
2 | --- README 2013-07-26 00:04:47 +0000 |
3 | +++ README 2013-10-17 02:42:11 +0000 |
4 | @@ -7,11 +7,17 @@ |
5 | Parameter: None |
6 | |
7 | * "activate-planner" |
8 | - Description: opens up a calendar appointment editor. |
9 | + Description: opens an appointment editor. |
10 | State: None |
11 | Parameter: int64, a time_t hinting which day/time to show in the planner, |
12 | or 0 for the current day |
13 | |
14 | + * "activate-appointment" |
15 | + Description: opens an appointment editor to the specified appointment. |
16 | + State: None |
17 | + Parameter: string, an opaque uid to specify which appointment to use. |
18 | + This uid comes from the menuitems' target values. |
19 | + |
20 | * "set-location" |
21 | Description: Set the current location. This will try to set the current |
22 | timezone to the new location's timezone. |
23 | |
24 | === modified file 'configure.ac' |
25 | --- configure.ac 2013-10-15 19:14:41 +0000 |
26 | +++ configure.ac 2013-10-17 02:42:11 +0000 |
27 | @@ -51,6 +51,7 @@ |
28 | ICAL_REQUIRED_VERSION=0.48 |
29 | ECAL_REQUIRED_VERSION=3.5 |
30 | EDS_REQUIRED_VERSION=3.5 |
31 | +LIBNOTIFY_REQUIRED_VERSION=0.7.6 |
32 | URL_DISPATCHER_1_REQUIRED_VERSION=1 |
33 | JSON_GLIB_REQUIRED_VERSION=0.16.2 |
34 | |
35 | @@ -62,6 +63,7 @@ |
36 | libical >= $ICAL_REQUIRED_VERSION |
37 | libecal-1.2 >= $ECAL_REQUIRED_VERSION |
38 | libedataserver-1.2 >= $EDS_REQUIRED_VERSION |
39 | + libnotify >= $LIBNOTIFY_REQUIRED_VERSION |
40 | url-dispatcher-1 >= $URL_DISPATCHER_1_REQUIRED_VERSION |
41 | json-glib-1.0 >= $JSON_GLIB_REQUIRED_VERSION]) |
42 | |
43 | |
44 | === modified file 'debian/control' |
45 | --- debian/control 2013-10-16 15:01:11 +0000 |
46 | +++ debian/control 2013-10-17 02:42:11 +0000 |
47 | @@ -10,6 +10,7 @@ |
48 | libxorg-gtest-dev, |
49 | libgtest-dev, |
50 | libglib2.0-dev (>= 2.35.4), |
51 | + libnotify-dev (>= 0.7.6), |
52 | libido3-0.1-dev (>= 0.2.90), |
53 | libgeoclue-dev (>= 0.12.0), |
54 | libecal1.2-dev (>= 3.5), |
55 | |
56 | === modified file 'src/Makefile.am' |
57 | --- src/Makefile.am 2013-09-07 14:48:46 +0000 |
58 | +++ src/Makefile.am 2013-10-17 02:42:11 +0000 |
59 | @@ -20,6 +20,8 @@ |
60 | libindicator_datetime_service_a_SOURCES = \ |
61 | planner.c \ |
62 | planner.h \ |
63 | + planner-mock.c \ |
64 | + planner-mock.h \ |
65 | planner-eds.c \ |
66 | planner-eds.h \ |
67 | service.c \ |
68 | |
69 | === modified file 'src/main.c' |
70 | --- src/main.c 2013-06-20 18:44:21 +0000 |
71 | +++ src/main.c 2013-10-17 02:42:11 +0000 |
72 | @@ -24,40 +24,81 @@ |
73 | |
74 | #include <glib/gi18n.h> |
75 | #include <gio/gio.h> |
76 | +#include <libnotify/notify.h> |
77 | |
78 | +#include "planner-eds.h" |
79 | +#include "planner-mock.h" |
80 | #include "service.h" |
81 | |
82 | /*** |
83 | **** |
84 | ***/ |
85 | |
86 | +/* When enabled, new alarms will show up every minute to test snap decisions */ |
87 | +static gboolean test_alarms = FALSE; |
88 | + |
89 | +static GOptionEntry entries[] = { |
90 | + { "test-alarms", '\0', 0, G_OPTION_ARG_NONE, &test_alarms, "Test Alarms", NULL }, |
91 | + { NULL } |
92 | +}; |
93 | + |
94 | static void |
95 | on_name_lost (gpointer instance G_GNUC_UNUSED, gpointer loop) |
96 | { |
97 | g_message ("exiting: service couldn't acquire or lost ownership of busname"); |
98 | - g_main_loop_quit ((GMainLoop*)loop); |
99 | + |
100 | + if (!test_alarms) |
101 | + g_main_loop_quit ((GMainLoop*)loop); |
102 | } |
103 | |
104 | int |
105 | main (int argc G_GNUC_UNUSED, char ** argv G_GNUC_UNUSED) |
106 | { |
107 | + GOptionContext * context; |
108 | + GError * error; |
109 | + IndicatorDatetimePlanner * planner; |
110 | + IndicatorDatetimeService * service; |
111 | GMainLoop * loop; |
112 | - IndicatorDatetimeService * service; |
113 | |
114 | /* boilerplate i18n */ |
115 | setlocale (LC_ALL, ""); |
116 | bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); |
117 | textdomain (GETTEXT_PACKAGE); |
118 | |
119 | + /* init libnotify */ |
120 | + if (!notify_init ("indicator-datetime-service")) |
121 | + g_critical ("libnotify initialization failed"); |
122 | + |
123 | + /* parse command-line options */ |
124 | + context = g_option_context_new (NULL); |
125 | + g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE); |
126 | + if (!g_option_context_parse (context, &argc, &argv, &error)) |
127 | + { |
128 | + g_print("option parsing failed: %s\n", error->message); |
129 | + return EXIT_FAILURE; |
130 | + } |
131 | + |
132 | + /* set up the planner */ |
133 | + if (test_alarms) |
134 | + { |
135 | + g_message ("Using fake appointment book for testing alarms."); |
136 | + planner = indicator_datetime_planner_mock_new (); |
137 | + } |
138 | + else |
139 | + { |
140 | + planner = indicator_datetime_planner_eds_new (); |
141 | + } |
142 | + |
143 | /* run */ |
144 | - service = indicator_datetime_service_new (); |
145 | + service = indicator_datetime_service_new (planner); |
146 | loop = g_main_loop_new (NULL, FALSE); |
147 | g_signal_connect (service, INDICATOR_DATETIME_SERVICE_SIGNAL_NAME_LOST, |
148 | G_CALLBACK(on_name_lost), loop); |
149 | g_main_loop_run (loop); |
150 | |
151 | /* cleanup */ |
152 | - g_clear_object (&service); |
153 | g_main_loop_unref (loop); |
154 | + g_object_unref (service); |
155 | + g_object_unref (planner); |
156 | return 0; |
157 | } |
158 | |
159 | === modified file 'src/planner-eds.c' |
160 | --- src/planner-eds.c 2013-09-05 21:27:35 +0000 |
161 | +++ src/planner-eds.c 2013-10-17 02:42:11 +0000 |
162 | @@ -43,78 +43,180 @@ |
163 | |
164 | /*** |
165 | **** |
166 | -***/ |
167 | - |
168 | -void |
169 | -indicator_datetime_appt_free (struct IndicatorDatetimeAppt * appt) |
170 | -{ |
171 | - if (appt != NULL) |
172 | - { |
173 | - g_date_time_unref (appt->end); |
174 | - g_date_time_unref (appt->begin); |
175 | - g_free (appt->color); |
176 | - g_free (appt->summary); |
177 | - g_slice_free (struct IndicatorDatetimeAppt, appt); |
178 | - } |
179 | -} |
180 | - |
181 | -/*** |
182 | -**** my_get_appointments() helpers |
183 | -***/ |
184 | - |
185 | -struct get_appointments_task_data |
186 | -{ |
187 | +**** my_get_appointments() helpers |
188 | +**** |
189 | +***/ |
190 | + |
191 | +/* whole-task data that all the subtasks can see */ |
192 | +struct appointment_task_data |
193 | +{ |
194 | + /* a ref to the planner's cancellable */ |
195 | + GCancellable * cancellable; |
196 | + |
197 | /* how many subtasks are still running on */ |
198 | int subtask_count; |
199 | |
200 | /* the list of appointments to be returned */ |
201 | GSList * appointments; |
202 | - |
203 | - /* ensure that recurring events don't get multiple IndicatorDatetimeAppts */ |
204 | - GHashTable * added; |
205 | }; |
206 | |
207 | -static void |
208 | -get_appointments_task_data_free (gpointer gdata) |
209 | -{ |
210 | - struct get_appointments_task_data * data = gdata; |
211 | - g_hash_table_unref (data->added); |
212 | - g_slice_free (struct get_appointments_task_data, data); |
213 | -} |
214 | - |
215 | -static void |
216 | -on_all_subtasks_done (GTask * task) |
217 | -{ |
218 | - struct get_appointments_task_data * data = g_task_get_task_data (task); |
219 | +static struct appointment_task_data * |
220 | +appointment_task_data_new (GCancellable * cancellable) |
221 | +{ |
222 | + struct appointment_task_data * data; |
223 | + |
224 | + data = g_slice_new0 (struct appointment_task_data); |
225 | + data->cancellable = g_object_ref (cancellable); |
226 | + return data; |
227 | +} |
228 | + |
229 | +static void |
230 | +appointment_task_data_free (gpointer gdata) |
231 | +{ |
232 | + struct appointment_task_data * data = gdata; |
233 | + |
234 | + g_object_unref (data->cancellable); |
235 | + |
236 | + g_slice_free (struct appointment_task_data, data); |
237 | +} |
238 | + |
239 | +static void |
240 | +appointment_task_done (GTask * task) |
241 | +{ |
242 | + struct appointment_task_data * data = g_task_get_task_data (task); |
243 | + |
244 | g_task_return_pointer (task, data->appointments, NULL); |
245 | g_object_unref (task); |
246 | } |
247 | |
248 | -struct get_appointments_subtask_data |
249 | -{ |
250 | - GTask * task; |
251 | - |
252 | +static void |
253 | +appointment_task_decrement_subtasks (GTask * task) |
254 | +{ |
255 | + struct appointment_task_data * data = g_task_get_task_data (task); |
256 | + |
257 | + if (g_atomic_int_dec_and_test (&data->subtask_count)) |
258 | + appointment_task_done (task); |
259 | +} |
260 | + |
261 | +static void |
262 | +appointment_task_increment_subtasks (GTask * task) |
263 | +{ |
264 | + struct appointment_task_data * data = g_task_get_task_data (task); |
265 | + |
266 | + g_atomic_int_inc (&data->subtask_count); |
267 | +} |
268 | + |
269 | +/** |
270 | +*** get-the-appointment's-uri subtasks |
271 | +**/ |
272 | + |
273 | +struct appointment_uri_subtask_data |
274 | +{ |
275 | + /* The parent task */ |
276 | + GTask * task; |
277 | + |
278 | + /* The appointment whose uri we're looking for. |
279 | + This pointer is owned by the Task and isn't reffed/unreffed by the subtask */ |
280 | + struct IndicatorDatetimeAppt * appt; |
281 | +}; |
282 | + |
283 | +static void |
284 | +appointment_uri_subtask_done (struct appointment_uri_subtask_data * subdata) |
285 | +{ |
286 | + GTask * task = subdata->task; |
287 | + |
288 | + /* free the subtask data */ |
289 | + g_slice_free (struct appointment_uri_subtask_data, subdata); |
290 | + |
291 | + appointment_task_decrement_subtasks (task); |
292 | +} |
293 | + |
294 | +static struct appointment_uri_subtask_data * |
295 | +appointment_uri_subtask_data_new (GTask * task, struct IndicatorDatetimeAppt * appt) |
296 | +{ |
297 | + struct appointment_uri_subtask_data * subdata; |
298 | + |
299 | + appointment_task_increment_subtasks (task); |
300 | + |
301 | + subdata = g_slice_new0 (struct appointment_uri_subtask_data); |
302 | + subdata->task = task; |
303 | + subdata->appt = appt; |
304 | + return subdata; |
305 | +} |
306 | + |
307 | +static void |
308 | +on_appointment_uris_ready (GObject * client, |
309 | + GAsyncResult * res, |
310 | + gpointer gsubdata) |
311 | +{ |
312 | + GSList * uris; |
313 | + GError * error; |
314 | + struct appointment_uri_subtask_data * subdata = gsubdata; |
315 | + |
316 | + uris = NULL; |
317 | + error = NULL; |
318 | + e_cal_client_get_attachment_uris_finish (E_CAL_CLIENT(client), res, &uris, &error); |
319 | + if (error != NULL) |
320 | + { |
321 | + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) |
322 | + g_warning ("Error getting appointment uris: %s", error->message); |
323 | + |
324 | + g_error_free (error); |
325 | + } |
326 | + else if (uris != NULL) |
327 | + { |
328 | + struct IndicatorDatetimeAppt * appt = subdata->appt; |
329 | + appt->url = g_strdup (uris->data); /* copy the first URL */ |
330 | + g_debug ("found url '%s' for appointment '%s'", appt->url, appt->summary); |
331 | + e_client_util_free_string_slist (uris); |
332 | + } |
333 | + |
334 | + appointment_uri_subtask_done (subdata); |
335 | +} |
336 | + |
337 | +/** |
338 | +*** enumerate-the-components subtasks |
339 | +**/ |
340 | + |
341 | +/* data struct for the enumerate-components subtask */ |
342 | +struct appointment_component_subtask_data |
343 | +{ |
344 | + /* The parent task */ |
345 | + GTask * task; |
346 | + |
347 | + /* The client we're walking through. The subtask owns a ref to this */ |
348 | + ECalClient * client; |
349 | + |
350 | + /* The appointment's color coding. The subtask owns this string */ |
351 | gchar * color; |
352 | }; |
353 | |
354 | static void |
355 | -on_subtask_done (gpointer gsubdata) |
356 | +on_appointment_component_subtask_done (gpointer gsubdata) |
357 | { |
358 | - struct get_appointments_subtask_data * subdata; |
359 | - GTask * task; |
360 | - struct get_appointments_task_data * data; |
361 | - |
362 | - subdata = gsubdata; |
363 | - task = subdata->task; |
364 | + struct appointment_component_subtask_data * subdata = gsubdata; |
365 | + GTask * task = subdata->task; |
366 | |
367 | /* free the subtask data */ |
368 | g_free (subdata->color); |
369 | - g_slice_free (struct get_appointments_subtask_data, subdata); |
370 | - |
371 | - /* poke the task */ |
372 | - data = g_task_get_task_data (task); |
373 | - if (g_atomic_int_dec_and_test (&data->subtask_count)) |
374 | - on_all_subtasks_done (task); |
375 | + g_object_unref (subdata->client); |
376 | + g_slice_free (struct appointment_component_subtask_data, subdata); |
377 | + |
378 | + appointment_task_decrement_subtasks (task); |
379 | +} |
380 | + |
381 | +static struct appointment_component_subtask_data * |
382 | +appointment_component_subtask_data_new (GTask * task, ECalClient * client, const gchar * color) |
383 | +{ |
384 | + struct appointment_component_subtask_data * subdata; |
385 | + |
386 | + appointment_task_increment_subtasks (task); |
387 | + |
388 | + subdata = g_slice_new0 (struct appointment_component_subtask_data); |
389 | + subdata->task = task; |
390 | + subdata->client = g_object_ref (client); |
391 | + subdata->color = g_strdup (color); |
392 | + return subdata; |
393 | } |
394 | |
395 | static gboolean |
396 | @@ -124,8 +226,8 @@ |
397 | gpointer gsubdata) |
398 | { |
399 | const ECalComponentVType vtype = e_cal_component_get_vtype (component); |
400 | - struct get_appointments_subtask_data * subdata = gsubdata; |
401 | - struct get_appointments_task_data * data = g_task_get_task_data (subdata->task); |
402 | + struct appointment_component_subtask_data * subdata = gsubdata; |
403 | + struct appointment_task_data * data = g_task_get_task_data (subdata->task); |
404 | |
405 | if ((vtype == E_CAL_COMPONENT_EVENT) || (vtype == E_CAL_COMPONENT_TODO)) |
406 | { |
407 | @@ -136,7 +238,6 @@ |
408 | e_cal_component_get_status (component, &status); |
409 | |
410 | if ((uid != NULL) && |
411 | - (!g_hash_table_contains (data->added, uid)) && |
412 | (status != ICAL_STATUS_COMPLETED) && |
413 | (status != ICAL_STATUS_CANCELLED)) |
414 | { |
415 | @@ -145,6 +246,7 @@ |
416 | GSList * recur_list; |
417 | ECalComponentText text; |
418 | struct IndicatorDatetimeAppt * appt; |
419 | + struct appointment_uri_subtask_data * uri_subdata; |
420 | |
421 | appt = g_slice_new0 (struct IndicatorDatetimeAppt); |
422 | |
423 | @@ -169,13 +271,22 @@ |
424 | appt->color = g_strdup (subdata->color); |
425 | appt->is_event = vtype == E_CAL_COMPONENT_EVENT; |
426 | appt->summary = g_strdup (text.value); |
427 | + appt->uid = g_strdup (uid); |
428 | |
429 | alarm_uids = e_cal_component_get_alarm_uids (component); |
430 | appt->has_alarms = alarm_uids != NULL; |
431 | cal_obj_uid_list_free (alarm_uids); |
432 | |
433 | data->appointments = g_slist_prepend (data->appointments, appt); |
434 | - g_hash_table_add (data->added, g_strdup(uid)); |
435 | + |
436 | + /* start a new subtask to get the associated URIs */ |
437 | + uri_subdata = appointment_uri_subtask_data_new (subdata->task, appt); |
438 | + e_cal_client_get_attachment_uris (subdata->client, |
439 | + uid, |
440 | + NULL, |
441 | + data->cancellable, |
442 | + on_appointment_uris_ready, |
443 | + uri_subdata); |
444 | } |
445 | } |
446 | |
447 | @@ -197,7 +308,6 @@ |
448 | priv_t * p; |
449 | const char * str; |
450 | icaltimezone * default_timezone; |
451 | - struct get_appointments_task_data * data; |
452 | const int64_t begin = g_date_time_to_unix (begin_datetime); |
453 | const int64_t end = g_date_time_to_unix (end_datetime); |
454 | GTask * task; |
455 | @@ -223,17 +333,18 @@ |
456 | *** walk through the sources to build the appointment list |
457 | **/ |
458 | |
459 | - data = g_slice_new0 (struct get_appointments_task_data); |
460 | - data->added = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); |
461 | task = g_task_new (planner, p->cancellable, callback, user_data); |
462 | - g_task_set_task_data (task, data, get_appointments_task_data_free); |
463 | + g_task_set_task_data (task, |
464 | + appointment_task_data_new (p->cancellable), |
465 | + appointment_task_data_free); |
466 | |
467 | subtasks_added = FALSE; |
468 | for (l=p->sources; l!=NULL; l=l->next) |
469 | { |
470 | ESource * source; |
471 | ECalClient * client; |
472 | - struct get_appointments_subtask_data * subdata; |
473 | + const char * color; |
474 | + struct appointment_component_subtask_data * subdata; |
475 | |
476 | source = l->data; |
477 | client = g_object_get_qdata (l->data, source_client_quark()); |
478 | @@ -243,11 +354,9 @@ |
479 | if (default_timezone != NULL) |
480 | e_cal_client_set_default_timezone (client, default_timezone); |
481 | |
482 | - subdata = g_slice_new (struct get_appointments_subtask_data); |
483 | - subdata->task = task; |
484 | - subdata->color = e_source_selectable_dup_color (e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR)); |
485 | - |
486 | - g_atomic_int_inc (&data->subtask_count); |
487 | + /* start a new subtask to enumerate all the components in this client. */ |
488 | + color = e_source_selectable_get_color (e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR)); |
489 | + subdata = appointment_component_subtask_data_new (task, client, color); |
490 | subtasks_added = TRUE; |
491 | e_cal_client_generate_instances (client, |
492 | begin, |
493 | @@ -255,11 +364,11 @@ |
494 | p->cancellable, |
495 | my_get_appointments_foreach, |
496 | subdata, |
497 | - on_subtask_done); |
498 | + on_appointment_component_subtask_done); |
499 | } |
500 | |
501 | if (!subtasks_added) |
502 | - on_all_subtasks_done (task); |
503 | + appointment_task_done (task); |
504 | } |
505 | |
506 | static GSList * |
507 | @@ -270,7 +379,7 @@ |
508 | return g_task_propagate_pointer (G_TASK(res), error); |
509 | } |
510 | |
511 | -gboolean |
512 | +static gboolean |
513 | my_is_configured (IndicatorDatetimePlanner * planner) |
514 | { |
515 | IndicatorDatetimePlannerEds * self; |
516 | |
517 | === modified file 'src/planner-eds.h' |
518 | --- src/planner-eds.h 2013-05-13 02:08:37 +0000 |
519 | +++ src/planner-eds.h 2013-10-17 02:42:11 +0000 |
520 | @@ -51,8 +51,6 @@ |
521 | IndicatorDatetimePlannerClass parent_class; |
522 | }; |
523 | |
524 | -gboolean indicator_datetime_planner_eds_is_usable (void); |
525 | - |
526 | IndicatorDatetimePlanner * indicator_datetime_planner_eds_new (void); |
527 | |
528 | G_END_DECLS |
529 | |
530 | === added file 'src/planner-mock.c' |
531 | --- src/planner-mock.c 1970-01-01 00:00:00 +0000 |
532 | +++ src/planner-mock.c 2013-10-17 02:42:11 +0000 |
533 | @@ -0,0 +1,178 @@ |
534 | +/* |
535 | + * Copyright 2013 Canonical Ltd. |
536 | + * |
537 | + * Authors: |
538 | + * Charles Kerr <charles.kerr@canonical.com> |
539 | + * |
540 | + * This program is free software: you can redistribute it and/or modify it |
541 | + * under the terms of the GNU General Public License version 3, as published |
542 | + * by the Free Software Foundation. |
543 | + * |
544 | + * This program is distributed in the hope that it will be useful, but |
545 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
546 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
547 | + * PURPOSE. See the GNU General Public License for more details. |
548 | + * |
549 | + * You should have received a copy of the GNU General Public License along |
550 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
551 | + */ |
552 | + |
553 | +#include "config.h" |
554 | + |
555 | +#include "planner-mock.h" |
556 | + |
557 | +struct _IndicatorDatetimePlannerMockPriv |
558 | +{ |
559 | + gboolean is_configured; |
560 | +}; |
561 | + |
562 | +typedef IndicatorDatetimePlannerMockPriv priv_t; |
563 | + |
564 | +G_DEFINE_TYPE (IndicatorDatetimePlannerMock, |
565 | + indicator_datetime_planner_mock, |
566 | + INDICATOR_TYPE_DATETIME_PLANNER) |
567 | + |
568 | +/*** |
569 | +**** IndicatorDatetimePlanner virtual funcs |
570 | +***/ |
571 | + |
572 | +static void |
573 | +my_get_appointments (IndicatorDatetimePlanner * planner, |
574 | + GDateTime * begin_datetime, |
575 | + GDateTime * end_datetime G_GNUC_UNUSED, |
576 | + GAsyncReadyCallback callback, |
577 | + gpointer user_data) |
578 | +{ |
579 | + GTask * task; |
580 | + GSList * appointments; |
581 | + struct IndicatorDatetimeAppt * appt; |
582 | + struct IndicatorDatetimeAppt * prev; |
583 | + |
584 | + task = g_task_new (planner, NULL, callback, user_data); |
585 | + |
586 | + /** |
587 | + *** Build the appointments list |
588 | + **/ |
589 | + |
590 | + appointments = NULL; |
591 | + |
592 | + /* add a daily appointment that occurs at the beginning of the next minute */ |
593 | + appt = g_slice_new0 (struct IndicatorDatetimeAppt); |
594 | + appt->is_daily = TRUE; |
595 | + appt->begin = g_date_time_add_seconds (begin_datetime, 60-g_date_time_get_seconds(begin_datetime)); |
596 | + appt->end = g_date_time_add_minutes (appt->begin, 1); |
597 | + appt->color = g_strdup ("#00FF00"); |
598 | + appt->is_event = TRUE; |
599 | + appt->summary = g_strdup ("Daily alarm"); |
600 | + appt->uid = g_strdup ("this uid isn't very random."); |
601 | + appt->has_alarms = TRUE; |
602 | + appt->url = g_strdup ("alarm:///some-alarm-info-goes-here"); |
603 | + appointments = g_slist_prepend (appointments, appt); |
604 | + prev = appt; |
605 | + |
606 | + /* and add one for a minute later that has an alarm uri */ |
607 | + appt = g_slice_new0 (struct IndicatorDatetimeAppt); |
608 | + appt->is_daily = TRUE; |
609 | + appt->begin = g_date_time_add_minutes (prev->end, 1); |
610 | + appt->end = g_date_time_add_minutes (appt->begin, 1); |
611 | + appt->color = g_strdup ("#0000FF"); |
612 | + appt->is_event = TRUE; |
613 | + appt->summary = g_strdup ("Second Daily alarm"); |
614 | + appt->uid = g_strdup ("this uid isn't very random either."); |
615 | + appt->has_alarms = FALSE; |
616 | + appointments = g_slist_prepend (appointments, appt); |
617 | + |
618 | + /* done */ |
619 | + g_task_return_pointer (task, appointments, NULL); |
620 | + g_object_unref (task); |
621 | +} |
622 | + |
623 | +static GSList * |
624 | +my_get_appointments_finish (IndicatorDatetimePlanner * self G_GNUC_UNUSED, |
625 | + GAsyncResult * res, |
626 | + GError ** error) |
627 | +{ |
628 | + return g_task_propagate_pointer (G_TASK(res), error); |
629 | +} |
630 | + |
631 | +static gboolean |
632 | +my_is_configured (IndicatorDatetimePlanner * planner) |
633 | +{ |
634 | + IndicatorDatetimePlannerMock * self; |
635 | + self = INDICATOR_DATETIME_PLANNER_MOCK (planner); |
636 | + return self->priv->is_configured; |
637 | +} |
638 | + |
639 | +static void |
640 | +my_activate (IndicatorDatetimePlanner * self G_GNUC_UNUSED) |
641 | +{ |
642 | + g_message ("%s %s", G_STRLOC, G_STRFUNC); |
643 | +} |
644 | + |
645 | +static void |
646 | +my_activate_time (IndicatorDatetimePlanner * self G_GNUC_UNUSED, |
647 | + GDateTime * activate_time) |
648 | +{ |
649 | + gchar * str = g_date_time_format (activate_time, "%F %T"); |
650 | + g_message ("%s %s: %s", G_STRLOC, G_STRFUNC, str); |
651 | + g_free (str); |
652 | +} |
653 | + |
654 | +/*** |
655 | +**** GObject virtual funcs |
656 | +***/ |
657 | + |
658 | +static void |
659 | +my_dispose (GObject * o) |
660 | +{ |
661 | + G_OBJECT_CLASS (indicator_datetime_planner_mock_parent_class)->dispose (o); |
662 | +} |
663 | + |
664 | +/*** |
665 | +**** Instantiation |
666 | +***/ |
667 | + |
668 | +static void |
669 | +indicator_datetime_planner_mock_class_init (IndicatorDatetimePlannerMockClass * klass) |
670 | +{ |
671 | + GObjectClass * object_class; |
672 | + IndicatorDatetimePlannerClass * planner_class; |
673 | + |
674 | + object_class = G_OBJECT_CLASS (klass); |
675 | + object_class->dispose = my_dispose; |
676 | + |
677 | + planner_class = INDICATOR_DATETIME_PLANNER_CLASS (klass); |
678 | + planner_class->is_configured = my_is_configured; |
679 | + planner_class->activate = my_activate; |
680 | + planner_class->activate_time = my_activate_time; |
681 | + planner_class->get_appointments = my_get_appointments; |
682 | + planner_class->get_appointments_finish = my_get_appointments_finish; |
683 | + |
684 | + g_type_class_add_private (klass, sizeof (IndicatorDatetimePlannerMockPriv)); |
685 | +} |
686 | + |
687 | +static void |
688 | +indicator_datetime_planner_mock_init (IndicatorDatetimePlannerMock * self) |
689 | +{ |
690 | + priv_t * p; |
691 | + |
692 | + p = G_TYPE_INSTANCE_GET_PRIVATE (self, |
693 | + INDICATOR_TYPE_DATETIME_PLANNER_MOCK, |
694 | + IndicatorDatetimePlannerMockPriv); |
695 | + |
696 | + p->is_configured = TRUE; |
697 | + |
698 | + self->priv = p; |
699 | +} |
700 | + |
701 | +/*** |
702 | +**** Public |
703 | +***/ |
704 | + |
705 | +IndicatorDatetimePlanner * |
706 | +indicator_datetime_planner_mock_new (void) |
707 | +{ |
708 | + gpointer o = g_object_new (INDICATOR_TYPE_DATETIME_PLANNER_MOCK, NULL); |
709 | + |
710 | + return INDICATOR_DATETIME_PLANNER (o); |
711 | +} |
712 | |
713 | === added file 'src/planner-mock.h' |
714 | --- src/planner-mock.h 1970-01-01 00:00:00 +0000 |
715 | +++ src/planner-mock.h 2013-10-17 02:42:11 +0000 |
716 | @@ -0,0 +1,58 @@ |
717 | +/* |
718 | + * Copyright 2013 Canonical Ltd. |
719 | + * |
720 | + * Authors: |
721 | + * Charles Kerr <charles.kerr@canonical.com> |
722 | + * |
723 | + * This program is free software: you can redistribute it and/or modify it |
724 | + * under the terms of the GNU General Public License version 3, as published |
725 | + * by the Free Software Foundation. |
726 | + * |
727 | + * This program is distributed in the hope that it will be useful, but |
728 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
729 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
730 | + * PURPOSE. See the GNU General Public License for more details. |
731 | + * |
732 | + * You should have received a copy of the GNU General Public License along |
733 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
734 | + */ |
735 | + |
736 | +#ifndef __INDICATOR_DATETIME_PLANNER_MOCK__H__ |
737 | +#define __INDICATOR_DATETIME_PLANNER_MOCK__H__ |
738 | + |
739 | +#include "planner.h" /* parent class */ |
740 | + |
741 | +G_BEGIN_DECLS |
742 | + |
743 | +#define INDICATOR_TYPE_DATETIME_PLANNER_MOCK (indicator_datetime_planner_mock_get_type()) |
744 | +#define INDICATOR_DATETIME_PLANNER_MOCK(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), INDICATOR_TYPE_DATETIME_PLANNER_MOCK, IndicatorDatetimePlannerMock)) |
745 | +#define INDICATOR_DATETIME_PLANNER_MOCK_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), INDICATOR_TYPE_DATETIME_PLANNER_MOCK, IndicatorDatetimePlannerMockClass)) |
746 | +#define INDICATOR_IS_DATETIME_PLANNER_MOCK(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), INDICATOR_TYPE_DATETIME_PLANNER_MOCK)) |
747 | + |
748 | +typedef struct _IndicatorDatetimePlannerMock IndicatorDatetimePlannerMock; |
749 | +typedef struct _IndicatorDatetimePlannerMockPriv IndicatorDatetimePlannerMockPriv; |
750 | +typedef struct _IndicatorDatetimePlannerMockClass IndicatorDatetimePlannerMockClass; |
751 | + |
752 | +GType indicator_datetime_planner_mock_get_type (void); |
753 | + |
754 | +/** |
755 | + * An IndicatorDatetimePlanner which uses Evolution Data Server |
756 | + * to get its list of appointments. |
757 | + */ |
758 | +struct _IndicatorDatetimePlannerMock |
759 | +{ |
760 | + /*< private >*/ |
761 | + IndicatorDatetimePlanner parent; |
762 | + IndicatorDatetimePlannerMockPriv * priv; |
763 | +}; |
764 | + |
765 | +struct _IndicatorDatetimePlannerMockClass |
766 | +{ |
767 | + IndicatorDatetimePlannerClass parent_class; |
768 | +}; |
769 | + |
770 | +IndicatorDatetimePlanner * indicator_datetime_planner_mock_new (void); |
771 | + |
772 | +G_END_DECLS |
773 | + |
774 | +#endif /* __INDICATOR_DATETIME_PLANNER_MOCK__H__ */ |
775 | |
776 | === modified file 'src/planner.c' |
777 | --- src/planner.c 2013-09-08 18:17:08 +0000 |
778 | +++ src/planner.c 2013-10-17 02:42:11 +0000 |
779 | @@ -259,3 +259,23 @@ |
780 | |
781 | return self->priv->timezone; |
782 | } |
783 | + |
784 | +/*** |
785 | +**** |
786 | +***/ |
787 | + |
788 | +void |
789 | +indicator_datetime_appt_free (struct IndicatorDatetimeAppt * appt) |
790 | +{ |
791 | + if (appt != NULL) |
792 | + { |
793 | + g_date_time_unref (appt->end); |
794 | + g_date_time_unref (appt->begin); |
795 | + g_free (appt->color); |
796 | + g_free (appt->summary); |
797 | + g_free (appt->url); |
798 | + g_free (appt->uid); |
799 | + g_slice_free (struct IndicatorDatetimeAppt, appt); |
800 | + } |
801 | +} |
802 | + |
803 | |
804 | === modified file 'src/planner.h' |
805 | --- src/planner.h 2013-09-05 16:57:05 +0000 |
806 | +++ src/planner.h 2013-10-17 02:42:11 +0000 |
807 | @@ -40,8 +40,10 @@ |
808 | |
809 | struct IndicatorDatetimeAppt |
810 | { |
811 | - char * color; |
812 | - char * summary; |
813 | + gchar * color; |
814 | + gchar * summary; |
815 | + gchar * url; |
816 | + gchar * uid; |
817 | GDateTime * begin; |
818 | GDateTime * end; |
819 | gboolean is_event; |
820 | |
821 | === modified file 'src/service.c' |
822 | --- src/service.c 2013-10-16 14:50:25 +0000 |
823 | +++ src/service.c 2013-10-17 02:42:11 +0000 |
824 | @@ -24,11 +24,11 @@ |
825 | |
826 | #include <glib/gi18n.h> |
827 | #include <gio/gio.h> |
828 | +#include <libnotify/notify.h> |
829 | #include <json-glib/json-glib.h> |
830 | #include <url-dispatcher.h> |
831 | |
832 | #include "dbus-shared.h" |
833 | -#include "planner-eds.h" |
834 | #include "timezone-file.h" |
835 | #include "timezone-geoclue.h" |
836 | #include "service.h" |
837 | @@ -53,6 +53,15 @@ |
838 | |
839 | enum |
840 | { |
841 | + PROP_0, |
842 | + PROP_PLANNER, |
843 | + PROP_LAST |
844 | +}; |
845 | + |
846 | +static GParamSpec * properties[PROP_LAST] = { 0 }; |
847 | + |
848 | +enum |
849 | +{ |
850 | SECTION_HEADER = (1<<0), |
851 | SECTION_CALENDAR = (1<<1), |
852 | SECTION_APPOINTMENTS = (1<<2), |
853 | @@ -119,6 +128,7 @@ |
854 | |
855 | guint header_timer; |
856 | guint timezone_timer; |
857 | + guint alarm_timer; |
858 | |
859 | /* Which year/month to show in the calendar, |
860 | and which day should get the cursor. |
861 | @@ -388,6 +398,197 @@ |
862 | g_date_time_unref (now); |
863 | } |
864 | |
865 | +/*** |
866 | +**** ALARMS |
867 | +***/ |
868 | + |
869 | +static void set_alarm_timer (IndicatorDatetimeService * self); |
870 | + |
871 | +static gboolean |
872 | +appointment_has_alarm_url (const struct IndicatorDatetimeAppt * appt) |
873 | +{ |
874 | + return (appt->has_alarms) && |
875 | + (appt->url != NULL) && |
876 | + (g_str_has_prefix (appt->url, "alarm:///")); |
877 | +} |
878 | + |
879 | +static gboolean |
880 | +datetimes_have_the_same_minute (GDateTime * a G_GNUC_UNUSED, GDateTime * b G_GNUC_UNUSED) |
881 | +{ |
882 | + int ay, am, ad; |
883 | + int by, bm, bd; |
884 | + |
885 | + g_date_time_get_ymd (a, &ay, &am, &ad); |
886 | + g_date_time_get_ymd (b, &by, &bm, &bd); |
887 | + |
888 | + return (ay == by) && |
889 | + (am == bm) && |
890 | + (ad == bd) && |
891 | + (g_date_time_get_hour (a) == g_date_time_get_hour (b)) && |
892 | + (g_date_time_get_minute (a) == g_date_time_get_minute (b)); |
893 | +} |
894 | + |
895 | +static void |
896 | +dispatch_alarm_url (const struct IndicatorDatetimeAppt * appt) |
897 | +{ |
898 | + gchar * str; |
899 | + |
900 | + g_return_if_fail (appt != NULL); |
901 | + g_return_if_fail (appointment_has_alarm_url (appt)); |
902 | + |
903 | + str = g_date_time_format (appt->begin, "%F %T"); |
904 | + g_debug ("dispatching url \"%s\" for appointment \"%s\", which begins at %s", |
905 | + appt->url, appt->summary, str); |
906 | + g_free (str); |
907 | + |
908 | + url_dispatch_send (appt->url, NULL, NULL); |
909 | +} |
910 | + |
911 | +static void |
912 | +on_snap_decided (NotifyNotification * notification G_GNUC_UNUSED, |
913 | + char * action, |
914 | + gpointer gurl) |
915 | +{ |
916 | + g_debug ("%s: %s", G_STRFUNC, action); |
917 | + |
918 | + if (!g_strcmp0 (action, "show")) |
919 | + { |
920 | + const gchar * url = gurl; |
921 | + g_debug ("dispatching url '%s'", url); |
922 | + url_dispatch_send (url, NULL, NULL); |
923 | + } |
924 | +} |
925 | + |
926 | +static void |
927 | +show_snap_decision_for_alarm (const struct IndicatorDatetimeAppt * appt) |
928 | +{ |
929 | + gchar * title; |
930 | + const gchar * body; |
931 | + const gchar * icon_name; |
932 | + NotifyNotification * nn; |
933 | + GError * error; |
934 | + |
935 | + title = g_date_time_format (appt->begin, |
936 | + get_terse_time_format_string (appt->begin)); |
937 | + body = appt->summary; |
938 | + icon_name = ALARM_CLOCK_ICON_NAME; |
939 | + g_debug ("creating a snap decision with title '%s', body '%s', icon '%s'", |
940 | + title, body, icon_name); |
941 | + |
942 | + nn = notify_notification_new (title, body, icon_name); |
943 | + notify_notification_set_hint_string (nn, |
944 | + "x-canonical-snap-decisions", |
945 | + "true"); |
946 | + notify_notification_set_hint_string (nn, |
947 | + "x-canonical-private-button-tint", |
948 | + "true"); |
949 | + notify_notification_add_action (nn, "show", _("Show"), |
950 | + on_snap_decided, g_strdup(appt->url), g_free); |
951 | + notify_notification_add_action (nn, "dismiss", _("Dismiss"), |
952 | + on_snap_decided, NULL, NULL); |
953 | + |
954 | + error = NULL; |
955 | + notify_notification_show (nn, &error); |
956 | + if (error != NULL) |
957 | + { |
958 | + g_warning ("Unable to show alarm '%s' popup: %s", body, error->message); |
959 | + g_error_free (error); |
960 | + dispatch_alarm_url (appt); |
961 | + } |
962 | + |
963 | + g_free (title); |
964 | +} |
965 | + |
966 | +static void update_appointment_lists (IndicatorDatetimeService * self); |
967 | + |
968 | +static gboolean |
969 | +on_alarm_timer (gpointer gself) |
970 | +{ |
971 | + IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (gself); |
972 | + GDateTime * now; |
973 | + GSList * l; |
974 | + |
975 | + /* If there are any alarms at the current time, show a snap decision */ |
976 | + now = indicator_datetime_service_get_localtime (self); |
977 | + for (l=self->priv->upcoming_appointments; l!=NULL; l=l->next) |
978 | + { |
979 | + const struct IndicatorDatetimeAppt * appt = l->data; |
980 | + |
981 | + if (appointment_has_alarm_url (appt)) |
982 | + if (datetimes_have_the_same_minute (now, appt->begin)) |
983 | + show_snap_decision_for_alarm (appt); |
984 | + } |
985 | + g_date_time_unref (now); |
986 | + |
987 | + /* rebuild the alarm list asynchronously. |
988 | + set_upcoming_appointments() will update the alarm timer when this |
989 | + async call is done, so no need to restart the timer here... */ |
990 | + update_appointment_lists (self); |
991 | + |
992 | + return G_SOURCE_REMOVE; |
993 | +} |
994 | + |
995 | +/* if there are upcoming alarms, set the alarm timer to the nearest one. |
996 | + otherwise, unset the alarm timer. */ |
997 | +static void |
998 | +set_alarm_timer (IndicatorDatetimeService * self) |
999 | +{ |
1000 | + priv_t * p; |
1001 | + GDateTime * now; |
1002 | + GDateTime * alarm_time; |
1003 | + GSList * l; |
1004 | + |
1005 | + p = self->priv; |
1006 | + indicator_clear_timer (&p->alarm_timer); |
1007 | + |
1008 | + now = indicator_datetime_service_get_localtime (self); |
1009 | + |
1010 | + /* find the time of the next alarm on our calendar */ |
1011 | + alarm_time = NULL; |
1012 | + for (l=p->upcoming_appointments; l!=NULL; l=l->next) |
1013 | + { |
1014 | + const struct IndicatorDatetimeAppt * appt = l->data; |
1015 | + |
1016 | + if (appointment_has_alarm_url (appt)) |
1017 | + if (g_date_time_compare (appt->begin, now) > 0) |
1018 | + if (!alarm_time || g_date_time_compare (alarm_time, appt->begin) > 0) |
1019 | + alarm_time = appt->begin; |
1020 | + } |
1021 | + |
1022 | + /* if there's an upcoming alarm, set a timer to wake up at that time */ |
1023 | + if (alarm_time != NULL) |
1024 | + { |
1025 | + GTimeSpan interval_msec; |
1026 | + gchar * str; |
1027 | + GDateTime * then; |
1028 | + |
1029 | + interval_msec = g_date_time_difference (alarm_time, now); |
1030 | + interval_msec += G_USEC_PER_SEC; /* fire a moment after alarm_time */ |
1031 | + interval_msec /= 1000; /* convert from usec to msec */ |
1032 | + |
1033 | + str = g_date_time_format (alarm_time, "%F %T"); |
1034 | + g_debug ("%s is the next alarm time", str); |
1035 | + g_free (str); |
1036 | + then = g_date_time_add_seconds (now, interval_msec/1000); |
1037 | + str = g_date_time_format (then, "%F %T"); |
1038 | + g_debug ("%s is when we'll wake up for it", str); |
1039 | + g_free (str); |
1040 | + g_date_time_unref (then); |
1041 | + |
1042 | + p->alarm_timer = g_timeout_add_full (G_PRIORITY_HIGH, |
1043 | + (guint) interval_msec, |
1044 | + on_alarm_timer, |
1045 | + self, |
1046 | + NULL); |
1047 | + } |
1048 | + |
1049 | + g_date_time_unref (now); |
1050 | +} |
1051 | + |
1052 | +/*** |
1053 | +**** |
1054 | +***/ |
1055 | + |
1056 | static void |
1057 | update_internal_timezone (IndicatorDatetimeService * self) |
1058 | { |
1059 | @@ -746,23 +947,36 @@ |
1060 | } |
1061 | |
1062 | static void |
1063 | -add_appointments (IndicatorDatetimeService * self, GMenu * menu, gboolean terse) |
1064 | +add_appointments (IndicatorDatetimeService * self, GMenu * menu, gboolean phone) |
1065 | { |
1066 | const int MAX_APPTS = 5; |
1067 | - GDateTime * now = indicator_datetime_service_get_localtime (self); |
1068 | + GDateTime * now; |
1069 | + GHashTable * added; |
1070 | GSList * appts; |
1071 | GSList * l; |
1072 | int i; |
1073 | |
1074 | + now = indicator_datetime_service_get_localtime (self); |
1075 | + |
1076 | + added = g_hash_table_new (g_str_hash, g_str_equal); |
1077 | + |
1078 | /* build appointment menuitems */ |
1079 | appts = self->priv->upcoming_appointments; |
1080 | for (l=appts, i=0; l!=NULL && i<MAX_APPTS; l=l->next, i++) |
1081 | { |
1082 | struct IndicatorDatetimeAppt * appt = l->data; |
1083 | - char * fmt = get_appointment_time_format (appt, now, self->priv->settings, terse); |
1084 | - const gint64 unix_time = g_date_time_to_unix (appt->begin); |
1085 | + char * fmt; |
1086 | + gint64 unix_time; |
1087 | GMenuItem * menu_item; |
1088 | |
1089 | + if (g_hash_table_contains (added, appt->uid)) |
1090 | + continue; |
1091 | + |
1092 | + g_hash_table_add (added, appt->uid); |
1093 | + |
1094 | + fmt = get_appointment_time_format (appt, now, self->priv->settings, phone); |
1095 | + unix_time = g_date_time_to_unix (appt->begin); |
1096 | + |
1097 | menu_item = g_menu_item_new (appt->summary, NULL); |
1098 | |
1099 | if (appt->has_alarms) |
1100 | @@ -779,15 +993,22 @@ |
1101 | g_menu_item_set_attribute (menu_item, "x-canonical-type", |
1102 | "s", appt->has_alarms ? "com.canonical.indicator.alarm" |
1103 | : "com.canonical.indicator.appointment"); |
1104 | - g_menu_item_set_action_and_target_value (menu_item, |
1105 | - "indicator.activate-planner", |
1106 | - g_variant_new_int64 (unix_time)); |
1107 | + |
1108 | + if (phone) |
1109 | + g_menu_item_set_action_and_target_value (menu_item, |
1110 | + "indicator.activate-appointment", |
1111 | + g_variant_new_string (appt->uid)); |
1112 | + else |
1113 | + g_menu_item_set_action_and_target_value (menu_item, |
1114 | + "indicator.activate-planner", |
1115 | + g_variant_new_int64 (unix_time)); |
1116 | g_menu_append_item (menu, menu_item); |
1117 | g_object_unref (menu_item); |
1118 | g_free (fmt); |
1119 | } |
1120 | |
1121 | /* cleanup */ |
1122 | + g_hash_table_unref (added); |
1123 | g_date_time_unref (now); |
1124 | } |
1125 | |
1126 | @@ -1366,6 +1587,34 @@ |
1127 | } |
1128 | |
1129 | static void |
1130 | +on_activate_appointment (GSimpleAction * a G_GNUC_UNUSED, |
1131 | + GVariant * param, |
1132 | + gpointer gself) |
1133 | +{ |
1134 | + priv_t * p = INDICATOR_DATETIME_SERVICE(gself)->priv; |
1135 | + const gchar * uid = g_variant_get_string (param, NULL); |
1136 | + |
1137 | + if (uid != NULL) |
1138 | + { |
1139 | + const struct IndicatorDatetimeAppt * appt; |
1140 | + GSList * l; |
1141 | + |
1142 | + /* find the appointment that matches that uid */ |
1143 | + for (l=p->upcoming_appointments, appt=NULL; l && !appt; l=l->next) |
1144 | + { |
1145 | + const struct IndicatorDatetimeAppt * tmp = l->data; |
1146 | + if (!g_strcmp0 (uid, tmp->uid)) |
1147 | + appt = tmp; |
1148 | + } |
1149 | + |
1150 | + /* if that appointment's an alarm, dispatch its url */ |
1151 | + g_debug ("%s: uri '%s'; matching appt is %p", G_STRFUNC, uid, appt); |
1152 | + if (appt && appointment_has_alarm_url (appt)) |
1153 | + dispatch_alarm_url (appt); |
1154 | + } |
1155 | +} |
1156 | + |
1157 | +static void |
1158 | on_phone_clock_activated (GSimpleAction * a G_GNUC_UNUSED, |
1159 | GVariant * param G_GNUC_UNUSED, |
1160 | gpointer gself G_GNUC_UNUSED) |
1161 | @@ -1429,6 +1678,7 @@ |
1162 | { "activate-phone-settings", on_phone_settings_activated }, |
1163 | { "activate-phone-clock-app", on_phone_clock_activated }, |
1164 | { "activate-planner", on_activate_planner, "x", NULL }, |
1165 | + { "activate-appointment", on_activate_appointment, "s", NULL }, |
1166 | { "set-location", on_set_location, "s" } |
1167 | }; |
1168 | |
1169 | @@ -1659,6 +1909,10 @@ |
1170 | |
1171 | /* sync the menus/actions */ |
1172 | rebuild_appointments_section_soon (self); |
1173 | + |
1174 | + /* alarm timer is keyed off of the next alarm time, |
1175 | + so it needs to be rebuilt when the appointment list changes */ |
1176 | + set_alarm_timer (self); |
1177 | } |
1178 | |
1179 | static void |
1180 | @@ -1828,6 +2082,45 @@ |
1181 | ***/ |
1182 | |
1183 | static void |
1184 | +my_get_property (GObject * o, |
1185 | + guint property_id, |
1186 | + GValue * value, |
1187 | + GParamSpec * pspec) |
1188 | +{ |
1189 | + IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (o); |
1190 | + |
1191 | + switch (property_id) |
1192 | + { |
1193 | + case PROP_PLANNER: |
1194 | + g_value_set_object (value, self->priv->planner); |
1195 | + break; |
1196 | + |
1197 | + default: |
1198 | + G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec); |
1199 | + } |
1200 | +} |
1201 | + |
1202 | +static void |
1203 | +my_set_property (GObject * o, |
1204 | + guint property_id, |
1205 | + const GValue * value, |
1206 | + GParamSpec * pspec) |
1207 | +{ |
1208 | + IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (o); |
1209 | + |
1210 | + switch (property_id) |
1211 | + { |
1212 | + case PROP_PLANNER: |
1213 | + indicator_datetime_service_set_planner (self, g_value_get_object (value)); |
1214 | + break; |
1215 | + |
1216 | + default: |
1217 | + G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec); |
1218 | + } |
1219 | +} |
1220 | + |
1221 | + |
1222 | +static void |
1223 | my_dispose (GObject * o) |
1224 | { |
1225 | int i; |
1226 | @@ -1850,13 +2143,7 @@ |
1227 | |
1228 | set_detect_location_enabled (self, FALSE); |
1229 | |
1230 | - if (p->planner != NULL) |
1231 | - { |
1232 | - g_signal_handlers_disconnect_by_data (p->planner, self); |
1233 | - g_clear_object (&p->planner); |
1234 | - } |
1235 | - g_clear_pointer (&p->upcoming_appointments, indicator_datetime_planner_free_appointments); |
1236 | - g_clear_pointer (&p->calendar_appointments, indicator_datetime_planner_free_appointments); |
1237 | + indicator_datetime_service_set_planner (self, NULL); |
1238 | |
1239 | if (p->login1_manager != NULL) |
1240 | { |
1241 | @@ -1868,6 +2155,7 @@ |
1242 | indicator_clear_timer (&p->rebuild_id); |
1243 | indicator_clear_timer (&p->timezone_timer); |
1244 | indicator_clear_timer (&p->header_timer); |
1245 | + indicator_clear_timer (&p->alarm_timer); |
1246 | |
1247 | if (p->settings != NULL) |
1248 | { |
1249 | @@ -1958,16 +2246,6 @@ |
1250 | p->cancellable = g_cancellable_new (); |
1251 | |
1252 | /*** |
1253 | - **** Create the planner and listen for changes |
1254 | - ***/ |
1255 | - |
1256 | - p->planner = indicator_datetime_planner_eds_new (); |
1257 | - |
1258 | - g_signal_connect_swapped (p->planner, "appointments-changed", |
1259 | - G_CALLBACK(update_appointment_lists), self); |
1260 | - |
1261 | - |
1262 | - /*** |
1263 | **** Create the settings object and listen for changes |
1264 | ***/ |
1265 | |
1266 | @@ -2036,6 +2314,8 @@ |
1267 | |
1268 | on_local_time_jumped (self); |
1269 | |
1270 | + set_alarm_timer (self); |
1271 | + |
1272 | for (i=0; i<N_PROFILES; ++i) |
1273 | create_menu (self, i); |
1274 | |
1275 | @@ -2046,9 +2326,12 @@ |
1276 | indicator_datetime_service_class_init (IndicatorDatetimeServiceClass * klass) |
1277 | { |
1278 | GObjectClass * object_class = G_OBJECT_CLASS (klass); |
1279 | + const GParamFlags flags = G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS; |
1280 | |
1281 | object_class->dispose = my_dispose; |
1282 | object_class->finalize = my_finalize; |
1283 | + object_class->get_property = my_get_property; |
1284 | + object_class->set_property = my_set_property; |
1285 | |
1286 | g_type_class_add_private (klass, sizeof (IndicatorDatetimeServicePrivate)); |
1287 | |
1288 | @@ -2060,6 +2343,18 @@ |
1289 | NULL, NULL, |
1290 | g_cclosure_marshal_VOID__VOID, |
1291 | G_TYPE_NONE, 0); |
1292 | + |
1293 | + /* install properties */ |
1294 | + |
1295 | + properties[PROP_0] = NULL; |
1296 | + |
1297 | + properties[PROP_PLANNER] = g_param_spec_object ("planner", |
1298 | + "Planner", |
1299 | + "The appointment provider", |
1300 | + INDICATOR_TYPE_DATETIME_PLANNER, |
1301 | + flags); |
1302 | + |
1303 | + g_object_class_install_properties (object_class, PROP_LAST, properties); |
1304 | } |
1305 | |
1306 | /*** |
1307 | @@ -2067,9 +2362,11 @@ |
1308 | ***/ |
1309 | |
1310 | IndicatorDatetimeService * |
1311 | -indicator_datetime_service_new (void) |
1312 | +indicator_datetime_service_new (IndicatorDatetimePlanner * planner) |
1313 | { |
1314 | - GObject * o = g_object_new (INDICATOR_TYPE_DATETIME_SERVICE, NULL); |
1315 | + GObject * o = g_object_new (INDICATOR_TYPE_DATETIME_SERVICE, |
1316 | + "planner", planner, |
1317 | + NULL); |
1318 | |
1319 | return INDICATOR_DATETIME_SERVICE (o); |
1320 | } |
1321 | @@ -2105,3 +2402,38 @@ |
1322 | if (dirty) |
1323 | update_appointment_lists (self); |
1324 | } |
1325 | + |
1326 | +void |
1327 | +indicator_datetime_service_set_planner (IndicatorDatetimeService * self, |
1328 | + IndicatorDatetimePlanner * planner) |
1329 | +{ |
1330 | + priv_t * p; |
1331 | + |
1332 | + g_return_if_fail (INDICATOR_IS_DATETIME_SERVICE (self)); |
1333 | + g_return_if_fail (INDICATOR_IS_DATETIME_PLANNER (planner)); |
1334 | + |
1335 | + p = self->priv; |
1336 | + |
1337 | + /* clear the old planner & appointments */ |
1338 | + |
1339 | + if (p->planner != NULL) |
1340 | + { |
1341 | + g_signal_handlers_disconnect_by_data (p->planner, self); |
1342 | + g_clear_object (&p->planner); |
1343 | + } |
1344 | + |
1345 | + g_clear_pointer (&p->upcoming_appointments, indicator_datetime_planner_free_appointments); |
1346 | + g_clear_pointer (&p->calendar_appointments, indicator_datetime_planner_free_appointments); |
1347 | + |
1348 | + /* set the new planner & begin fetching appointments from it */ |
1349 | + |
1350 | + if (planner != NULL) |
1351 | + { |
1352 | + p->planner = g_object_ref (planner); |
1353 | + |
1354 | + g_signal_connect_swapped (p->planner, "appointments-changed", |
1355 | + G_CALLBACK(update_appointment_lists), self); |
1356 | + |
1357 | + update_appointment_lists (self); |
1358 | + } |
1359 | +} |
1360 | |
1361 | === modified file 'src/service.h' |
1362 | --- src/service.h 2013-06-20 18:44:21 +0000 |
1363 | +++ src/service.h 2013-10-17 02:42:11 +0000 |
1364 | @@ -22,6 +22,7 @@ |
1365 | |
1366 | #include <glib.h> |
1367 | #include <glib-object.h> |
1368 | +#include "planner.h" |
1369 | |
1370 | G_BEGIN_DECLS |
1371 | |
1372 | @@ -62,13 +63,17 @@ |
1373 | |
1374 | GType indicator_datetime_service_get_type (void); |
1375 | |
1376 | -IndicatorDatetimeService * indicator_datetime_service_new (void); |
1377 | +IndicatorDatetimeService * indicator_datetime_service_new (IndicatorDatetimePlanner * planner); |
1378 | |
1379 | GDateTime * indicator_datetime_service_get_localtime (IndicatorDatetimeService * service); |
1380 | |
1381 | void indicator_datetime_service_set_calendar_date (IndicatorDatetimeService * self, |
1382 | GDateTime * date); |
1383 | |
1384 | +void indicator_datetime_service_set_planner (IndicatorDatetimeService * self, |
1385 | + IndicatorDatetimePlanner * planner); |
1386 | + |
1387 | + |
1388 | |
1389 | G_END_DECLS |
1390 |
PASSED: Continuous integration, rev:270 jenkins. qa.ubuntu. com/job/ indicator- datetime- ci/109/ jenkins. qa.ubuntu. com/job/ indicator- datetime- saucy-amd64- ci/63 jenkins. qa.ubuntu. com/job/ indicator- datetime- saucy-armhf- ci/64 jenkins. qa.ubuntu. com/job/ indicator- datetime- saucy-armhf- ci/64/artifact/ work/output/ *zip*/output. zip
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild: 10.97.0. 26:8080/ job/indicator- datetime- ci/109/ rebuild
http://