Merge lp:~charlesk/indicator-datetime/lp-1233176 into lp:~indicator-applet-developers/indicator-datetime/trunk.13.10

Proposed by Charles Kerr
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
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

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_generate_instances(). What's new is that for each ECalComponent we find in generate_instances(), we add another new subtask that tries to get the uris for that component.

== Testing changes:

Make "planner" a property in IndicatorDatetimeService so that we can swap in different appointment planners at runtime. This is for unit testing purposes.

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.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
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.

review: Needs Information
Revision history for this message
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?

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
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://mail-attachment.googleusercontent.com/attachment/u/0/?ui=2&ik=4f90c10d21&view=att&th=1419d12b40349644&attid=0.1&disp=inline&realattid=f_hmki0fiy0&safe=1&zw&saduie=AG9B_P8X6flBM44Q7TV0yPjSPEw3&sadet=1381357607375&sads=JTIWz7ZeIrRfaXWIFQ0XP7tlTyk. It should not open the clock app.

The clock app is opened only when the user clicks on one of the alarms in the indicator-date-time

Revision history for this message
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://mail-attachment.googleusercontent.com/attachment...

We don't know your Gmail password. ;-)

Revision history for this message
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.

Revision history for this message
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://imgur.com/o4LW3Je

> > as shown in https://mail-attachment.googleusercontent.com/attachment...
> We don't know your Gmail password. ;-)

Revision history for this message
Nekhelesh Ramananthan (nik90) wrote :

Charles can we get this in asap before the freeze. This is critical to alarm functionality.

Revision history for this message
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.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
288. By Charles Kerr

change ok/cancel buttons to the slightly-more-informative show/dismiss

289. By Charles Kerr

in the alarm snap decision, add x-canonical-private-button-tint hint to highlight the 'Show' button

Revision history for this message
Ted Gould (ted) :
review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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

Subscribers

People subscribed via source and target branches