Merge lp:~karl-qdh/ubuntu/natty/indicator-datetime/indicator-datetime.withappointments into lp:indicator-datetime/0.3

Proposed by Karl Lattimer
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
Reviewer Review Type Date Requested Status
Michael Terry Pending
Indicator Applet Developers Pending
Review via email: mp+48607@code.launchpad.net

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.

To post a comment you must log in.
Revision history for this message
Karl Lattimer (karl-qdh) wrote :

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.

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

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

Subscribers

People subscribed via source and target branches