diff options
-rw-r--r-- | configure.ac | 18 | ||||
-rw-r--r-- | src/datetime-service.c | 219 | ||||
-rw-r--r-- | src/dbus-shared.h | 4 | ||||
-rw-r--r-- | src/indicator-datetime.c | 143 |
4 files changed, 377 insertions, 7 deletions
diff --git a/configure.ac b/configure.ac index a57d91e..bcfd968 100644 --- a/configure.ac +++ b/configure.ac @@ -59,18 +59,24 @@ GIO_REQUIRED_VERSION=2.25.11 INDICATOR_DISPLAY_OBJECTS=0.1.10 GEOCLUE_REQUIRED_VERSION=0.12.0 OOBS_REQUIRED_VERSION=2.31.0 +ECAL_REQUIRED_VERSION=2.30 +ICAL_REQUIRED_VERSION=0.44 AS_IF([test "x$with_gtk" = x3], [PKG_CHECK_MODULES(INDICATOR, indicator3 >= $INDICATOR_REQUIRED_VERSION - dbusmenu-glib-0.4 >= $DBUSMENUGLIB_REQUIRED_VERSION - dbusmenu-gtk3-0.4 >= $DBUSMENUGTK_REQUIRED_VERSION - libido3-0.1 >= $INDICATOR_DISPLAY_OBJECTS) + dbusmenu-glib >= $DBUSMENUGLIB_REQUIRED_VERSION + dbusmenu-gtk3 >= $DBUSMENUGTK_REQUIRED_VERSION + libido3-0.1 >= $INDICATOR_DISPLAY_OBJECTS + libecal >= $ECAL_REQUIRED_VERSION + libical >= $ICAL_REQUIRED_VERSION) ], [test "x$with_gtk" = x2], [PKG_CHECK_MODULES(INDICATOR, indicator >= $INDICATOR_REQUIRED_VERSION - dbusmenu-glib-0.4 >= $DBUSMENUGLIB_REQUIRED_VERSION - dbusmenu-gtk-0.4 >= $DBUSMENUGTK_REQUIRED_VERSION - libido-0.1 >= $INDICATOR_DISPLAY_OBJECTS) + dbusmenu-glib >= $DBUSMENUGLIB_REQUIRED_VERSION + dbusmenu-gtk >= $DBUSMENUGTK_REQUIRED_VERSION + libido-0.1 >= $INDICATOR_DISPLAY_OBJECTS + libecal >= $ECAL_REQUIRED_VERSION + libical >= $ICAL_REQUIRED_VERSION) ], [AC_MSG_FAILURE([Value for --with-gtk was neither 2 nor 3])] ) diff --git a/src/datetime-service.c b/src/datetime-service.c index 8a31940..e06b76d 100644 --- a/src/datetime-service.c +++ b/src/datetime-service.c @@ -33,6 +33,17 @@ with this program. If not, see <http://www.gnu.org/licenses/>. #include <geoclue/geoclue-master.h> #include <geoclue/geoclue-master-client.h> +#include <time.h> +#include <libecal/e-cal.h> +#include <libical/ical.h> +#include <e-util/e-time-utils.h> +#include <libecal/e-cal-time-util.h> +#include <libedataserver/e-source.h> +// Other users of ecal seem to also include these, not sure why they should be included by the above +#include <libecal/e-cal-time-util.h> +#include <libical/icaltime.h> + + #include <oobs/oobs-timeconfig.h> #include "datetime-interface.h" @@ -53,6 +64,9 @@ static DbusmenuMenuitem * date = NULL; static DbusmenuMenuitem * calendar = NULL; static DbusmenuMenuitem * settings = NULL; static DbusmenuMenuitem * tzchange = NULL; +static GList * appointments = NULL; +static ECal * ecal = NULL; +static gchar * ecal_timezone = NULL; /* Geoclue trackers */ static GeoclueMasterClient * geo_master = NULL; @@ -225,6 +239,188 @@ check_for_calendar (gpointer user_data) return FALSE; } +static gboolean +timezone_clicked_cb() { + + return FALSE; +} + +static gboolean +update_timezone_menu_items(gpointer user_data) { + + return FALSE; +} + +/* Populate the menu with todays, next 5 appointments. + * we should hook into the ABOUT TO SHOW signal and use that to update the appointments. + * Experience has shown that caldav's and webcals can be slow to load from eds + * this is a problem mainly on the EDS side of things, not ours. + */ +static gboolean +update_appointment_menu_items (gpointer user_data) { + // FFR: we should take into account short term timers, for instance + // tea timers, pomodoro timers etc... that people may add, this is hinted to in the spec. + time_t t1, t2; + gchar *query, *is, *ie; + GList *objects = NULL, *l; + GError *gerror = NULL; + DbusmenuMenuitem * item = NULL; + gint i; + gint width, height; + + ecal = e_cal_new_system_calendar(); + + if (!ecal) { + g_debug("e_cal_new_system_calendar failed"); + ecal = NULL; + return FALSE; + } + if (!e_cal_open(ecal, FALSE, &gerror) ) { + g_debug("e_cal_open: %s\n", gerror->message); + g_free(ecal); + ecal = NULL; + return FALSE; + } + + if (!e_cal_get_timezone(ecal, "UTC", &tzone, &gerror) { + g_debug("failed to get time zone\n"); + g_free(ecal); + ecal = NULL; + return FALSE; + } + + /* This timezone represents the timezone of the calendar, this might be different to the current UTC offset. + * this means we'll have some geoclue interaction going on, and possibly the user will be involved in setting + * their location manually, case in point: trains have satellite links which often geoclue to sweden, + * this shouldn't automatically set the location and mess up all the appointments for the user. + */ + ecal_timezone = icaltimezone_get_tzid(tzone); + + time(&t1); + time(&t2); + t2 += (time_t) (7 * 24 * 60 * 60); /* 7 days ahead of now */ + + is = isodate_from_time_t(t1); + ie = isodate_from_time_t(t2); + // FIXME can we put a limit on the number of results? Or if not complete, or is event/todo? + query = g_strdup_printf("(occur-in-time-range? (make-time\"%s\") (make-time\"%s\"))", is, ie); + + if (!e_cal_get_object_list_as_comp(ecal, query, &objects, &gerror);) { + g_debug("Failed to get objects\n"); + g_free(ecal); + ecal = NULL; + return FALSE; + } + gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height); + + if (appointments != NULL) { + for (l = appointments; l; l = l->next) { + item = l->data; + // TODO: Remove all the existing menu items which are in appointments. + + // pop it out of the dbus menu + // remove it from the appointments glist + // free it + } + appointments = NULL; + } + + i = 0; + for (l = objects; l; l = l->next) { + ECalComponent *ecalcomp = l->data; + ECalComponentText valuetext; + ECalComponentDateTime datetime; + icaltimezone *appointment_zone; + time_t appointment_time, ti; + icalproperty_status status; + gchar *summary, right[20], *cmd; + const gchar *uri; + struct tm *tmp_tm, *tm_today; + + vtype = e_cal_component_get_vtype (comp); + + // See above FIXME regarding query result + // If it's not an event or todo, continue no-increment + if (vtype != E_CAL_COMPONENT_EVENT || vtype != E_CAL_COMPONENT_TODO) + continue; + + // See above FIXME regarding query result + // if the status is completed, continue no-increment + e_cal_component_get_status (ecalcomp, &status); + if (status == ICAL_STATUS_COMPLETED || status == ICAL_STATUS_CANCELLED) + continue; + + // INPROGRESS: Create a menu item for each of them, try to include helpful metadata e.g. colours, due time + item = dbusmenu_menuitem_new(); + dbusmenu_menuitem_property_set (item, "type", INDICATOR_MENUITEM_TYPE); + dbusmenu_menuitem_property_set_bool (item, DBUSMENU_MENUITEM_PROP_ENABLED, TRUE); + dbusmenu_menuitem_property_set_bool (item, DBUSMENU_MENUITEM_PROP_VISIBLE, TRUE); + + // Label text + e_cal_component_get_summary (ecalcomp, &valuetext); + summary = g_strdup (valuetext.value); + e_cal_component_free_text (&valuetext); + dbusmenu_menuitem_property_set (item, APPOINTMENT_MENUITEM_PROP_LABEL, summary); + g_free (summary); + + // Due text + if (vtype == E_CAL_COMPONENT_EVENT) + e_cal_component_get_dtstart (ecalcomp, &datetime); + else + e_cal_component_get_due (ecalcomp, &datetime); + + if (datetime.value) { + appointment_zone = get_zone_from_tzid (client, datetime.tzid); + if (!appointment_zone || datetime.value->is_date) + appointment_zone = ecal_timezone; + appointment_time = icaltime_as_timet_with_zone (*datetime.value, appointment_zone); + } + e_cal_component_free_datetime (&datetime); + tmp_tm = convert_timet_to_struct_tm (appointment_time, appointment_zone); + + // Get today + time(&ti); + tm_today = convert_timet_to_struct_tm (ti, appointment_zone); + if (tm_today->mday == tmp_tm->mday && tm_today->mon == tmp_tm->mon && tm_today->year == tmp_tm->year) + strftime(&right, sizeof(right), "%X", tmp_tm); + else + strftime(&right, sizeof(right), "%a %X", tmp_tm); + dbusmenu_menuitem_property_set (item, APPOINTMENT_MENUITEM_PROP_RIGHT, &right); + + // Now we pull out the URI for the calendar event and try to create a URI that'll work when we execute evolution + e_cal_component_get_url(ecalcomp, &uri); + cmd = g_strconcat("evolution ", uri, NULL); + g_signal_connect (G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, + G_CALLBACK (activate_cb), cmd); + + // Get the colour E_CAL_COMPONENT_FIELD_COLOR + // Get the icon, either EVENT or MEMO or TODO? + gdouble red, blue, green; + ECalSource *source = e_cal_get_source (ecalcomp->client); + if (!ecalcomp->color && e_source_get_color (source, &source_color)) { + g_free (comp_data->color); + ecalcomp->color = g_strdup_printf ("#%06x", source_color & 0xffffff); + } + + //cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); + //cairo_t *cr = cairo_create(cs); + + // TODO: Draw the correct icon for the appointment type and then tint it using mask fill. + + //GdkPixbuf * pixbuf = gdk_pixbuf_get_from_drawable(NULL, (GdkDrawable*)cs, 0,0,0,0, width, height); + + //dbusmenu_menuitem_property_set_image (item, APPOINTMENT_MENUITEM_PROP_ICON, pixbuf); + + dbusmenu_menuitem_child_append (root, item); + appointments = g_list_append(appointments, item); // Keep track of the items here to make them east to remove + + if (i == 4) break; // See above FIXME regarding query result limit + i++; + } + g_free(ecal); // Really we should do the setup somewhere where we know it'll only run once, right now, we'll do it every time and free it. + return TRUE; +} + /* Looks for the time and date admin application and enables the item we have one */ static gboolean @@ -272,8 +468,29 @@ build_menus (DbusmenuMenuitem * root) g_idle_add(check_for_calendar, NULL); } + DbusmenuMenuitem * separator; + + separator = dbusmenu_menuitem_new(); + dbusmenu_menuitem_property_set(separator, DBUSMENU_MENUITEM_PROP_TYPE, DBUSMENU_CLIENT_TYPES_SEPARATOR); + dbusmenu_menuitem_child_append(root, separator); - DbusmenuMenuitem * separator = dbusmenu_menuitem_new(); + // This just populates the items on startup later we want to be able to update the appointments before + // presenting the menu. + update_appointment_menu_items(NULL); + if (calendar != NULL) { + // TODO Create "Add appointment" menu item + } + // TODO Create FFR? "Add timer" menu item + + separator = dbusmenu_menuitem_new(); + dbusmenu_menuitem_property_set(separator, DBUSMENU_MENUITEM_PROP_TYPE, DBUSMENU_CLIENT_TYPES_SEPARATOR); + dbusmenu_menuitem_child_append(root, separator); + + update_timezone_menu_items(NULL); + // TODO Create "detected location" menu item? (defaults to the geoclue location) + // TODO Create "Add location" menu item + + separator = dbusmenu_menuitem_new(); dbusmenu_menuitem_property_set(separator, DBUSMENU_MENUITEM_PROP_TYPE, DBUSMENU_CLIENT_TYPES_SEPARATOR); dbusmenu_menuitem_child_append(root, separator); diff --git a/src/dbus-shared.h b/src/dbus-shared.h index 357beb5..4ecd64b 100644 --- a/src/dbus-shared.h +++ b/src/dbus-shared.h @@ -29,3 +29,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>. #define DBUSMENU_CALENDAR_MENUITEM_TYPE "x-canonical-calendar-item" +#define APPOINTMENT_MENUITEM_TYPE "indicator-item" +#define APPOINTMENT_MENUITEM_PROP_LABEL "indicator-label" +#define APPOINTMENT_MENUITEM_PROP_ICON "indicator-icon" +#define APPOINTMENT_MENUITEM_PROP_RIGHT "right-side-text" diff --git a/src/indicator-datetime.c b/src/indicator-datetime.c index c3b7e4a..5d92296 100644 --- a/src/indicator-datetime.c +++ b/src/indicator-datetime.c @@ -41,6 +41,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>. /* DBusMenu */ #include <libdbusmenu-gtk/menu.h> #include <libido/idocalendarmenuitem.h> +#include <libdbusmenu-gtk/menuitem.h> #include "dbus-shared.h" @@ -102,6 +103,13 @@ enum { PROP_CUSTOM_TIME_FORMAT }; +typedef struct _indicator_item_t indicator_item_t; +struct _indicator_item_t { + GtkWidget * icon; + GtkWidget * label; + GtkWidget * right; +}; + #define PROP_TIME_FORMAT_S "time-format" #define PROP_SHOW_SECONDS_S "show-seconds" #define PROP_SHOW_DAY_S "show-day" @@ -174,6 +182,8 @@ INDICATOR_SET_TYPE(INDICATOR_DATETIME_TYPE) G_DEFINE_TYPE (IndicatorDatetime, indicator_datetime, INDICATOR_OBJECT_TYPE); +static GtkSizeGroup * indicator_right_group = NULL; + static void indicator_datetime_class_init (IndicatorDatetimeClass *klass) { @@ -1055,6 +1065,128 @@ generate_format_string (IndicatorDatetime * self) return g_strdup_printf(T_("%s, %s"), date_string, time_string); } +/* Whenever we have a property change on a DbusmenuMenuitem + we need to be responsive to that. */ +static void +indicator_prop_change_cb (DbusmenuMenuitem * mi, gchar * prop, gchar * value, indicator_item_t * mi_data) +{ + if (!g_strcmp0(prop, APPOINTMENT_MENUITEM_PROP_LABEL)) { + /* Set the main label */ + gtk_label_set_text(GTK_LABEL(mi_data->label), value); + } else if (!g_strcmp0(prop, APPOINTMENT_MENUITEM_PROP_RIGHT)) { + /* Set the right label */ + gtk_label_set_text(GTK_LABEL(mi_data->right), value); + } else if (!g_strcmp0(prop, APPOINTMENT_MENUITEM_PROP_ICON)) { + /* We don't use the value here, which is probably less efficient, + but it's easier to use the easy function. And since th value + is already cached, shouldn't be a big deal really. */ + GdkPixbuf * pixbuf = dbusmenu_menuitem_property_get_image(mi, APPOINTMENT_MENUITEM_PROP_ICON); + if (pixbuf != NULL) { + /* If we've got a pixbuf we need to make sure it's of a reasonable + size to fit in the menu. If not, rescale it. */ + GdkPixbuf * resized_pixbuf; + gint width, height; + gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height); + if (gdk_pixbuf_get_width(pixbuf) > width || + gdk_pixbuf_get_height(pixbuf) > height) { + g_debug("Resizing icon from %dx%d to %dx%d", gdk_pixbuf_get_width(pixbuf), gdk_pixbuf_get_height(pixbuf), width, height); + resized_pixbuf = gdk_pixbuf_scale_simple(pixbuf, + width, + height, + GDK_INTERP_BILINEAR); + } else { + g_debug("Happy with icon sized %dx%d", gdk_pixbuf_get_width(pixbuf), gdk_pixbuf_get_height(pixbuf)); + resized_pixbuf = pixbuf; + } + gtk_image_set_from_pixbuf(GTK_IMAGE(mi_data->icon), resized_pixbuf); + /* The other pixbuf should be free'd by the dbusmenu. */ + if (resized_pixbuf != pixbuf) { + g_object_unref(resized_pixbuf); + } + } + } else { + g_warning("Indicator Item property '%s' unknown", prop); + } + return; +} + +/* We have a small little menuitem type that handles all + of the fun stuff for indicators. Mostly this is the + shifting over and putting the icon in with some right + side text that'll be determined by the service. + Copied verbatim from an old revision (including comments) of indicator-messages +*/ +static gboolean +new_appointment_item (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client) +{ + g_return_val_if_fail(DBUSMENU_IS_MENUITEM(newitem), FALSE); + g_return_val_if_fail(DBUSMENU_IS_GTKCLIENT(client), FALSE); + /* Note: not checking parent, it's reasonable for it to be NULL */ + + indicator_item_t * mi_data = g_new0(indicator_item_t, 1); + + GtkMenuItem * gmi = GTK_MENU_ITEM(gtk_menu_item_new()); + + GtkWidget * hbox = gtk_hbox_new(FALSE, 4); + + /* Icon, probably someone's face or avatar on an IM */ + mi_data->icon = gtk_image_new(); + GdkPixbuf * pixbuf = dbusmenu_menuitem_property_get_image(newitem, APPOINTMENT_MENUITEM_PROP_ICON); + + if (pixbuf != NULL) { + /* If we've got a pixbuf we need to make sure it's of a reasonable + size to fit in the menu. If not, rescale it. */ + GdkPixbuf * resized_pixbuf; + gint width, height; + gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height); + if (gdk_pixbuf_get_width(pixbuf) > width || + gdk_pixbuf_get_height(pixbuf) > height) { + g_debug("Resizing icon from %dx%d to %dx%d", gdk_pixbuf_get_width(pixbuf), gdk_pixbuf_get_height(pixbuf), width, height); + resized_pixbuf = gdk_pixbuf_scale_simple(pixbuf, + width, + height, + GDK_INTERP_BILINEAR); + } else { + g_debug("Happy with icon sized %dx%d", gdk_pixbuf_get_width(pixbuf), gdk_pixbuf_get_height(pixbuf)); + resized_pixbuf = pixbuf; + } + + gtk_image_set_from_pixbuf(GTK_IMAGE(mi_data->icon), resized_pixbuf); + + /* The other pixbuf should be free'd by the dbusmenu. */ + if (resized_pixbuf != pixbuf) { + g_object_unref(resized_pixbuf); + } + } + gtk_misc_set_alignment(GTK_MISC(mi_data->icon), 0.0, 0.5); + gtk_box_pack_start(GTK_BOX(hbox), mi_data->icon, FALSE, FALSE, 0); + gtk_widget_show(mi_data->icon); + + /* Label, probably a username, chat room or mailbox name */ + mi_data->label = gtk_label_new(dbusmenu_menuitem_property_get(newitem, APPOINTMENT_MENUITEM_PROP_LABEL)); + gtk_misc_set_alignment(GTK_MISC(mi_data->label), 0.0, 0.5); + gtk_box_pack_start(GTK_BOX(hbox), mi_data->label, TRUE, TRUE, 0); + gtk_widget_show(mi_data->label); + + /* Usually either the time or the count on the individual + item. */ + mi_data->right = gtk_label_new(dbusmenu_menuitem_property_get(newitem, APPOINTMENT_MENUITEM_PROP_RIGHT)); + gtk_size_group_add_widget(indicator_right_group, mi_data->right); + gtk_misc_set_alignment(GTK_MISC(mi_data->right), 1.0, 0.5); + gtk_box_pack_start(GTK_BOX(hbox), mi_data->right, FALSE, FALSE, 0); + gtk_widget_show(mi_data->right); + + gtk_container_add(GTK_CONTAINER(gmi), hbox); + gtk_widget_show(hbox); + + dbusmenu_gtkclient_newitem_base(DBUSMENU_GTKCLIENT(client), newitem, gmi, parent); + + g_signal_connect(G_OBJECT(newitem), DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED, G_CALLBACK(indicator_prop_change_cb), mi_data); + g_signal_connect(G_OBJECT(newitem), "destroyed", G_CALLBACK(g_free), mi_data); + + return TRUE; +} + static gboolean new_calendar_item (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, @@ -1081,6 +1213,16 @@ new_calendar_item (DbusmenuMenuitem * newitem, return TRUE; } +static gboolean +new_timezone_item(DbusmenuMenuitem * newitem, + DbusmenuMenuitem * parent, + DbusmenuClient * client) +{ + // Menu item with a radio button and a right aligned time + + return TRUE; +} + /* Grabs the label. Creates it if it doesn't exist already */ static GtkLabel * @@ -1120,6 +1262,7 @@ get_menu (IndicatorObject * io) g_object_set_data (G_OBJECT (client), "indicator", io); dbusmenu_client_add_type_handler(DBUSMENU_CLIENT(client), DBUSMENU_CALENDAR_MENUITEM_TYPE, new_calendar_item); + dbusmenu_client_add_type_handler(DBUSMENU_CLIENT(client), APPOINTMENT_MENUITEM_TYPE, new_appointment_item); return GTK_MENU(self->priv->menu); } |