Merge lp:~karl-qdh/ubuntu/natty/indicator-datetime/indicator-datetime.withappointments into lp:indicator-datetime/0.3
- Natty (11.04)
- indicator-datetime.withappointments
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 39 |
Proposed branch: | lp:~karl-qdh/ubuntu/natty/indicator-datetime/indicator-datetime.withappointments |
Merge into: | lp:indicator-datetime/0.3 |
Diff against target: |
686 lines (+530/-11) 4 files modified
configure.ac (+35/-7) src/datetime-service.c (+297/-4) src/dbus-shared.h (+9/-0) src/indicator-datetime.c (+189/-0) |
To merge this branch: | bzr merge lp:~karl-qdh/ubuntu/natty/indicator-datetime/indicator-datetime.withappointments |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Terry | Pending | ||
Indicator Applet Developers | Pending | ||
Review via email: mp+48607@code.launchpad.net |
Commit message
Description of the change
Fixes *most* of the requirements of bug #542218 calendar integration.
What hasn't been done
* calendar coloured icons (thats next)
* Locations
* Timezone matching - this requires an awful lot of careful consideration based on eds timezone, current selected timezone and geoclue timezone.
Is affected by bug #713041 also, leaving a small iB or something in place of the last deleted item.
Karl Lattimer (karl-qdh) wrote : | # |
Michael Terry (mterry) wrote : | # |
I looked at this with an eye for why you'd be having problems when deleting menu items. Code seems fine to me looking over it... You're just adding and deleting items. The bugs I told you to expect have been fixed in dbusmenu and friends for a while now, so I don't think they are causing your issue.
- 43. By Karl Lattimer
-
Added the timezone/location menu item code.
- 44. By Karl Lattimer
-
Added ESource Colours a circle drawn onto a cairo surface, however this code
has one caveat, it's un-testable with evolution as evolutions color peek is
currently broken. However I've used a very similar method of exchanging
GdkDrawables and cairo_surface_t before and it worked... Lets see this time :/ - 45. By Karl Lattimer
-
Updated datetime service slightly to draw coloured dots for evolution colours, evolution is still slightly broken at doing it's part here and returns null colours.
Updated configure.ac (hope this doesn't break merge), and added the radio menu item with a right aligned time to the indicator so we can have location/timezone entries. - 46. By Karl Lattimer
-
Merging ted's branch
- 47. By Karl Lattimer
-
Re-adding tedg's changes
Preview Diff
1 | === modified file 'configure.ac' |
2 | --- configure.ac 2011-01-27 22:34:24 +0000 |
3 | +++ configure.ac 2011-02-08 16:26:45 +0000 |
4 | @@ -59,6 +59,11 @@ |
5 | INDICATOR_DISPLAY_OBJECTS=0.1.10 |
6 | GEOCLUE_REQUIRED_VERSION=0.12.0 |
7 | OOBS_REQUIRED_VERSION=2.31.0 |
8 | +ECAL_REQUIRED_VERSION=2.30 |
9 | +EDS_REQUIRED_VERSION=2.30 |
10 | +ICAL_REQUIRED_VERSION=0.44 |
11 | +CAIRO_REQUIRED_VERSION=1.10 |
12 | +GDK_REQUIRED_VERSION=2.22 |
13 | |
14 | AS_IF([test "x$with_gtk" = x3], |
15 | [PKG_CHECK_MODULES(INDICATOR, indicator3 >= $INDICATOR_REQUIRED_VERSION |
16 | @@ -75,13 +80,36 @@ |
17 | [AC_MSG_FAILURE([Value for --with-gtk was neither 2 nor 3])] |
18 | ) |
19 | |
20 | -PKG_CHECK_MODULES(SERVICE, indicator >= $INDICATOR_REQUIRED_VERSION |
21 | - dbusmenu-glib-0.4 >= $DBUSMENUGLIB_REQUIRED_VERSION |
22 | - libido-0.1 >= $INDICATOR_DISPLAY_OBJECTS |
23 | - gio-2.0 >= $GIO_REQUIRED_VERSION |
24 | - geoclue >= $GEOCLUE_REQUIRED_VERSION |
25 | - liboobs-1 >= $OOBS_REQUIRED_VERSION) |
26 | - |
27 | +AS_IF([test "x$with_gtk" = x3], |
28 | + [PKG_CHECK_MODULES(SERVICE, indicator >= $INDICATOR_REQUIRED_VERSION |
29 | + dbusmenu-glib-0.4 >= $DBUSMENUGLIB_REQUIRED_VERSION |
30 | + dbusmenu-gtk3-0.4 >= $DBUSMENUGTK_REQUIRED_VERSION |
31 | + libido-0.1 >= $INDICATOR_DISPLAY_OBJECTS |
32 | + gio-2.0 >= $GIO_REQUIRED_VERSION |
33 | + geoclue >= $GEOCLUE_REQUIRED_VERSION |
34 | + liboobs-1 >= $OOBS_REQUIRED_VERSION |
35 | + libecal-1.2 >= $ECAL_REQUIRED_VERSION |
36 | + libical >= $ICAL_REQUIRED_VERSION |
37 | + libedataserver-1.2 >= EDS_REQUIRED_VERSION |
38 | + cairo >= CAIRO_REQUIRED_VERSION |
39 | + gdk-2.0 >= GDK_REQUIRED_VERSION) |
40 | + ], |
41 | + [test "x$with_gtk" = x2], |
42 | + [PKG_CHECK_MODULES(SERVICE, indicator >= $INDICATOR_REQUIRED_VERSION |
43 | + dbusmenu-glib-0.4 >= $DBUSMENUGLIB_REQUIRED_VERSION |
44 | + dbusmenu-gtk-0.4 >= $DBUSMENUGTK_REQUIRED_VERSION |
45 | + libido-0.1 >= $INDICATOR_DISPLAY_OBJECTS |
46 | + gio-2.0 >= $GIO_REQUIRED_VERSION |
47 | + geoclue >= $GEOCLUE_REQUIRED_VERSION |
48 | + liboobs-1 >= $OOBS_REQUIRED_VERSION |
49 | + libecal-1.2 >= $ECAL_REQUIRED_VERSION |
50 | + libical >= $ICAL_REQUIRED_VERSION |
51 | + libedataserver-1.2 >= EDS_REQUIRED_VERSION |
52 | + cairo >= CAIRO_REQUIRED_VERSION |
53 | + gdk-2.0 >= GDK_REQUIRED_VERSION) |
54 | + ], |
55 | + [AC_MSG_FAILURE([Value for --with-gtk was neither 2 nor 3])] |
56 | +) |
57 | AC_SUBST(INDICATOR_CFLAGS) |
58 | AC_SUBST(INDICATOR_LIBS) |
59 | |
60 | |
61 | === modified file 'src/datetime-service.c' |
62 | --- src/datetime-service.c 2011-02-01 15:52:10 +0000 |
63 | +++ src/datetime-service.c 2011-02-08 16:26:45 +0000 |
64 | @@ -23,9 +23,13 @@ |
65 | #include <libindicator/indicator-service.h> |
66 | #include <locale.h> |
67 | |
68 | +#include <gtk/gtk.h> |
69 | +#include <gdk/gdk.h> |
70 | #include <glib/gi18n.h> |
71 | #include <gio/gio.h> |
72 | +#include <math.h> |
73 | |
74 | +#include <libdbusmenu-gtk/menuitem.h> |
75 | #include <libdbusmenu-glib/server.h> |
76 | #include <libdbusmenu-glib/client.h> |
77 | #include <libdbusmenu-glib/menuitem.h> |
78 | @@ -33,12 +37,23 @@ |
79 | #include <geoclue/geoclue-master.h> |
80 | #include <geoclue/geoclue-master-client.h> |
81 | |
82 | +#include <time.h> |
83 | +#include <libecal/e-cal.h> |
84 | +#include <libical/ical.h> |
85 | +#include <libecal/e-cal-time-util.h> |
86 | +#include <libedataserver/e-source.h> |
87 | +// Other users of ecal seem to also include these, not sure why they should be included by the above |
88 | +#include <libical/icaltime.h> |
89 | +#include <cairo/cairo.h> |
90 | + |
91 | + |
92 | #include <oobs/oobs-timeconfig.h> |
93 | |
94 | #include "datetime-interface.h" |
95 | #include "dbus-shared.h" |
96 | |
97 | static void geo_create_client (GeoclueMaster * master, GeoclueMasterClient * client, gchar * path, GError * error, gpointer user_data); |
98 | +static gboolean update_appointment_menu_items (gpointer user_data); |
99 | static void setup_timer (void); |
100 | static void geo_client_invalid (GeoclueMasterClient * client, gpointer user_data); |
101 | static void geo_address_change (GeoclueMasterClient * client, gchar * a, gchar * b, gchar * c, gchar * d, gpointer user_data); |
102 | @@ -55,6 +70,12 @@ |
103 | static DbusmenuMenuitem * calendar = NULL; |
104 | static DbusmenuMenuitem * settings = NULL; |
105 | static DbusmenuMenuitem * tzchange = NULL; |
106 | +static DbusmenuMenuitem * add_appointment = NULL; |
107 | +static DbusmenuMenuitem * add_location = NULL; |
108 | +static GList * appointments = NULL; |
109 | +static ECal * ecal = NULL; |
110 | +static const gchar * ecal_timezone = NULL; |
111 | +static icaltimezone *tzone; |
112 | |
113 | /* Geoclue trackers */ |
114 | static GeoclueMasterClient * geo_master = NULL; |
115 | @@ -218,13 +239,268 @@ |
116 | g_debug("Found the calendar application: %s", evo); |
117 | dbusmenu_menuitem_property_set_bool(calendar, DBUSMENU_MENUITEM_PROP_ENABLED, TRUE); |
118 | dbusmenu_menuitem_property_set_bool(calendar, DBUSMENU_MENUITEM_PROP_VISIBLE, TRUE); |
119 | + dbusmenu_menuitem_property_set_bool(add_appointment, DBUSMENU_MENUITEM_PROP_ENABLED, TRUE); |
120 | + dbusmenu_menuitem_property_set_bool(add_appointment, DBUSMENU_MENUITEM_PROP_VISIBLE, TRUE); |
121 | + |
122 | + GError *gerror = NULL; |
123 | + // TODO: In reality we should iterate sources of calendar, but getting the local one doens't lag for > a minute |
124 | + g_debug("Setting up ecal."); |
125 | + if (!ecal) |
126 | + ecal = e_cal_new_system_calendar(); |
127 | + |
128 | + if (!ecal) { |
129 | + g_debug("e_cal_new_system_calendar failed"); |
130 | + ecal = NULL; |
131 | + } |
132 | + g_debug("Open calendar."); |
133 | + if (!e_cal_open(ecal, FALSE, &gerror) ) { |
134 | + g_debug("e_cal_open: %s\n", gerror->message); |
135 | + g_free(ecal); |
136 | + ecal = NULL; |
137 | + } |
138 | + g_debug("Get calendar timezone."); |
139 | + if (!e_cal_get_timezone(ecal, "UTC", &tzone, &gerror)) { |
140 | + g_debug("failed to get time zone\n"); |
141 | + g_free(ecal); |
142 | + ecal = NULL; |
143 | + } |
144 | + |
145 | + /* This timezone represents the timezone of the calendar, this might be different to the current UTC offset. |
146 | + * this means we'll have some geoclue interaction going on, and possibly the user will be involved in setting |
147 | + * their location manually, case in point: trains have satellite links which often geoclue to sweden, |
148 | + * this shouldn't automatically set the location and mess up all the appointments for the user. |
149 | + */ |
150 | + if (ecal) ecal_timezone = icaltimezone_get_tzid(tzone); |
151 | + |
152 | + update_appointment_menu_items(NULL); |
153 | + g_signal_connect(root, DBUSMENU_MENUITEM_SIGNAL_ABOUT_TO_SHOW, G_CALLBACK(update_appointment_menu_items), NULL); |
154 | + |
155 | g_free(evo); |
156 | } else { |
157 | g_debug("Unable to find calendar app."); |
158 | dbusmenu_menuitem_property_set_bool(calendar, DBUSMENU_MENUITEM_PROP_VISIBLE, FALSE); |
159 | - } |
160 | - |
161 | - return FALSE; |
162 | + dbusmenu_menuitem_property_set_bool(add_appointment, DBUSMENU_MENUITEM_PROP_VISIBLE, FALSE); |
163 | + } |
164 | + |
165 | + return FALSE; |
166 | +} |
167 | + |
168 | +static gboolean |
169 | +update_timezone_menu_items(gpointer user_data) { |
170 | + // Get the current location as specified by the user as a place name and time and add it, |
171 | + // Get the location from geoclue as a place and time and add it, |
172 | + // Get the evolution calendar timezone as a place and time and add it, |
173 | + // Get the current timezone that the clock uses and select that |
174 | + // Iterate over configured places and add any which aren't already listed |
175 | + // Hook up each of these to setting the time/current timezone |
176 | + return FALSE; |
177 | +} |
178 | + |
179 | +// Compare function for g_list_sort of ECalComponent objects |
180 | +static gint |
181 | +compare_appointment_items (ECalComponent *a, |
182 | + ECalComponent *b) { |
183 | + |
184 | + ECalComponentDateTime datetime_a, datetime_b; |
185 | + struct tm tm_a, tm_b; |
186 | + time_t t_a, t_b; |
187 | + gint retval = 0; |
188 | + |
189 | + ECalComponentVType vtype = e_cal_component_get_vtype (a); |
190 | + if (vtype == E_CAL_COMPONENT_EVENT) |
191 | + e_cal_component_get_dtstart (a, &datetime_a); |
192 | + else |
193 | + e_cal_component_get_due (a, &datetime_a); |
194 | + tm_a = icaltimetype_to_tm(datetime_a.value); |
195 | + t_a = mktime(&tm_a); |
196 | + |
197 | + vtype = e_cal_component_get_vtype (b); |
198 | + if (vtype == E_CAL_COMPONENT_EVENT) |
199 | + e_cal_component_get_dtstart (b, &datetime_b); |
200 | + else |
201 | + e_cal_component_get_due (b, &datetime_b); |
202 | + tm_b = icaltimetype_to_tm(datetime_b.value); |
203 | + t_b = mktime(&tm_b); |
204 | + |
205 | + // Compare datetime_a and datetime_b, newest first in this sort. |
206 | + if (t_a > t_b) retval = 1; |
207 | + else if (t_a < t_b) retval = -1; |
208 | + |
209 | + e_cal_component_free_datetime (&datetime_a); |
210 | + e_cal_component_free_datetime (&datetime_b); |
211 | + return retval; |
212 | +} |
213 | + |
214 | +/* Populate the menu with todays, next 5 appointments. |
215 | + * we should hook into the ABOUT TO SHOW signal and use that to update the appointments. |
216 | + * Experience has shown that caldav's and webcals can be slow to load from eds |
217 | + * this is a problem mainly on the EDS side of things, not ours. |
218 | + */ |
219 | +static gboolean |
220 | +update_appointment_menu_items (gpointer user_data) { |
221 | + if (!ecal) return FALSE; |
222 | + // FFR: we should take into account short term timers, for instance |
223 | + // tea timers, pomodoro timers etc... that people may add, this is hinted to in the spec. |
224 | + time_t t1, t2; |
225 | + gchar *query, *is, *ie, *ad; |
226 | + GList *objects = NULL, *l; |
227 | + GError *gerror = NULL; |
228 | + gint i; |
229 | + gint width, height; |
230 | + |
231 | + time(&t1); |
232 | + time(&t2); |
233 | + t2 += (time_t) (7 * 24 * 60 * 60); /* 7 days ahead of now */ |
234 | + |
235 | + is = isodate_from_time_t(t1); |
236 | + ie = isodate_from_time_t(t2); |
237 | + |
238 | + // FIXME can we put a limit on the number of results? Or if not complete, or is event/todo? Or sort it by date? |
239 | + query = g_strdup_printf("(occur-in-time-range? (make-time\"%s\") (make-time\"%s\"))", is, ie); |
240 | + g_debug("Getting objects with query: %s", query); |
241 | + if (!e_cal_get_object_list_as_comp(ecal, query, &objects, &gerror)) { |
242 | + g_debug("Failed to get objects\n"); |
243 | + g_free(ecal); |
244 | + ecal = NULL; |
245 | + return FALSE; |
246 | + } |
247 | + g_debug("Number of objects returned: %d", g_list_length(objects)); |
248 | + gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height); |
249 | + |
250 | + /* Remove all of the previous appointments */ |
251 | + if (appointments != NULL) { |
252 | + g_debug("Freeing old appointments"); |
253 | + while (appointments != NULL) { |
254 | + DbusmenuMenuitem * litem = DBUSMENU_MENUITEM(appointments->data); |
255 | + g_debug("Freeing old appointment: %p", litem); |
256 | + // Remove all the existing menu items which are in appointments. |
257 | + appointments = g_list_remove(appointments, litem); |
258 | + dbusmenu_menuitem_child_delete(root, DBUSMENU_MENUITEM(litem)); |
259 | + g_object_unref(G_OBJECT(litem)); |
260 | + } |
261 | + } |
262 | + |
263 | + // Sort the list see above FIXME regarding queries |
264 | + objects = g_list_sort(objects, (GCompareFunc) compare_appointment_items); |
265 | + i = 0; |
266 | + for (l = objects; l; l = l->next) { |
267 | + ECalComponent *ecalcomp = l->data; |
268 | + ECalComponentText valuetext; |
269 | + ECalComponentDateTime datetime; |
270 | + icaltimezone *appointment_zone = NULL; |
271 | + icalproperty_status status; |
272 | + gchar *summary, *cmd; |
273 | + char right[20]; |
274 | + //const gchar *uri; |
275 | + struct tm tmp_tm; |
276 | + DbusmenuMenuitem * item; |
277 | + |
278 | + ECalComponentVType vtype = e_cal_component_get_vtype (ecalcomp); |
279 | + |
280 | + // See above FIXME regarding query result |
281 | + // If it's not an event or todo, continue no-increment |
282 | + if (vtype != E_CAL_COMPONENT_EVENT && vtype != E_CAL_COMPONENT_TODO) |
283 | + continue; |
284 | + |
285 | + // See above FIXME regarding query result |
286 | + // if the status is completed, continue no-increment |
287 | + e_cal_component_get_status (ecalcomp, &status); |
288 | + if (status == ICAL_STATUS_COMPLETED || status == ICAL_STATUS_CANCELLED) |
289 | + continue; |
290 | + |
291 | + item = dbusmenu_menuitem_new(); |
292 | + dbusmenu_menuitem_property_set (item, DBUSMENU_MENUITEM_PROP_TYPE, APPOINTMENT_MENUITEM_TYPE); |
293 | + dbusmenu_menuitem_property_set_bool (item, DBUSMENU_MENUITEM_PROP_ENABLED, TRUE); |
294 | + dbusmenu_menuitem_property_set_bool (item, DBUSMENU_MENUITEM_PROP_VISIBLE, TRUE); |
295 | + |
296 | + g_debug("Start Object"); |
297 | + // Label text |
298 | + e_cal_component_get_summary (ecalcomp, &valuetext); |
299 | + summary = g_strdup (valuetext.value); |
300 | + |
301 | + dbusmenu_menuitem_property_set (item, APPOINTMENT_MENUITEM_PROP_LABEL, summary); |
302 | + g_debug("Summary: %s", summary); |
303 | + g_free (summary); |
304 | + |
305 | + // Due text |
306 | + if (vtype == E_CAL_COMPONENT_EVENT) |
307 | + e_cal_component_get_dtstart (ecalcomp, &datetime); |
308 | + else |
309 | + e_cal_component_get_due (ecalcomp, &datetime); |
310 | + |
311 | + // FIXME need to get the timezone of the above datetime, |
312 | + // and get the icaltimezone of the geoclue timezone/selected timezone (whichever is preferred) |
313 | + if (!datetime.value) { |
314 | + g_free(item); |
315 | + continue; |
316 | + } |
317 | + |
318 | + if (!appointment_zone || datetime.value->is_date) { // If it's today put in the current timezone? |
319 | + appointment_zone = tzone; |
320 | + } |
321 | + tmp_tm = icaltimetype_to_tm_with_zone (datetime.value, appointment_zone, tzone); |
322 | + |
323 | + g_debug("Generate time string"); |
324 | + // Get today |
325 | + time_t curtime = time(NULL); |
326 | + struct tm* today = localtime(&curtime); |
327 | + if (today->tm_mday == tmp_tm.tm_mday && |
328 | + today->tm_mon == tmp_tm.tm_mon && |
329 | + today->tm_year == tmp_tm.tm_year) |
330 | + strftime(right, 20, "%l:%M %P", &tmp_tm); |
331 | + else |
332 | + strftime(right, 20, "%a %l:%M %P", &tmp_tm); |
333 | + |
334 | + g_debug("Appointment time: %s", right); |
335 | + dbusmenu_menuitem_property_set (item, APPOINTMENT_MENUITEM_PROP_RIGHT, right); |
336 | + |
337 | + e_cal_component_free_datetime (&datetime); |
338 | + |
339 | + ad = is = isodate_from_time_t(mktime(&tmp_tm)); |
340 | + |
341 | + // Now we pull out the URI for the calendar event and try to create a URI that'll work when we execute evolution |
342 | + // FIXME Because the URI stuff is really broken, we're going to open the calendar at todays date instead |
343 | + |
344 | + //e_cal_component_get_uid(ecalcomp, &uri); |
345 | + cmd = g_strconcat("evolution calendar:///?startdate=", ad, NULL); |
346 | + g_signal_connect (G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, |
347 | + G_CALLBACK (activate_cb), cmd); |
348 | + |
349 | + g_debug("Command to Execute: %s", cmd); |
350 | + |
351 | + ESource *source = e_cal_get_source (ecal); |
352 | + //e_source_get_color (source, &source_color); api has been changed |
353 | + const gchar *color_spec = e_source_peek_color_spec(source); |
354 | + GdkColor color; |
355 | + g_debug("Colour to use: %s", color_spec); |
356 | + |
357 | + // Draw the correct icon for the appointment type and then tint it using mask fill. |
358 | + // For now we'll create a circle |
359 | + if (color_spec != NULL) { |
360 | + gdk_color_parse (color_spec, &color); |
361 | + |
362 | + cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); |
363 | + cairo_t *cr = cairo_create(cs); |
364 | + cairo_arc (cr, width/2, height/2, width/2, 0, 2 * M_PI); |
365 | + gdk_cairo_set_source_color(cr, &color); |
366 | + cairo_fill(cr); |
367 | + |
368 | + GdkPixbuf * pixbuf = gdk_pixbuf_get_from_drawable(NULL, (GdkDrawable*)cs, |
369 | + gdk_colormap_new(gdk_drawable_get_visual((GdkDrawable*)cs), TRUE), 0,0,0,0, width, height); |
370 | + cairo_destroy(cr); |
371 | + |
372 | + dbusmenu_menuitem_property_set_image (item, APPOINTMENT_MENUITEM_PROP_ICON, pixbuf); |
373 | + } |
374 | + dbusmenu_menuitem_child_add_position (root, item, 4+i); |
375 | + appointments = g_list_append (appointments, item); // Keep track of the items here to make them east to remove |
376 | + g_debug("Adding appointment: %p", item); |
377 | + |
378 | + if (i == 4) break; // See above FIXME regarding query result limit |
379 | + i++; |
380 | + } |
381 | + g_list_free(objects); |
382 | + g_debug("End of objects"); |
383 | + return TRUE; |
384 | } |
385 | |
386 | /* Looks for the time and date admin application and enables the |
387 | @@ -274,8 +550,20 @@ |
388 | |
389 | g_idle_add(check_for_calendar, NULL); |
390 | } |
391 | + DbusmenuMenuitem * separator; |
392 | + |
393 | + separator = dbusmenu_menuitem_new(); |
394 | + dbusmenu_menuitem_property_set(separator, DBUSMENU_MENUITEM_PROP_TYPE, DBUSMENU_CLIENT_TYPES_SEPARATOR); |
395 | + dbusmenu_menuitem_child_append(root, separator); |
396 | |
397 | - DbusmenuMenuitem * separator = dbusmenu_menuitem_new(); |
398 | + add_appointment = dbusmenu_menuitem_new(); |
399 | + dbusmenu_menuitem_property_set (add_appointment, DBUSMENU_MENUITEM_PROP_LABEL, _("Add Appointment")); |
400 | + dbusmenu_menuitem_property_set_bool(add_appointment, DBUSMENU_MENUITEM_PROP_ENABLED, FALSE); |
401 | + dbusmenu_menuitem_property_set_bool(add_appointment, DBUSMENU_MENUITEM_PROP_VISIBLE, TRUE); |
402 | + g_signal_connect(G_OBJECT(add_appointment), DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, G_CALLBACK(activate_cb), "evolution -c calendar"); |
403 | + dbusmenu_menuitem_child_add_position (root, add_appointment, 4); |
404 | + |
405 | + separator = dbusmenu_menuitem_new(); |
406 | dbusmenu_menuitem_property_set(separator, DBUSMENU_MENUITEM_PROP_TYPE, DBUSMENU_CLIENT_TYPES_SEPARATOR); |
407 | dbusmenu_menuitem_child_append(root, separator); |
408 | |
409 | @@ -286,6 +574,10 @@ |
410 | dbusmenu_menuitem_child_append(root, tzchange); |
411 | check_timezone_sync(); |
412 | |
413 | + separator = dbusmenu_menuitem_new(); |
414 | + dbusmenu_menuitem_property_set(separator, DBUSMENU_MENUITEM_PROP_TYPE, DBUSMENU_CLIENT_TYPES_SEPARATOR); |
415 | + dbusmenu_menuitem_child_append(root, separator); |
416 | + |
417 | settings = dbusmenu_menuitem_new(); |
418 | dbusmenu_menuitem_property_set (settings, DBUSMENU_MENUITEM_PROP_LABEL, _("Time & Date Settings...")); |
419 | /* insensitive until we check for available apps */ |
420 | @@ -566,6 +858,7 @@ |
421 | server = dbusmenu_server_new(MENU_OBJ); |
422 | root = dbusmenu_menuitem_new(); |
423 | dbusmenu_server_set_root(server, root); |
424 | + |
425 | build_menus(root); |
426 | |
427 | /* Setup geoclue */ |
428 | |
429 | === modified file 'src/dbus-shared.h' |
430 | --- src/dbus-shared.h 2011-01-17 17:42:47 +0000 |
431 | +++ src/dbus-shared.h 2011-02-08 16:26:45 +0000 |
432 | @@ -29,3 +29,12 @@ |
433 | |
434 | #define DBUSMENU_CALENDAR_MENUITEM_TYPE "x-canonical-calendar-item" |
435 | |
436 | +#define APPOINTMENT_MENUITEM_TYPE "appointment-item" |
437 | +#define APPOINTMENT_MENUITEM_PROP_LABEL "appointment-label" |
438 | +#define APPOINTMENT_MENUITEM_PROP_ICON "appointment-icon" |
439 | +#define APPOINTMENT_MENUITEM_PROP_RIGHT "appointment-time" |
440 | + |
441 | +#define TIMEZONE_MENUITEM_TYPE "timezone-item" |
442 | +#define TIMEZONE_MENUITEM_PROP_LABEL "timezone-label" |
443 | +#define TIMEZONE_MENUITEM_PROP_RADIO "timezone-radio" |
444 | +#define TIMEZONE_MENUITEM_PROP_RIGHT "timezone-time" |
445 | |
446 | === modified file 'src/indicator-datetime.c' |
447 | --- src/indicator-datetime.c 2011-02-01 03:12:12 +0000 |
448 | +++ src/indicator-datetime.c 2011-02-08 16:26:45 +0000 |
449 | @@ -41,6 +41,7 @@ |
450 | /* DBusMenu */ |
451 | #include <libdbusmenu-gtk/menu.h> |
452 | #include <libido/idocalendarmenuitem.h> |
453 | +#include <libdbusmenu-gtk/menuitem.h> |
454 | |
455 | #include "dbus-shared.h" |
456 | |
457 | @@ -102,6 +103,14 @@ |
458 | PROP_CUSTOM_TIME_FORMAT |
459 | }; |
460 | |
461 | +typedef struct _indicator_item_t indicator_item_t; |
462 | +struct _indicator_item_t { |
463 | + GtkWidget * radio; |
464 | + GtkWidget * icon; |
465 | + GtkWidget * label; |
466 | + GtkWidget * right; |
467 | +}; |
468 | + |
469 | #define PROP_TIME_FORMAT_S "time-format" |
470 | #define PROP_SHOW_SECONDS_S "show-seconds" |
471 | #define PROP_SHOW_DAY_S "show-day" |
472 | @@ -167,6 +176,7 @@ |
473 | static void receive_signal (GDBusProxy * proxy, gchar * sender_name, gchar * signal_name, GVariant * parameters, gpointer user_data); |
474 | static void service_proxy_cb (GObject * object, GAsyncResult * res, gpointer user_data); |
475 | static gint generate_strftime_bitmask (const char *time_str); |
476 | +static GSList *location_group = NULL; |
477 | |
478 | /* Indicator Module Config */ |
479 | INDICATOR_SET_VERSION |
480 | @@ -174,6 +184,8 @@ |
481 | |
482 | G_DEFINE_TYPE (IndicatorDatetime, indicator_datetime, INDICATOR_OBJECT_TYPE); |
483 | |
484 | +static GtkSizeGroup * indicator_right_group = NULL; |
485 | + |
486 | static void |
487 | indicator_datetime_class_init (IndicatorDatetimeClass *klass) |
488 | { |
489 | @@ -1055,6 +1067,129 @@ |
490 | return g_strdup_printf(T_("%s, %s"), date_string, time_string); |
491 | } |
492 | |
493 | +/* Whenever we have a property change on a DbusmenuMenuitem |
494 | + we need to be responsive to that. */ |
495 | +static void |
496 | +indicator_prop_change_cb (DbusmenuMenuitem * mi, gchar * prop, gchar * value, indicator_item_t * mi_data) |
497 | +{ |
498 | + if (!g_strcmp0(prop, APPOINTMENT_MENUITEM_PROP_LABEL)) { |
499 | + /* Set the main label */ |
500 | + gtk_label_set_text(GTK_LABEL(mi_data->label), value); |
501 | + } else if (!g_strcmp0(prop, APPOINTMENT_MENUITEM_PROP_RIGHT)) { |
502 | + /* Set the right label */ |
503 | + gtk_label_set_text(GTK_LABEL(mi_data->right), value); |
504 | + } else if (!g_strcmp0(prop, APPOINTMENT_MENUITEM_PROP_ICON)) { |
505 | + /* We don't use the value here, which is probably less efficient, |
506 | + but it's easier to use the easy function. And since th value |
507 | + is already cached, shouldn't be a big deal really. */ |
508 | + GdkPixbuf * pixbuf = dbusmenu_menuitem_property_get_image(mi, APPOINTMENT_MENUITEM_PROP_ICON); |
509 | + if (pixbuf != NULL) { |
510 | + /* If we've got a pixbuf we need to make sure it's of a reasonable |
511 | + size to fit in the menu. If not, rescale it. */ |
512 | + GdkPixbuf * resized_pixbuf; |
513 | + gint width, height; |
514 | + gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height); |
515 | + if (gdk_pixbuf_get_width(pixbuf) > width || |
516 | + gdk_pixbuf_get_height(pixbuf) > height) { |
517 | + g_debug("Resizing icon from %dx%d to %dx%d", gdk_pixbuf_get_width(pixbuf), gdk_pixbuf_get_height(pixbuf), width, height); |
518 | + resized_pixbuf = gdk_pixbuf_scale_simple(pixbuf, |
519 | + width, |
520 | + height, |
521 | + GDK_INTERP_BILINEAR); |
522 | + } else { |
523 | + g_debug("Happy with icon sized %dx%d", gdk_pixbuf_get_width(pixbuf), gdk_pixbuf_get_height(pixbuf)); |
524 | + resized_pixbuf = pixbuf; |
525 | + } |
526 | + gtk_image_set_from_pixbuf(GTK_IMAGE(mi_data->icon), resized_pixbuf); |
527 | + /* The other pixbuf should be free'd by the dbusmenu. */ |
528 | + if (resized_pixbuf != pixbuf) { |
529 | + g_object_unref(resized_pixbuf); |
530 | + } |
531 | + } |
532 | + } else { |
533 | + g_warning("Indicator Item property '%s' unknown", prop); |
534 | + } |
535 | + return; |
536 | +} |
537 | + |
538 | +/* We have a small little menuitem type that handles all |
539 | + of the fun stuff for indicators. Mostly this is the |
540 | + shifting over and putting the icon in with some right |
541 | + side text that'll be determined by the service. |
542 | + Copied verbatim from an old revision (including comments) of indicator-messages |
543 | +*/ |
544 | +static gboolean |
545 | +new_appointment_item (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client, gpointer user_data) |
546 | +{ |
547 | + g_return_val_if_fail(DBUSMENU_IS_MENUITEM(newitem), FALSE); |
548 | + g_return_val_if_fail(DBUSMENU_IS_GTKCLIENT(client), FALSE); |
549 | + /* Note: not checking parent, it's reasonable for it to be NULL */ |
550 | + |
551 | + indicator_item_t * mi_data = g_new0(indicator_item_t, 1); |
552 | + |
553 | + GtkMenuItem * gmi = GTK_MENU_ITEM(gtk_menu_item_new()); |
554 | + |
555 | + GtkWidget * hbox = gtk_hbox_new(FALSE, 4); |
556 | + |
557 | + /* Icon, probably someone's face or avatar on an IM */ |
558 | + mi_data->icon = gtk_image_new(); |
559 | + GdkPixbuf * pixbuf = dbusmenu_menuitem_property_get_image(newitem, APPOINTMENT_MENUITEM_PROP_ICON); |
560 | + |
561 | + if (pixbuf != NULL) { |
562 | + /* If we've got a pixbuf we need to make sure it's of a reasonable |
563 | + size to fit in the menu. If not, rescale it. */ |
564 | + GdkPixbuf * resized_pixbuf; |
565 | + gint width, height; |
566 | + gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height); |
567 | + if (gdk_pixbuf_get_width(pixbuf) > width || |
568 | + gdk_pixbuf_get_height(pixbuf) > height) { |
569 | + g_debug("Resizing icon from %dx%d to %dx%d", gdk_pixbuf_get_width(pixbuf), gdk_pixbuf_get_height(pixbuf), width, height); |
570 | + resized_pixbuf = gdk_pixbuf_scale_simple(pixbuf, |
571 | + width, |
572 | + height, |
573 | + GDK_INTERP_BILINEAR); |
574 | + } else { |
575 | + g_debug("Happy with icon sized %dx%d", gdk_pixbuf_get_width(pixbuf), gdk_pixbuf_get_height(pixbuf)); |
576 | + resized_pixbuf = pixbuf; |
577 | + } |
578 | + |
579 | + gtk_image_set_from_pixbuf(GTK_IMAGE(mi_data->icon), resized_pixbuf); |
580 | + |
581 | + /* The other pixbuf should be free'd by the dbusmenu. */ |
582 | + if (resized_pixbuf != pixbuf) { |
583 | + g_object_unref(resized_pixbuf); |
584 | + } |
585 | + } |
586 | + gtk_misc_set_alignment(GTK_MISC(mi_data->icon), 0.0, 0.5); |
587 | + gtk_box_pack_start(GTK_BOX(hbox), mi_data->icon, FALSE, FALSE, 0); |
588 | + gtk_widget_show(mi_data->icon); |
589 | + |
590 | + /* Label, probably a username, chat room or mailbox name */ |
591 | + mi_data->label = gtk_label_new(dbusmenu_menuitem_property_get(newitem, APPOINTMENT_MENUITEM_PROP_LABEL)); |
592 | + gtk_misc_set_alignment(GTK_MISC(mi_data->label), 0.0, 0.5); |
593 | + gtk_box_pack_start(GTK_BOX(hbox), mi_data->label, TRUE, TRUE, 0); |
594 | + gtk_widget_show(mi_data->label); |
595 | + |
596 | + /* Usually either the time or the count on the individual |
597 | + item. */ |
598 | + mi_data->right = gtk_label_new(dbusmenu_menuitem_property_get(newitem, APPOINTMENT_MENUITEM_PROP_RIGHT)); |
599 | + gtk_size_group_add_widget(indicator_right_group, mi_data->right); |
600 | + gtk_misc_set_alignment(GTK_MISC(mi_data->right), 1.0, 0.5); |
601 | + gtk_box_pack_start(GTK_BOX(hbox), mi_data->right, FALSE, FALSE, 0); |
602 | + gtk_widget_show(mi_data->right); |
603 | + |
604 | + gtk_container_add(GTK_CONTAINER(gmi), hbox); |
605 | + gtk_widget_show(hbox); |
606 | + |
607 | + dbusmenu_gtkclient_newitem_base(DBUSMENU_GTKCLIENT(client), newitem, gmi, parent); |
608 | + |
609 | + g_signal_connect(G_OBJECT(newitem), DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED, G_CALLBACK(indicator_prop_change_cb), mi_data); |
610 | + g_signal_connect_swapped(G_OBJECT(newitem), "destroyed", G_CALLBACK(g_free), mi_data); |
611 | + |
612 | + return TRUE; |
613 | +} |
614 | + |
615 | + |
616 | static gboolean |
617 | new_calendar_item (DbusmenuMenuitem * newitem, |
618 | DbusmenuMenuitem * parent, |
619 | @@ -1082,6 +1217,58 @@ |
620 | return TRUE; |
621 | } |
622 | |
623 | +static gboolean |
624 | +new_timezone_item(DbusmenuMenuitem * newitem, |
625 | + DbusmenuMenuitem * parent, |
626 | + DbusmenuClient * client, |
627 | + gpointer user_data) |
628 | +{ |
629 | + g_return_val_if_fail(DBUSMENU_IS_MENUITEM(newitem), FALSE); |
630 | + g_return_val_if_fail(DBUSMENU_IS_GTKCLIENT(client), FALSE); |
631 | + /* Note: not checking parent, it's reasonable for it to be NULL */ |
632 | + |
633 | + // Menu item with a radio button and a right aligned time |
634 | + indicator_item_t * mi_data = g_new0(indicator_item_t, 1); |
635 | + |
636 | + GtkMenuItem * gmi = GTK_MENU_ITEM(gtk_menu_item_new()); |
637 | + |
638 | + GtkWidget * hbox = gtk_hbox_new(FALSE, 4); |
639 | + |
640 | + mi_data->radio = gtk_radio_button_new(location_group); |
641 | + if (location_group == NULL) |
642 | + location_group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(mi_data->radio)); |
643 | + |
644 | + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(mi_data->radio), |
645 | + dbusmenu_menuitem_property_get_bool(newitem, TIMEZONE_MENUITEM_PROP_RADIO)); |
646 | + |
647 | + gtk_box_pack_start(GTK_BOX(hbox), mi_data->radio, FALSE, FALSE, 0); |
648 | + gtk_widget_show(mi_data->radio); |
649 | + |
650 | + /* Label, probably a username, chat room or mailbox name */ |
651 | + mi_data->label = gtk_label_new(dbusmenu_menuitem_property_get(newitem, APPOINTMENT_MENUITEM_PROP_LABEL)); |
652 | + gtk_misc_set_alignment(GTK_MISC(mi_data->label), 0.0, 0.5); |
653 | + gtk_box_pack_start(GTK_BOX(hbox), mi_data->label, TRUE, TRUE, 0); |
654 | + gtk_widget_show(mi_data->label); |
655 | + |
656 | + /* Usually either the time or the count on the individual |
657 | + item. */ |
658 | + mi_data->right = gtk_label_new(dbusmenu_menuitem_property_get(newitem, APPOINTMENT_MENUITEM_PROP_RIGHT)); |
659 | + gtk_size_group_add_widget(indicator_right_group, mi_data->right); |
660 | + gtk_misc_set_alignment(GTK_MISC(mi_data->right), 1.0, 0.5); |
661 | + gtk_box_pack_start(GTK_BOX(hbox), mi_data->right, FALSE, FALSE, 0); |
662 | + gtk_widget_show(mi_data->right); |
663 | + |
664 | + gtk_container_add(GTK_CONTAINER(gmi), hbox); |
665 | + gtk_widget_show(hbox); |
666 | + |
667 | + dbusmenu_gtkclient_newitem_base(DBUSMENU_GTKCLIENT(client), newitem, gmi, parent); |
668 | + |
669 | + g_signal_connect(G_OBJECT(newitem), DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED, G_CALLBACK(indicator_prop_change_cb), mi_data); |
670 | + g_signal_connect_swapped(G_OBJECT(newitem), "destroyed", G_CALLBACK(g_free), mi_data); |
671 | + |
672 | + return TRUE; |
673 | +} |
674 | + |
675 | /* Grabs the label. Creates it if it doesn't |
676 | exist already */ |
677 | static GtkLabel * |
678 | @@ -1121,6 +1308,8 @@ |
679 | g_object_set_data (G_OBJECT (client), "indicator", io); |
680 | |
681 | dbusmenu_client_add_type_handler(DBUSMENU_CLIENT(client), DBUSMENU_CALENDAR_MENUITEM_TYPE, new_calendar_item); |
682 | + dbusmenu_client_add_type_handler(DBUSMENU_CLIENT(client), APPOINTMENT_MENUITEM_TYPE, new_appointment_item); |
683 | + dbusmenu_client_add_type_handler(DBUSMENU_CLIENT(client), TIMEZONE_MENUITEM_TYPE, new_timezone_item); |
684 | |
685 | return GTK_MENU(self->priv->menu); |
686 | } |
Added mterry for review: this code exhibits bugs you said I should *expect* when deleting menu items. So please use it if it helps, and let me know if you can see how to prevent the bug appearing.
The bug puts a pair of characters into each menu item label field. It may be adding a new item with some uninitialised data in it, or it may be overwriting one of the old ones - only one item per refresh.
bug #713041 covers this problem.