? t.tmp Index: .cvsignore =================================================================== RCS file: /cvs/gnome/gnome-panel/applets/clock/.cvsignore,v retrieving revision 1.8 diff -u -p -r1.8 .cvsignore --- .cvsignore 26 Jan 2003 21:25:32 -0000 1.8 +++ .cvsignore 12 Jan 2004 17:15:54 -0000 @@ -7,3 +7,4 @@ Makefile GNOME_ClockApplet_Factory.server GNOME_ClockApplet_Factory.server.in clock.schemas +clock-applet Index: ChangeLog =================================================================== RCS file: /cvs/gnome/gnome-panel/applets/clock/ChangeLog,v retrieving revision 1.500 diff -u -p -r1.500 ChangeLog --- ChangeLog 12 Jan 2004 04:41:53 -0000 1.500 +++ ChangeLog 12 Jan 2004 17:15:55 -0000 @@ -1,3 +1,35 @@ +2004-01-12 Mark McLoughlin + + * GNOME_ClockApplet_Factory.server.in.in: make the applet + out of process. + + * Makefile.am: build the cut-n-paste subdir, make a binary + instead of an shlib, build the calendar bits. + + * clock.c: add tasks and appointments to the calendar popup. + + * calendar-client.[ch]: hide all the book-keeping behind + a nice API. + + * calendar-debug.h: debugging utils. + + * calendar-sources.[ch]: keep track of Evolution's selected + sources. + + * cut-n-paste/eel-pango-extensions.[ch]: copy eel's method + for creating a PangoLayout with text ellipsized at a given + width. + + * cut-n-paste/eggcellrenderertext.[ch]: copy GtkCellRendererText + and add ellipsizing support. + + * cut-n-paste/eggintl.h: i18n support. + + * cut-n-paste/eggmarshalers.list: marhsallers. + + * cut-n-paste/eggtreeprivate.h: copy bit from gtktreeprivate.h, + but they're not really used. + 2004-01-09 Kaushal Kumar * GNOME_ClockApplet_Factory.server.in.in: Added info for bug-reporting. Index: GNOME_ClockApplet_Factory.server.in.in =================================================================== RCS file: /cvs/gnome/gnome-panel/applets/clock/GNOME_ClockApplet_Factory.server.in.in,v retrieving revision 1.22 diff -u -p -r1.22 GNOME_ClockApplet_Factory.server.in.in --- GNOME_ClockApplet_Factory.server.in.in 12 Jan 2004 04:41:53 -0000 1.22 +++ GNOME_ClockApplet_Factory.server.in.in 12 Jan 2004 17:15:55 -0000 @@ -1,8 +1,8 @@ + type="exe" + location="@LIBEXECDIR@/clock-applet"> Index: Makefile.am =================================================================== RCS file: /cvs/gnome/gnome-panel/applets/clock/Makefile.am,v retrieving revision 1.69 diff -u -p -r1.69 Makefile.am --- Makefile.am 10 Dec 2003 17:54:54 -0000 1.69 +++ Makefile.am 12 Jan 2004 17:15:55 -0000 @@ -1,3 +1,5 @@ +SUBDIRS = cut-n-paste + INCLUDES = \ -I$(srcdir)/../../libpanel-applet \ -I$(top_builddir)/libpanel-applet \ @@ -11,18 +13,26 @@ INCLUDES = \ -DPREFIX=\""$(prefix)"\" \ -DGNOMELOCALEDIR=\""$(prefix)/$(DATADIRNAME)/locale"\" -libclock_applet_2_la_SOURCES = clock.c +if HAVE_LIBECAL +CALENDAR_SOURCES = \ + calendar-client.c \ + calendar-client.h \ + calendar-sources.c \ + calendar-sources.h \ + calendar-debug.h +endif + +clock_applet_SOURCES = clock.c $(CALENDAR_SOURCES) -libclock_applet_2_la_LDFLAGS = -module -avoid-version -no-undefined -libclock_applet_2_la_LIBADD = \ +clock_applet_LDADD = \ ../../libpanel-applet/libpanel-applet-2.la \ + cut-n-paste/libclockcnp.la \ $(CLOCK_LIBS) -appletdir = $(libdir) -applet_LTLIBRARIES = libclock-applet-2.la +libexec_PROGRAMS = clock-applet GNOME_ClockApplet_Factory.server.in: GNOME_ClockApplet_Factory.server.in.in - sed -e "s|\@APPLET_LIBDIR\@|$(appletdir)|" $< > $@ + sed -e "s|\@LIBEXECDIR\@|$(libexecdir)|" $< > $@ uidir = $(datadir)/gnome-2.0/ui ui_DATA = GNOME_ClockApplet.xml Index: calendar-client.c =================================================================== RCS file: calendar-client.c diff -N calendar-client.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ calendar-client.c 12 Jan 2004 17:15:55 -0000 @@ -0,0 +1,1802 @@ +/* + * Copyright (C) 2004 Free Software Foundation, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Authors: + * Mark McLoughlin + * William Jon McCann + * Martin Grimme + * Christian Kellner + */ + +#include + +#include "calendar-client.h" + +#include +#include +#include +#include +#include + +#include "calendar-sources.h" + +#undef CALENDAR_ENABLE_DEBUG +#include "calendar-debug.h" + +#ifndef _ +#define _(x) gettext(x) +#endif + +#ifndef N_ +#define N_(x) x +#endif + +#define CALENDAR_CLIENT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CALENDAR_TYPE_CLIENT, CalendarClientPrivate)) + +typedef struct _CalendarClientQuery CalendarClientQuery; +typedef struct _CalendarClientSource CalendarClientSource; + +struct _CalendarClientQuery +{ + ECalView *view; + GHashTable *events; +}; + +struct _CalendarClientSource +{ + CalendarClient *client; + ECal *source; + + CalendarClientQuery completed_query; + CalendarClientQuery in_progress_query; + + guint changed_signal_id; + + guint query_completed : 1; + guint query_in_progress : 1; +}; + +struct _CalendarClientPrivate +{ + CalendarSources *calendar_sources; + + GSList *appointment_sources; + GSList *task_sources; + + guint day; + guint month; + guint year; +}; + +static void calendar_client_class_init (CalendarClientClass *klass); +static void calendar_client_init (CalendarClient *client); +static void calendar_client_finalize (GObject *object); +static void calendar_client_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void calendar_client_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +static GSList *calendar_client_update_sources_list (CalendarClient *client, + GSList *sources, + GSList *esources, + guint changed_signal_id); +static void calendar_client_appointment_sources_changed (CalendarClient *client); +static void calendar_client_task_sources_changed (CalendarClient *client); + +static void calendar_client_stop_query (CalendarClient *client, + CalendarClientSource *source, + CalendarClientQuery *query); +static void calendar_client_start_query (CalendarClient *client, + CalendarClientSource *source, + const char *query); + +static void calendar_client_source_finalize (CalendarClientSource *source); +static void calendar_client_query_finalize (CalendarClientQuery *query); + +enum +{ + PROP_O, + PROP_DAY, + PROP_MONTH, + PROP_YEAR +}; + +enum +{ + APPOINTMENTS_CHANGED, + TASKS_CHANGED, + LAST_SIGNAL +}; + +static GObjectClass *parent_class = NULL; +static guint signals [LAST_SIGNAL] = { 0, }; + +GType +calendar_client_get_type (void) +{ + static GType client_type = 0; + + if (!client_type) + { + static const GTypeInfo client_info = + { + sizeof (CalendarClientClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) calendar_client_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (CalendarClient), + 0, /* n_preallocs */ + (GInstanceInitFunc) calendar_client_init, + }; + + client_type = g_type_register_static (G_TYPE_OBJECT, + "CalendarClient", + &client_info, 0); + } + + return client_type; +} + +static void +calendar_client_class_init (CalendarClientClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + parent_class = g_type_class_peek_parent (klass); + + gobject_class->finalize = calendar_client_finalize; + gobject_class->set_property = calendar_client_set_property; + gobject_class->get_property = calendar_client_get_property; + + g_type_class_add_private (klass, sizeof (CalendarClientPrivate)); + + g_object_class_install_property (gobject_class, + PROP_DAY, + g_param_spec_uint ("day", + _("Day"), + _("The currently monitored day between 1 and 31 (0 denotes unset)"), + 0, G_MAXUINT, 0, + G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, + PROP_MONTH, + g_param_spec_uint ("month", + _("Month"), + _("The currently monitored month between 0 and 11"), + 0, G_MAXUINT, 0, + G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, + PROP_YEAR, + g_param_spec_uint ("year", + _("Year"), + _("The currently monitored year"), + 0, G_MAXUINT, 0, + G_PARAM_READWRITE)); + + signals [APPOINTMENTS_CHANGED] = + g_signal_new ("appointments-changed", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (CalendarClientClass, tasks_changed), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + signals [TASKS_CHANGED] = + g_signal_new ("tasks-changed", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (CalendarClientClass, tasks_changed), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); +} + +static void +calendar_client_init (CalendarClient *client) +{ + GSList *esources; + + client->priv = CALENDAR_CLIENT_GET_PRIVATE (client); + + client->priv->calendar_sources = calendar_sources_get (); + + esources = calendar_sources_get_appointment_sources (client->priv->calendar_sources); + client->priv->appointment_sources = + calendar_client_update_sources_list (client, NULL, esources, signals [APPOINTMENTS_CHANGED]); + + esources = calendar_sources_get_task_sources (client->priv->calendar_sources); + client->priv->task_sources = + calendar_client_update_sources_list (client, NULL, esources, signals [TASKS_CHANGED]); + + g_signal_connect_swapped (client->priv->calendar_sources, + "appointment-sources-changed", + G_CALLBACK (calendar_client_appointment_sources_changed), + client); + g_signal_connect_swapped (client->priv->calendar_sources, + "task-sources-changed", + G_CALLBACK (calendar_client_task_sources_changed), + client); + + client->priv->day = -1; + client->priv->month = -1; + client->priv->year = -1; +} + +static void +calendar_client_finalize (GObject *object) +{ + CalendarClient *client = CALENDAR_CLIENT (object); + GSList *l; + + for (l = client->priv->appointment_sources; l; l = l->next) + { + calendar_client_source_finalize (l->data); + g_free (l->data); + } + g_slist_free (client->priv->appointment_sources); + client->priv->appointment_sources = NULL; + + for (l = client->priv->task_sources; l; l = l->next) + { + calendar_client_source_finalize (l->data); + g_free (l->data); + } + g_slist_free (client->priv->task_sources); + client->priv->task_sources = NULL; + + if (client->priv->calendar_sources) + g_object_unref (client->priv->calendar_sources); + client->priv->calendar_sources = NULL; + + if (G_OBJECT_CLASS (parent_class)->finalize) + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +calendar_client_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CalendarClient *client = CALENDAR_CLIENT (object); + + switch (prop_id) + { + case PROP_DAY: + calendar_client_select_day (client, g_value_get_uint (value)); + break; + case PROP_MONTH: + calendar_client_select_month (client, + g_value_get_uint (value), + client->priv->year); + break; + case PROP_YEAR: + calendar_client_select_month (client, + client->priv->month, + g_value_get_uint (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +calendar_client_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CalendarClient *client = CALENDAR_CLIENT (object); + + switch (prop_id) + { + case PROP_DAY: + g_value_set_uint (value, client->priv->day); + break; + case PROP_MONTH: + g_value_set_uint (value, client->priv->month); + break; + case PROP_YEAR: + g_value_set_uint (value, client->priv->year); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +CalendarClient * +calendar_client_new (void) +{ + return g_object_new (CALENDAR_TYPE_CLIENT, NULL); +} + +/* @day and @month can happily be out of range as + * mktime() will normalize them correctly. From mktime(3): + * + * "If structure members are outside their legal interval, + * they will be normalized (so that, e.g., 40 October is + * changed into 9 November)." + * + * "What?", you say, "Something useful in libc?" + */ +static inline GTime +make_time_for_day_begin (int day, + int month, + int year) +{ + struct tm localtime_tm = { 0, }; + + localtime_tm.tm_mday = day; + localtime_tm.tm_mon = month; + localtime_tm.tm_year = year - 1900; + localtime_tm.tm_isdst = -1; + + return mktime (&localtime_tm); +} + +static inline char * +make_isodate_for_day_begin (int day, + int month, + int year) +{ + GTime utctime; + + utctime = make_time_for_day_begin (day, month, year); + + return utctime != -1 ? isodate_from_time_t (utctime) : NULL; +} + +static GTime +get_time_from_property (icalcomponent *ical, + icalproperty_kind prop_kind, + struct icaltimetype (* get_prop_func) (const icalproperty *prop)) +{ + icalproperty *prop; + struct icaltimetype ical_time; + icalparameter *param; + icaltimezone *timezone = NULL; + + prop = icalcomponent_get_first_property (ical, prop_kind); + if (!prop) + return 0; + + ical_time = get_prop_func (prop); + + param = icalproperty_get_first_parameter (prop, ICAL_TZID_PARAMETER); + if (param) + { + timezone = icaltimezone_get_builtin_timezone_from_tzid (icalparameter_get_tzid (param)); + } + else if (icaltime_is_utc (ical_time)) + { + timezone = icaltimezone_get_utc_timezone (); + } + + return icaltime_as_timet_with_zone (ical_time, timezone); +} + +static char * +get_ical_uid (icalcomponent *ical) +{ + return g_strdup (icalcomponent_get_uid (ical)); +} + +static char * +get_ical_summary (icalcomponent *ical) +{ + icalproperty *prop; + + prop = icalcomponent_get_first_property (ical, ICAL_SUMMARY_PROPERTY); + if (!prop) + return NULL; + + return g_strdup (icalproperty_get_summary (prop)); +} + +static char * +get_ical_description (icalcomponent *ical) +{ + icalproperty *prop; + + prop = icalcomponent_get_first_property (ical, ICAL_DESCRIPTION_PROPERTY); + if (!prop) + return NULL; + + return g_strdup (icalproperty_get_description (prop)); +} + +static inline GTime +get_ical_start_time (icalcomponent *ical) +{ + return get_time_from_property (ical, + ICAL_DTSTART_PROPERTY, + icalproperty_get_dtstart); +} + +static inline GTime +get_ical_end_time (icalcomponent *ical) +{ + return get_time_from_property (ical, + ICAL_DTEND_PROPERTY, + icalproperty_get_dtend); +} + +static gboolean +get_ical_is_all_day (icalcomponent *ical, + GTime start_time) +{ + icalproperty *prop; + struct tm *start_tm; + GTime end_time; + struct icaldurationtype duration; + + start_tm = gmtime ((time_t *)&start_time); + if (start_tm->tm_sec != 0 || + start_tm->tm_min != 0 || + start_tm->tm_hour != 0) + return FALSE; + + if ((end_time = get_ical_end_time (ical))) + return (start_time - end_time) == 86400; + + prop = icalcomponent_get_first_property (ical, ICAL_DURATION_PROPERTY); + if (!prop) + return FALSE; + + duration = icalproperty_get_duration (prop); + + return icaldurationtype_as_int (duration) == 86400; +} + +static inline GTime +get_ical_due_time (icalcomponent *ical) +{ + return get_time_from_property (ical, + ICAL_DUE_PROPERTY, + icalproperty_get_due); +} +static guint +get_ical_percent_complete (icalcomponent *ical) +{ + icalproperty *prop; + int percent_complete; + + prop = icalcomponent_get_first_property (ical, ICAL_COMPLETED_PROPERTY); + if (prop) + return 100; + + prop = icalcomponent_get_first_property (ical, ICAL_PERCENTCOMPLETE_PROPERTY); + if (!prop) + return 0; + + percent_complete = icalproperty_get_percentcomplete (prop); + + return CLAMP (percent_complete, 0, 100); +} + +static inline GTime +get_ical_completed_time (icalcomponent *ical) +{ + return get_time_from_property (ical, + ICAL_COMPLETED_PROPERTY, + icalproperty_get_completed); +} + +static inline int +null_safe_strcmp (const char *a, + const char *b) +{ + return (!a && !b) ? 0 : (a && !b) || (!a && b) ? 1 : strcmp (a, b); +} + +static inline gboolean +calendar_appointment_equal (CalendarAppointment *a, + CalendarAppointment *b) +{ + GSList *la, *lb; + + if (g_slist_length (a->occurrences) != g_slist_length (b->occurrences)) + return FALSE; + + for (la = a->occurrences, lb = b->occurrences; la && lb; la = la->next, lb = lb->next) + { + CalendarOccurrence *oa = la->data; + CalendarOccurrence *ob = lb->data; + + if (oa->start_time != ob->start_time || + oa->end_time != ob->end_time) + return FALSE; + } + + return + null_safe_strcmp (a->uid, b->uid) == 0 && + null_safe_strcmp (a->summary, b->summary) == 0 && + null_safe_strcmp (a->description, b->description) == 0 && + a->start_time == b->start_time && + a->end_time == b->end_time && + a->is_all_day == b->is_all_day; +} + +static void +calendar_appointment_copy (CalendarAppointment *appointment, + CalendarAppointment *appointment_copy) +{ + GSList *l; + + g_assert (appointment != NULL); + g_assert (appointment_copy != NULL); + + appointment_copy->occurrences = g_slist_copy (appointment->occurrences); + for (l = appointment_copy->occurrences; l; l = l->next) + { + CalendarOccurrence *occurrence = l->data; + CalendarOccurrence *occurrence_copy; + + occurrence_copy = g_new0 (CalendarOccurrence, 1); + occurrence_copy->start_time = occurrence->start_time; + occurrence_copy->end_time = occurrence->end_time; + + l->data = occurrence_copy; + } + + appointment_copy->uid = g_strdup (appointment->uid); + appointment_copy->summary = g_strdup (appointment->summary); + appointment_copy->description = g_strdup (appointment->description); + appointment_copy->start_time = appointment->start_time; + appointment_copy->end_time = appointment->end_time; + appointment_copy->is_all_day = appointment->is_all_day; +} + +static void +calendar_appointment_finalize (CalendarAppointment *appointment) +{ + GSList *l; + + for (l = appointment->occurrences; l; l = l->next) + g_free (l->data); + g_slist_free (appointment->occurrences); + appointment->occurrences = NULL; + + g_free (appointment->uid); + appointment->uid = NULL; + + g_free (appointment->summary); + appointment->summary = NULL; + + g_free (appointment->description); + appointment->description = NULL; + + appointment->start_time = 0; + appointment->is_all_day = FALSE; +} + +static void +calendar_appointment_init (CalendarAppointment *appointment, + icalcomponent *ical) +{ + appointment->uid = get_ical_uid (ical); + appointment->summary = get_ical_summary (ical); + appointment->description = get_ical_description (ical); + appointment->start_time = get_ical_start_time (ical); + appointment->end_time = get_ical_end_time (ical); + appointment->is_all_day = get_ical_is_all_day (ical, + appointment->start_time); +} + +static icaltimezone * +resolve_timezone_id (const char *tzid, + ECal *source) +{ + icaltimezone *retval; + + retval = icaltimezone_get_builtin_timezone_from_tzid (tzid); + if (!retval) + { + e_cal_get_timezone (source, tzid, &retval, NULL); + } + + return retval; +} + +static gboolean +calendar_appointment_collect_occurrence (ECalComponent *component, + GTime occurrence_start, + GTime occurrence_end, + GSList **collect_loc) +{ + CalendarOccurrence *occurrence; + + occurrence = g_new0 (CalendarOccurrence, 1); + occurrence->start_time = occurrence_start; + occurrence->end_time = occurrence_end; + + *collect_loc = g_slist_prepend (*collect_loc, occurrence); + + return TRUE; +} + +static void +calendar_appointment_generate_ocurrences (CalendarAppointment *appointment, + icalcomponent *ical, + ECal *source, + GTime start, + GTime end) +{ + ECalComponent *ecal; + + g_assert (appointment->occurrences == NULL); + + ecal = e_cal_component_new (); + e_cal_component_set_icalcomponent (ecal, + icalcomponent_new_clone (ical)); + + e_cal_recur_generate_instances (ecal, + start, + end, + (ECalRecurInstanceFn) calendar_appointment_collect_occurrence, + &appointment->occurrences, + (ECalRecurResolveTimezoneFn) resolve_timezone_id, + source, + NULL); + + g_object_unref (ecal); + + appointment->occurrences = g_slist_reverse (appointment->occurrences); +} + +static inline gboolean +calendar_task_equal (CalendarTask *a, + CalendarTask *b) +{ + return + null_safe_strcmp (a->uid, b->uid) == 0 && + null_safe_strcmp (a->summary, b->summary) == 0 && + null_safe_strcmp (a->description, b->description) == 0 && + a->start_time == b->start_time && + a->due_time == b->due_time && + a->percent_complete == b->percent_complete && + a->completed_time == b->completed_time; +} + +static void +calendar_task_copy (CalendarTask *task, + CalendarTask *task_copy) +{ + g_assert (task != NULL); + g_assert (task_copy != NULL); + + task_copy->uid = g_strdup (task->uid); + task_copy->summary = g_strdup (task->summary); + task_copy->description = g_strdup (task->description); + task_copy->start_time = task->start_time; + task_copy->due_time = task->due_time; + task_copy->percent_complete = task->percent_complete; + task_copy->completed_time = task->completed_time; +} + +static void +calendar_task_finalize (CalendarTask *task) +{ + g_free (task->uid); + task->uid = NULL; + + g_free (task->summary); + task->summary = NULL; + + g_free (task->description); + task->description = NULL; + + task->percent_complete = 0; +} + +static void +calendar_task_init (CalendarTask *task, + icalcomponent *ical) +{ + task->uid = get_ical_uid (ical); + task->summary = get_ical_summary (ical); + task->description = get_ical_description (ical); + task->start_time = get_ical_start_time (ical); + task->due_time = get_ical_due_time (ical); + task->percent_complete = get_ical_percent_complete (ical); + task->completed_time = get_ical_completed_time (ical); +} + +void +calendar_event_free (CalendarEvent *event) +{ + switch (event->type) + { + case CALENDAR_EVENT_APPOINTMENT: + calendar_appointment_finalize (CALENDAR_APPOINTMENT (event)); + break; + case CALENDAR_EVENT_TASK: + calendar_task_finalize (CALENDAR_TASK (event)); + break; + default: + g_assert_not_reached (); + break; + } + + g_free (event); +} + +static CalendarEvent * +calendar_event_new (icalcomponent *ical) +{ + CalendarEvent *event; + + event = g_new0 (CalendarEvent, 1); + + switch (icalcomponent_isa (ical)) + { + case ICAL_VEVENT_COMPONENT: + event->type = CALENDAR_EVENT_APPOINTMENT; + calendar_appointment_init (CALENDAR_APPOINTMENT (event), ical); + break; + case ICAL_VTODO_COMPONENT: + event->type = CALENDAR_EVENT_TASK; + calendar_task_init (CALENDAR_TASK (event), ical); + break; + default: + g_warning ("Unknown calendar component type\n"); + g_free (event); + return NULL; + } + + return event; +} + +static CalendarEvent * +calendar_event_copy (CalendarEvent *event) +{ + CalendarEvent *retval; + + if (!event) + return NULL; + + retval = g_new0 (CalendarEvent, 1); + + retval->type = event->type; + + switch (event->type) + { + case CALENDAR_EVENT_APPOINTMENT: + calendar_appointment_copy (CALENDAR_APPOINTMENT (event), + CALENDAR_APPOINTMENT (retval)); + break; + case CALENDAR_EVENT_TASK: + calendar_task_copy (CALENDAR_TASK (event), + CALENDAR_TASK (retval)); + break; + default: + g_assert_not_reached (); + break; + } + + return retval; +} + +static const char * +calendar_event_get_uid (CalendarEvent *event) +{ + switch (event->type) + { + case CALENDAR_EVENT_APPOINTMENT: + return CALENDAR_APPOINTMENT (event)->uid; + break; + case CALENDAR_EVENT_TASK: + return CALENDAR_TASK (event)->uid; + break; + default: + g_assert_not_reached (); + break; + } + + return NULL; +} + +static gboolean +calendar_event_equal (CalendarEvent *a, + CalendarEvent *b) +{ + if (!a && !b) + return TRUE; + + if ((a && !b) || (!a && b)) + return FALSE; + + if (a->type != b->type) + return FALSE; + + switch (a->type) + { + case CALENDAR_EVENT_APPOINTMENT: + return calendar_appointment_equal (CALENDAR_APPOINTMENT (a), + CALENDAR_APPOINTMENT (b)); + case CALENDAR_EVENT_TASK: + return calendar_task_equal (CALENDAR_TASK (a), + CALENDAR_TASK (b)); + default: + break; + } + + g_assert_not_reached (); + + return FALSE; +} + +static void +calendar_event_generate_ocurrences (CalendarEvent *event, + icalcomponent *ical, + ECal *source, + GTime start, + GTime end) +{ + if (event->type != CALENDAR_EVENT_APPOINTMENT) + return; + + calendar_appointment_generate_ocurrences (CALENDAR_APPOINTMENT (event), + ical, + source, + start, + end); +} + +static inline void +calendar_event_debug_dump (CalendarEvent *event) +{ +#ifdef CALENDAR_ENABLE_DEBUG + switch (event->type) + { + case CALENDAR_EVENT_APPOINTMENT: + { + char *start_str; + char *end_str; + GSList *l; + + start_str = CALENDAR_APPOINTMENT (event)->start_time ? + isodate_from_time_t (CALENDAR_APPOINTMENT (event)->start_time) : + g_strdup ("(undefined)"); + end_str = CALENDAR_APPOINTMENT (event)->end_time ? + isodate_from_time_t (CALENDAR_APPOINTMENT (event)->end_time) : + g_strdup ("(undefined)"); + + dprintf ("Appointment: uid '%s', summary '%s', description '%s', " + "start_time '%s', end_time '%s', is_all_day %s\n", + CALENDAR_APPOINTMENT (event)->uid, + CALENDAR_APPOINTMENT (event)->summary, + CALENDAR_APPOINTMENT (event)->description, + start_str, + end_str, + CALENDAR_APPOINTMENT (event)->is_all_day ? "(true)" : "(false)"); + + g_free (start_str); + g_free (end_str); + + dprintf (" Occurrences:\n"); + for (l = CALENDAR_APPOINTMENT (event)->occurrences; l; l = l->next) + { + CalendarOccurrence *occurrence = l->data; + + start_str = occurrence->start_time ? + isodate_from_time_t (occurrence->start_time) : + g_strdup ("(undefined)"); + + end_str = occurrence->end_time ? + isodate_from_time_t (occurrence->end_time) : + g_strdup ("(undefined)"); + + dprintf (" start_time '%s', end_time '%s'\n", + start_str, end_str); + + g_free (start_str); + g_free (end_str); + } + } + break; + case CALENDAR_EVENT_TASK: + { + char *start_str; + char *due_str; + char *completed_str; + + start_str = CALENDAR_TASK (event)->start_time ? + isodate_from_time_t (CALENDAR_TASK (event)->start_time) : + g_strdup ("(undefined)"); + due_str = CALENDAR_TASK (event)->due_time ? + isodate_from_time_t (CALENDAR_TASK (event)->due_time) : + g_strdup ("(undefined)"); + completed_str = CALENDAR_TASK (event)->completed_time ? + isodate_from_time_t (CALENDAR_TASK (event)->completed_time) : + g_strdup ("(undefined)"); + + dprintf ("Task: uid '%s', summary '%s', description '%s', " + "start_time '%s', due_time '%s', percent_complete %d, completed_time '%s'\n", + CALENDAR_TASK (event)->uid, + CALENDAR_TASK (event)->summary, + CALENDAR_TASK (event)->description, + start_str, + due_str, + CALENDAR_TASK (event)->percent_complete, + completed_str); + + g_free (completed_str); + } + break; + default: + g_assert_not_reached (); + break; + } +#endif +} + +static inline CalendarClientQuery * +goddamn_this_is_crack (CalendarClientSource *source, + ECalView *view, + gboolean *emit_signal) +{ + g_assert (view != NULL); + + if (source->completed_query.view == view) + { + if (emit_signal) + *emit_signal = TRUE; + return &source->completed_query; + } + else if (source->in_progress_query.view == view) + { + if (emit_signal) + *emit_signal = FALSE; + return &source->in_progress_query; + } + + g_assert_not_reached (); + + return NULL; +} + +static void +calendar_client_handle_query_completed (CalendarClientSource *source, + ECalendarStatus status, + ECalView *view) +{ + CalendarClientQuery *query; + + query = goddamn_this_is_crack (source, view, NULL); + + dprintf ("Query %p completed: %s\n", query, e_cal_get_error_message (status)); + + if (status != E_CALENDAR_STATUS_OK) + { + g_warning ("Calendar query failed: %s\n", + e_cal_get_error_message (status)); + calendar_client_stop_query (source->client, source, query); + return; + } + + g_assert (source->query_in_progress != FALSE); + g_assert (query == &source->in_progress_query); + + calendar_client_query_finalize (&source->completed_query); + + source->completed_query = source->in_progress_query; + source->query_completed = TRUE; + + source->query_in_progress = FALSE; + source->in_progress_query.view = NULL; + source->in_progress_query.events = NULL; + + g_signal_emit (source->client, source->changed_signal_id, 0); +} + +static void +calendar_client_handle_query_result (CalendarClientSource *source, + GSList *objects, + ECalView *view) +{ + CalendarClientQuery *query; + CalendarClient *client; + gboolean emit_signal; + gboolean events_changed; + GSList *l; + GTime month_begin; + GTime month_end; + + client = source->client; + + query = goddamn_this_is_crack (source, view, &emit_signal); + + dprintf ("Query %p result: %d objects:\n", + query, g_slist_length (objects)); + + month_begin = make_time_for_day_begin (1, + client->priv->month, + client->priv->year); + + month_end = make_time_for_day_begin (1, + client->priv->month + 1, + client->priv->year); + + events_changed = FALSE; + for (l = objects; l; l = l->next) + { + CalendarEvent *event; + CalendarEvent *old_event; + icalcomponent *ical = l->data; + + event = calendar_event_new (ical); + calendar_event_generate_ocurrences (event, + ical, + source->source, + month_begin, + month_end); + + old_event = g_hash_table_lookup (query->events, + icalcomponent_get_uid (ical)); + + if (!calendar_event_equal (event, old_event)) + { + dprintf ("Event %s: ", old_event ? "modified" : "added"); + + calendar_event_debug_dump (event); + + g_hash_table_replace (query->events, + (char *) calendar_event_get_uid (event), + event); + + events_changed = TRUE; + } + } + + if (emit_signal && events_changed) + { + g_signal_emit (source->client, source->changed_signal_id, 0); + } +} + +static void +calendar_client_handle_objects_removed (CalendarClientSource *source, + GSList *uids, + ECalView *view) +{ + CalendarClientQuery *query; + gboolean emit_signal; + gboolean events_changed; + GSList *l; + + query = goddamn_this_is_crack (source, view, &emit_signal); + + events_changed = FALSE; + for (l = uids; l; l = l->next) + { + CalendarEvent *event; + const char *uid = l->data; + + if ((event = g_hash_table_lookup (query->events, uid))) + { + dprintf ("Event removed: "); + + calendar_event_debug_dump (event); + + g_assert (g_hash_table_remove (query->events, uid)); + + events_changed = TRUE; + } + } + + if (emit_signal && events_changed) + { + g_signal_emit (source->client, source->changed_signal_id, 0); + } +} + +static void +calendar_client_query_finalize (CalendarClientQuery *query) +{ + if (query->view) + g_object_unref (query->view); + query->view = NULL; + + if (query->events) + g_hash_table_destroy (query->events); + query->events = NULL; +} + +static void +calendar_client_stop_query (CalendarClient *client, + CalendarClientSource *source, + CalendarClientQuery *query) +{ + if (query == &source->in_progress_query) + { + dprintf ("Stopping in progress query %p\n", query); + + g_assert (source->query_in_progress != FALSE); + + source->query_in_progress = FALSE; + } + else if (query == &source->completed_query) + { + dprintf ("Stopping completed query %p\n", query); + + g_assert (source->query_completed != FALSE); + + source->query_completed = FALSE; + } + else + g_assert_not_reached (); + + calendar_client_query_finalize (query); +} + +static void +calendar_client_start_query (CalendarClient *client, + CalendarClientSource *source, + const char *query) +{ + ECalView *view = NULL; + GError *error = NULL; + + if (!e_cal_get_query (source->source, query, &view, &error)) + { + g_warning ("Error preparing the query: '%s': %s\n", + query, error->message); + g_error_free (error); + return; + } + + g_assert (view != NULL); + + if (source->query_in_progress) + calendar_client_stop_query (client, source, &source->in_progress_query); + + dprintf ("Starting query %p: '%s'\n", &source->in_progress_query, query); + + source->query_in_progress = TRUE; + source->in_progress_query.view = view; + source->in_progress_query.events = + g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + (GDestroyNotify) calendar_event_free); + + g_signal_connect_swapped (view, "objects-added", + G_CALLBACK (calendar_client_handle_query_result), + source); + g_signal_connect_swapped (view, "objects-modified", + G_CALLBACK (calendar_client_handle_query_result), + source); + g_signal_connect_swapped (view, "objects-removed", + G_CALLBACK (calendar_client_handle_objects_removed), + source); + g_signal_connect_swapped (view, "view-done", + G_CALLBACK (calendar_client_handle_query_completed), + source); + + e_cal_view_start (view); +} + +static void +calendar_client_update_appointments (CalendarClient *client) +{ + GSList *l; + char *query; + char *month_begin; + char *month_end; + + if (client->priv->month == -1 || + client->priv->year == -1) + return; + + month_begin = make_isodate_for_day_begin (1, + client->priv->month, + client->priv->year); + + month_end = make_isodate_for_day_begin (1, + client->priv->month + 1, + client->priv->year); + + /* FIXME: occur-in-time-range should take recurrences into account */ + query = g_strdup_printf ("(or (occur-in-time-range? (make-time \"%s\") " + "(make-time \"%s\")) " + "(has-recurrences?))", + month_begin, month_end); + + for (l = client->priv->appointment_sources; l; l = l->next) + calendar_client_start_query (client, l->data, query); + + g_free (month_begin); + g_free (month_end); + g_free (query); +} + +/* FIXME: + * perhaps we should use evo's "hide_completed_tasks" pref? + */ +static void +calendar_client_update_tasks (CalendarClient *client) +{ + GSList *l; + char *query; + +#ifdef FIX_BROKEN_TASKS_QUERY + /* FIXME: this doesn't work for tasks without a start or + * due date + * Look at filter_task() to see the behaviour we + * want. + */ + + char *day_begin; + char *day_end; + + if (client->priv->day == -1 || + client->priv->month == -1 || + client->priv->year == -1) + return; + + day_begin = make_isodate_for_day_begin (client->priv->day, + client->priv->month, + client->priv->year); + + day_end = make_isodate_for_day_begin (client->priv->day + 1, + client->priv->month, + client->priv->year); + if (!day_begin || !day_end) + { + g_warning ("Cannot run query with invalid date: %dd %dy %dm\n", + client->priv->day, + client->priv->month, + client->priv->year); + g_free (day_begin); + g_free (day_end); + return; + } + + query = g_strdup_printf ("(and (occur-in-time-range? (make-time \"%s\") " + "(make-time \"%s\")) " + "(or (not is-completed?) " + "(and (is-completed?) " + "(not (completed-before? (make-time \"%s\"))))))", + day_begin, day_end, day_begin); +#else + query = g_strdup ("#t"); +#endif /* FIX_BROKEN_TASKS_QUERY */ + + for (l = client->priv->task_sources; l; l = l->next) + calendar_client_start_query (client, l->data, query); + +#if FIX_BROKEN_TASKS_QUERY + g_free (day_begin); + g_free (day_end); +#endif + g_free (query); +} + +static void +calendar_client_source_finalize (CalendarClientSource *source) +{ + source->client = NULL; + + if (source->source) + g_object_unref (source->source); + source->source = NULL; + + calendar_client_query_finalize (&source->completed_query); + calendar_client_query_finalize (&source->in_progress_query); + + source->query_completed = FALSE; + source->query_in_progress = FALSE; +} + +static int +compare_calendar_sources (CalendarClientSource *s1, + CalendarClientSource *s2) +{ + return (s1->source == s2->source) ? 0 : 1; +} + +static GSList * +calendar_client_update_sources_list (CalendarClient *client, + GSList *sources, + GSList *esources, + guint changed_signal_id) +{ + GSList *retval, *l; + + retval = NULL; + + for (l = esources; l; l = l->next) + { + CalendarClientSource dummy_source; + CalendarClientSource *new_source; + GSList *s; + ECal *esource = l->data; + + dummy_source.source = esource; + + dprintf ("update_sources_list: adding client %s: ", + e_source_peek_uid (e_cal_get_source (esource))); + + if ((s = g_slist_find_custom (sources, + &dummy_source, + (GCompareFunc) compare_calendar_sources))) + { + dprintf ("already on list\n"); + new_source = s->data; + sources = g_slist_delete_link (sources, s); + } + else + { + dprintf ("added\n"); + new_source = g_new0 (CalendarClientSource, 1); + new_source->client = client; + new_source->source = g_object_ref (esource); + new_source->changed_signal_id = changed_signal_id; + } + + retval = g_slist_prepend (retval, new_source); + } + + for (l = sources; l; l = l->next) + { + CalendarClientSource *source = l->data; + + dprintf ("Removing client %s from list\n", + e_source_peek_uid (e_cal_get_source (source->source))); + + calendar_client_source_finalize (source); + g_free (source); + } + g_slist_free (sources); + + return retval; +} + +static void +calendar_client_appointment_sources_changed (CalendarClient *client) +{ + GSList *esources; + + dprintf ("appointment_sources_changed: updating ...\n"); + + esources = calendar_sources_get_appointment_sources (client->priv->calendar_sources); + + client->priv->appointment_sources = + calendar_client_update_sources_list (client, + client->priv->appointment_sources, + esources, + signals [APPOINTMENTS_CHANGED]); + + calendar_client_update_appointments (client); +} + +static void +calendar_client_task_sources_changed (CalendarClient *client) +{ + GSList *esources; + + dprintf ("task_sources_changed: updating ...\n"); + + esources = calendar_sources_get_task_sources (client->priv->calendar_sources); + + client->priv->task_sources = + calendar_client_update_sources_list (client, + client->priv->task_sources, + esources, + signals [TASKS_CHANGED]); + + calendar_client_update_tasks (client); +} + +void +calendar_client_select_month (CalendarClient *client, + guint month, + guint year) +{ + g_return_if_fail (CALENDAR_IS_CLIENT (client)); + g_return_if_fail (month >= 0 && month <= 11); + + if (client->priv->year != year || client->priv->month != month) + { + client->priv->month = month; + client->priv->year = year; + + calendar_client_update_appointments (client); + calendar_client_update_tasks (client); + + g_object_freeze_notify (G_OBJECT (client)); + g_object_notify (G_OBJECT (client), "month"); + g_object_notify (G_OBJECT (client), "year"); + g_object_thaw_notify (G_OBJECT (client)); + } +} + +void +calendar_client_select_day (CalendarClient *client, + guint day) +{ + g_return_if_fail (CALENDAR_IS_CLIENT (client)); + g_return_if_fail (day <= 31); + + if (client->priv->day != day) + { + client->priv->day = day; + + /* don't need to update appointments unless + * the selected month changes + */ +#if FIX_BROKEN_TASKS_QUERY + calendar_client_update_tasks (client); +#endif + + g_object_notify (G_OBJECT (client), "day"); + } +} + +typedef struct +{ + CalendarClient *client; + GSList *events; + GTime start_time; + GTime end_time; +} FilterData; + +typedef void (* CalendarEventFilterFunc) (const char *uid, + CalendarEvent *event, + FilterData *filter_data); + +static void +filter_appointment (const char *uid, + CalendarEvent *event, + FilterData *filter_data) +{ + GSList *occurrences, *l; + + if (event->type != CALENDAR_EVENT_APPOINTMENT) + return; + + occurrences = CALENDAR_APPOINTMENT (event)->occurrences; + CALENDAR_APPOINTMENT (event)->occurrences = NULL; + + for (l = occurrences; l; l = l->next) + { + CalendarOccurrence *occurrence = l->data; + + if (occurrence->start_time >= filter_data->start_time && + occurrence->end_time <= filter_data->end_time) + { + CalendarEvent *new_event; + + new_event = calendar_event_copy (event); + + CALENDAR_APPOINTMENT (new_event)->start_time = occurrence->start_time; + CALENDAR_APPOINTMENT (new_event)->end_time = occurrence->end_time; + + filter_data->events = g_slist_prepend (filter_data->events, new_event); + } + } + + CALENDAR_APPOINTMENT (event)->occurrences = occurrences; +} + +static void +filter_task (const char *uid, + CalendarEvent *event, + FilterData *filter_data) +{ + CalendarTask *task; + + if (event->type != CALENDAR_EVENT_TASK) + return; + + task = CALENDAR_TASK (event); + +#ifdef FIX_BROKEN_TASKS_QUERY + if (task->start_time && task->start_time > filter_data->start_time) + return; + + if (task->completed_time && + (task->completed_time < filter_data->start_time || + task->completed_time > filter_data->end_time)) + return; +#endif /* FIX_BROKEN_TASKS_QUERY */ + + filter_data->events = g_slist_prepend (filter_data->events, + calendar_event_copy (event)); +} + +static GSList * +calendar_client_filter_events (CalendarClient *client, + GSList *sources, + CalendarEventFilterFunc filter_func, + GTime start_time, + GTime end_time) +{ + FilterData filter_data; + GSList *l; + GSList *retval; + + if (!sources) + return NULL; + + filter_data.client = client; + filter_data.events = NULL; + filter_data.start_time = start_time; + filter_data.end_time = end_time; + + retval = NULL; + for (l = sources; l; l = l->next) + { + CalendarClientSource *source = l->data; + + if (source->query_completed) + { + filter_data.events = NULL; + g_hash_table_foreach (source->completed_query.events, + (GHFunc) filter_func, + &filter_data); + + filter_data.events = g_slist_reverse (filter_data.events); + + retval = g_slist_concat (retval, filter_data.events); + } + } + + return retval; +} + +GSList * +calendar_client_get_events (CalendarClient *client, + CalendarEventType event_mask) +{ + GSList *appointments; + GSList *tasks; + GTime day_begin; + GTime day_end; + + g_return_val_if_fail (CALENDAR_IS_CLIENT (client), NULL); + g_return_val_if_fail (client->priv->day != -1 && + client->priv->month != -1 && + client->priv->year != -1, NULL); + + day_begin = make_time_for_day_begin (client->priv->day, + client->priv->month, + client->priv->year); + day_end = make_time_for_day_begin (client->priv->day + 1, + client->priv->month, + client->priv->year); + + appointments = NULL; + if (event_mask & CALENDAR_EVENT_APPOINTMENT) + { + appointments = calendar_client_filter_events (client, + client->priv->appointment_sources, + filter_appointment, + day_begin, + day_end); + } + + tasks = NULL; + if (event_mask & CALENDAR_EVENT_TASK) + { + tasks = calendar_client_filter_events (client, + client->priv->task_sources, + filter_task, + day_begin, + day_end); + } + + return g_slist_concat (appointments, tasks); +} + +static inline int +day_from_time_t (time_t t) +{ + struct tm *tm = localtime (&t); + + g_assert (tm == NULL || (tm->tm_mday >=1 && tm->tm_mday <= 31)); + + return tm ? tm->tm_mday : 0; +} + +void +calendar_client_foreach_appointment_day (CalendarClient *client, + CalendarDayIter iter_func, + gpointer user_data) +{ + GSList *appointments, *l; + gboolean marked_days [32] = { FALSE, }; + GTime month_begin; + GTime month_end; + int i; + + g_return_if_fail (CALENDAR_IS_CLIENT (client)); + g_return_if_fail (iter_func != NULL); + g_return_if_fail (client->priv->month != -1 && + client->priv->year != -1); + + month_begin = make_time_for_day_begin (1, + client->priv->month, + client->priv->year); + month_end = make_time_for_day_begin (1, + client->priv->month + 1, + client->priv->year); + + appointments = calendar_client_filter_events (client, + client->priv->appointment_sources, + filter_appointment, + month_begin, + month_end); + for (l = appointments; l; l = l->next) + { + CalendarAppointment *appointment = l->data; + + if (appointment->start_time) + marked_days [day_from_time_t (appointment->start_time)] = TRUE; + + if (appointment->end_time) + marked_days [day_from_time_t (appointment->end_time)] = TRUE; + + calendar_event_free (CALENDAR_EVENT (appointment)); + } + + g_slist_free (appointments); + + for (i = 1; i < 32; i++) + { + if (marked_days [i]) + iter_func (client, i, user_data); + } +} + +void +calendar_client_set_task_completed (CalendarClient *client, + char *task_uid, + gboolean task_completed, + guint percent_complete) +{ + GSList *l; + ECal *esource; + icalcomponent *ical; + icalproperty *prop; + icalproperty_status status; + + g_return_if_fail (CALENDAR_IS_CLIENT (client)); + g_return_if_fail (task_uid != NULL); + g_return_if_fail (task_completed == FALSE || percent_complete == 100); + + ical = NULL; + esource = NULL; + for (l = client->priv->task_sources; l; l = l->next) + { + CalendarClientSource *source = l->data; + + esource = source->source; + e_cal_get_object (esource, task_uid, NULL, &ical, NULL); + if (ical) + break; + } + + if (!ical) + { + g_warning ("Connot locate task with uid = '%s'\n", task_uid); + return; + } + + g_assert (esource != NULL); + + /* Completed time */ + prop = icalcomponent_get_first_property (ical, + ICAL_COMPLETED_PROPERTY); + if (task_completed) + { + struct icaltimetype completed_time; + + completed_time = icaltime_current_time_with_zone (NULL); + if (!prop) + { + icalcomponent_add_property (ical, + icalproperty_new_completed (completed_time)); + } + else + { + icalproperty_set_completed (prop, completed_time); + } + } + else if (prop) + { + icalcomponent_remove_property (ical, prop); + } + + /* Percent complete */ + prop = icalcomponent_get_first_property (ical, + ICAL_PERCENTCOMPLETE_PROPERTY); + if (!prop) + { + icalcomponent_add_property (ical, + icalproperty_new_percentcomplete (percent_complete)); + } + else + { + icalproperty_set_percentcomplete (prop, percent_complete); + } + + /* Status */ + status = task_completed ? ICAL_STATUS_COMPLETED : ICAL_STATUS_NEEDSACTION; + prop = icalcomponent_get_first_property (ical, ICAL_STATUS_PROPERTY); + if (prop) + { + icalproperty_set_status (prop, status); + } + else + { + icalcomponent_add_property (ical, + icalproperty_new_status (status)); + } + + e_cal_modify_object (esource, ical, CALOBJ_MOD_ALL, NULL); +} + +gboolean +calendar_client_launch_editor (CalendarClient *client, + CalendarEventType event_type, + GdkScreen *screen, + GError **error) +{ +#define EVOLUTION_COMMAND "evolution-1.5" +#define EVOLUTION_APPOINTMENTS_OAFIID "OAFIID:GNOME_Evolution_Calendar_Component:1.5" +#define EVOLUTION_TASKS_OAFIID "OAFIID:GNOME_Evolution_Tasks_Component:1.5" + + char *command_line; + gboolean retval; + + g_return_val_if_fail (CALENDAR_IS_CLIENT (client), FALSE); + g_return_val_if_fail (event_type == CALENDAR_EVENT_APPOINTMENT || + event_type == CALENDAR_EVENT_TASK, FALSE); + + command_line = g_strdup_printf ("%s -c %s", + EVOLUTION_COMMAND, + event_type == CALENDAR_EVENT_APPOINTMENT ? + EVOLUTION_APPOINTMENTS_OAFIID : + EVOLUTION_TASKS_OAFIID); + + retval = gdk_spawn_command_line_on_screen (screen, command_line, error); + + g_free (command_line); + + return retval; + +#undef EVOLUTION_COMMAND +#undef EVOLUTION_APPOINTMENTS_OAFIID +#undef EVOLUTION_TASKS_OAFIID +} Index: calendar-client.h =================================================================== RCS file: calendar-client.h diff -N calendar-client.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ calendar-client.h 12 Jan 2004 17:15:55 -0000 @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2004 Free Software Foundation, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Authors: + * Mark McLoughlin + * William Jon McCann + * Martin Grimme + * Christian Kellner + */ + +#ifndef __CALENDAR_CLIENT_H__ +#define __CALENDAR_CLIENT_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef enum +{ + CALENDAR_EVENT_APPOINTMENT = 1 << 0, + CALENDAR_EVENT_TASK = 1 << 1, + CALENDAR_EVENT_ALL = (1 << 2) - 1 +} CalendarEventType; + +#define CALENDAR_TYPE_CLIENT (calendar_client_get_type ()) +#define CALENDAR_CLIENT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CALENDAR_TYPE_CLIENT, CalendarClient)) +#define CALENDAR_CLIENT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), CALENDAR_TYPE_CLIENT, CalendarClientClass)) +#define CALENDAR_IS_CLIENT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CALENDAR_TYPE_CLIENT)) +#define CALENDAR_IS_CLIENT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CALENDAR_TYPE_CLIENT)) +#define CALENDAR_CLIENT_GET_CLASS(o)(G_TYPE_INSTANCE_GET_CLASS ((o), CALENDAR_TYPE_CLIENT, CalendarClientClass)) + +typedef struct _CalendarClient CalendarClient; +typedef struct _CalendarClientClass CalendarClientClass; +typedef struct _CalendarClientPrivate CalendarClientPrivate; + +struct _CalendarClient +{ + GObject parent; + CalendarClientPrivate *priv; +}; + +struct _CalendarClientClass +{ + GObjectClass parent_class; + + void (* appointments_changed) (CalendarClient *client); + void (* tasks_changed) (CalendarClient *client); +}; + + +typedef struct +{ + GTime start_time; + GTime end_time; +} CalendarOccurrence; + +typedef struct +{ + char *uid; + char *summary; + char *description; + GTime start_time; + GTime end_time; + guint is_all_day : 1; + + /* Only used internally */ + GSList *occurrences; +} CalendarAppointment; + +typedef struct +{ + char *uid; + char *summary; + char *description; + GTime start_time; + GTime due_time; + guint percent_complete; + GTime completed_time; +} CalendarTask; + +typedef struct +{ + union + { + CalendarAppointment appointment; + CalendarTask task; + } event; + CalendarEventType type; +} CalendarEvent; + +#define CALENDAR_EVENT(e) ((CalendarEvent *)(e)) +#define CALENDAR_APPOINTMENT(e) ((CalendarAppointment *)(e)) +#define CALENDAR_TASK(e) ((CalendarTask *)(e)) + +typedef void (* CalendarDayIter) (CalendarClient *client, + guint day, + gpointer user_data); + + +GType calendar_client_get_type (void) G_GNUC_CONST; +CalendarClient *calendar_client_new (void); + +void calendar_client_select_month (CalendarClient *client, + guint month, + guint year); +void calendar_client_select_day (CalendarClient *client, + guint day); + +GSList *calendar_client_get_events (CalendarClient *client, + CalendarEventType event_mask); +void calendar_client_foreach_appointment_day (CalendarClient *client, + CalendarDayIter iter_func, + gpointer user_data); + +void calendar_client_set_task_completed (CalendarClient *client, + char *task_uid, + gboolean task_completed, + guint percent_complete); +gboolean calendar_client_launch_editor (CalendarClient *client, + CalendarEventType event_type, + GdkScreen *screen, + GError **error); + +void calendar_event_free (CalendarEvent *event); + +G_END_DECLS + +#endif /* __CALENDAR_CLIENT_H__ */ Index: calendar-debug.h =================================================================== RCS file: calendar-debug.h diff -N calendar-debug.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ calendar-debug.h 12 Jan 2004 17:15:55 -0000 @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2004 Free Software Foundation, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Authors: + * Mark McLoughlin + */ + +#ifndef __CALENDAR_DEBUG_H__ +#define __CALENDAR_DEBUG_H__ + +#include + +G_BEGIN_DECLS + +#ifdef CALENDAR_ENABLE_DEBUG + +#include + +#ifdef G_HAVE_ISO_VARARGS +# define dprintf(...) fprintf (stderr, __VA_ARGS__); +#elif defined(G_HAVE_GNUC_VARARGS) +# define dprintf(args...) fprintf (stderr, args); +#endif + +#else /* if !defined (CALENDAR_DEBUG) */ + +#ifdef G_HAVE_ISO_VARARGS +# define dprintf(...) +#elif defined(G_HAVE_GNUC_VARARGS) +# define dprintf(args...) +#endif + +#endif /* CALENDAR_ENABLE_DEBUG */ + +G_END_DECLS + +#endif /* __CALENDAR_DEBUG_H__ */ Index: calendar-sources.c =================================================================== RCS file: calendar-sources.c diff -N calendar-sources.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ calendar-sources.c 12 Jan 2004 17:15:55 -0000 @@ -0,0 +1,566 @@ +/* + * Copyright (C) 2004 Free Software Foundation, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Authors: + * Mark McLoughlin + * William Jon McCann + * Martin Grimme + * Christian Kellner + */ + +#include + +#include "calendar-sources.h" + +#include +#include +#include +#include +#include + +#undef CALENDAR_ENABLE_DEBUG +#include "calendar-debug.h" + +#ifndef _ +#define _(x) gettext(x) +#endif + +#ifndef N_ +#define N_(x) x +#endif + +#define CALENDAR_SOURCES_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CALENDAR_TYPE_SOURCES, CalendarSourcesPrivate)) + +#define CALENDAR_SOURCES_EVO_DIR "/apps/evolution" +#define CALENDAR_SOURCES_APPOINTMENT_SOURCES_KEY CALENDAR_SOURCES_EVO_DIR "/calendar/sources" +#define CALENDAR_SOURCES_SELECTED_APPOINTMENT_SOURCES_DIR CALENDAR_SOURCES_EVO_DIR "/calendar/display" +#define CALENDAR_SOURCES_SELECTED_APPOINTMENT_SOURCES_KEY CALENDAR_SOURCES_SELECTED_APPOINTMENT_SOURCES_DIR "/selected_calendars" +#define CALENDAR_SOURCES_TASK_SOURCES_KEY CALENDAR_SOURCES_EVO_DIR "/tasks/sources" +#define CALENDAR_SOURCES_SELECTED_TASK_SOURCES_DIR CALENDAR_SOURCES_EVO_DIR "/calendar/tasks" +#define CALENDAR_SOURCES_SELECTED_TASK_SOURCES_KEY CALENDAR_SOURCES_SELECTED_APPOINTMENT_SOURCES_DIR "/selected_tasks" + +typedef struct _CalendarSourceData CalendarSourceData; + +struct _CalendarSourceData +{ + ECalSourceType source_type; + CalendarSources *sources; + guint changed_signal; + + GSList *clients; + GSList *selected_sources; + ESourceList *esource_list; + + guint selected_sources_listener; + char *selected_sources_dir; + + guint loaded : 1; +}; + +struct _CalendarSourcesPrivate +{ + CalendarSourceData appointment_sources; + CalendarSourceData task_sources; + + GConfClient *gconf_client; +}; + +static void calendar_sources_class_init (CalendarSourcesClass *klass); +static void calendar_sources_init (CalendarSources *sources); +static void calendar_sources_finalize (GObject *object); + +enum +{ + APPOINTMENT_SOURCES_CHANGED, + TASK_SOURCES_CHANGED, + LAST_SIGNAL +}; +static guint signals [LAST_SIGNAL] = { 0, }; + +static GObjectClass *parent_class = NULL; +static CalendarSources *calendar_sources_singleton = NULL; + +GType +calendar_sources_get_type (void) +{ + static GType sources_type = 0; + + if (!sources_type) + { + static const GTypeInfo sources_info = + { + sizeof (CalendarSourcesClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) calendar_sources_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (CalendarSources), + 0, /* n_preallocs */ + (GInstanceInitFunc) calendar_sources_init, + }; + + sources_type = g_type_register_static (G_TYPE_OBJECT, + "CalendarSources", + &sources_info, 0); + } + + return sources_type; +} + +static void +calendar_sources_class_init (CalendarSourcesClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + parent_class = g_type_class_peek_parent (klass); + + gobject_class->finalize = calendar_sources_finalize; + + g_type_class_add_private (klass, sizeof (CalendarSourcesPrivate)); + + signals [APPOINTMENT_SOURCES_CHANGED] = + g_signal_new ("appointment-sources-changed", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (CalendarSourcesClass, + appointment_sources_changed), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + signals [TASK_SOURCES_CHANGED] = + g_signal_new ("task-sources-changed", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (CalendarSourcesClass, + task_sources_changed), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); +} + +static void +calendar_sources_init (CalendarSources *sources) +{ + sources->priv = CALENDAR_SOURCES_GET_PRIVATE (sources); + + sources->priv->appointment_sources.source_type = E_CAL_SOURCE_TYPE_EVENT; + sources->priv->appointment_sources.sources = sources; + sources->priv->appointment_sources.changed_signal = signals [APPOINTMENT_SOURCES_CHANGED]; + + sources->priv->task_sources.source_type = E_CAL_SOURCE_TYPE_TODO; + sources->priv->task_sources.sources = sources; + sources->priv->task_sources.changed_signal = signals [TASK_SOURCES_CHANGED]; + + sources->priv->gconf_client = gconf_client_get_default (); +} + +static void +calendar_sources_finalize_source_data (CalendarSources *sources, + CalendarSourceData *source_data) +{ + if (source_data->loaded) + { + GSList *l; + + if (source_data->selected_sources_dir) + { + gconf_client_remove_dir (sources->priv->gconf_client, + source_data->selected_sources_dir, + NULL); + + g_free (source_data->selected_sources_dir); + source_data->selected_sources_dir = NULL; + } + + if (source_data->selected_sources_listener) + { + gconf_client_notify_remove (sources->priv->gconf_client, + source_data->selected_sources_listener); + source_data->selected_sources_listener = 0; + } + + for (l = source_data->clients; l; l = l->next) + g_object_unref (l->data); + source_data->clients = NULL; + + if (source_data->esource_list) + g_object_unref (source_data->esource_list); + source_data->esource_list = NULL; + + for (l = source_data->selected_sources; l; l = l->next) + g_free (l->data); + g_slist_free (source_data->selected_sources); + source_data->selected_sources = NULL; + + source_data->loaded = FALSE; + } +} + +static void +calendar_sources_finalize (GObject *object) +{ + CalendarSources *sources = CALENDAR_SOURCES (object); + + calendar_sources_finalize_source_data (sources, &sources->priv->appointment_sources); + calendar_sources_finalize_source_data (sources, &sources->priv->task_sources); + + if (sources->priv->gconf_client) + g_object_unref (sources->priv->gconf_client); + sources->priv->gconf_client = NULL; + + if (G_OBJECT_CLASS (parent_class)->finalize) + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +CalendarSources * +calendar_sources_get (void) +{ + if (calendar_sources_singleton) + return g_object_ref (calendar_sources_singleton); + + calendar_sources_singleton = g_object_new (CALENDAR_TYPE_SOURCES, NULL); + g_object_add_weak_pointer (G_OBJECT (calendar_sources_singleton), + (gpointer *) &calendar_sources_singleton); + + return calendar_sources_singleton; +} + +static gboolean +is_source_selected (ESource *esource, + GSList *selected_sources) +{ + const char *uid; + GSList *l; + + uid = e_source_peek_uid (esource); + + for (l = selected_sources; l; l = l->next) + { + const char *source = l->data; + + if (!strcmp (source, uid)) + return TRUE; + } + + return FALSE; +} + +static ECal * +load_esource (ESource *esource, + ECalSourceType source_type, + GSList *existing_clients) +{ + ECal *retval; + GError *error; + + + if (existing_clients) + { + GSList *l; + + for (l = existing_clients; l; l = l->next) + { + ECal *client = E_CAL (l->data); + + if (e_source_equal (esource, e_cal_get_source (client))) + { + dprintf (" load_esource: found existing source ... returning that\n"); + + return g_object_ref (client); + } + } + } + + retval = e_cal_new (esource, source_type); + if (!retval) + { + g_warning ("Could not load source '%s' from '%s'\n", + e_source_peek_name (esource), + e_source_peek_relative_uri (esource)); + return NULL; + } + + error = NULL; + if (!e_cal_open (retval, TRUE, &error)) + { + g_assert (error != NULL); + g_warning ("Cannot open calendar from uri '%s': %s\n", + e_cal_get_uri (retval), error->message); + g_error_free (error); + g_object_unref (retval); + return NULL; + } + + dprintf (" Loaded calendar from uri '%s'\n", + e_cal_get_uri (retval)); + + return retval; +} + +/* - Order doesn't matter + * - Can just compare object pointers since we + * re-use client connections + */ +static gboolean +compare_ecal_lists (GSList *a, + GSList *b) +{ + GSList *l; + + if (g_slist_length (a) != g_slist_length (b)) + return FALSE; + + for (l = a; l; l = l->next) + { + if (!g_slist_find (b, l->data)) + return FALSE; + } + + return TRUE; +} + +static inline void +debug_dump_selected_sources (GSList *selected_sources) +{ +#ifdef CALENDAR_ENABLE_DEBUG + GSList *l; + + dprintf ("Selected sources:\n"); + for (l = selected_sources; l; l = l->next) + { + char *source = l->data; + + dprintf (" %s\n", source); + } + dprintf ("\n"); +#endif +} + +static inline void +debug_dump_ecal_list (GSList *ecal_list) +{ +#ifdef CALENDAR_ENABLE_DEBUG + GSList *l; + + dprintf ("Loaded clients:\n"); + for (l = ecal_list; l; l = l->next) + { + ECal *client = l->data; + ESource *source = e_cal_get_source (client); + + dprintf (" %s %s %s\n", + e_source_peek_uid (source), + e_source_peek_name (source), + e_cal_get_uri (client)); + } +#endif +} + +static void +calendar_sources_load_esource_list (CalendarSourceData *source_data) +{ + GSList *loaded_clients = NULL; + GSList *groups, *l; + + g_return_if_fail (source_data->esource_list != NULL); + + debug_dump_selected_sources (source_data->selected_sources); + + dprintf ("Source groups:\n"); + groups = e_source_list_peek_groups (source_data->esource_list); + for (l = groups; l; l = l->next) + { + GSList *esources, *s; + + dprintf (" %s\n", e_source_group_peek_uid (l->data)); + dprintf (" sources:\n"); + + esources = e_source_group_peek_sources (l->data); + for (s = esources; s; s = s->next) + { + ESource *esource = E_SOURCE (s->data); + ECal *client; + + dprintf (" uid = '%s', name = '%s', relative uri = '%s': \n", + e_source_peek_uid (esource), + e_source_peek_name (esource), + e_source_peek_relative_uri (esource)); + + if (is_source_selected (esource, source_data->selected_sources) && + (client = load_esource (esource, source_data->source_type, source_data->clients))) + { + loaded_clients = g_slist_prepend (loaded_clients, client); + } + } + } + dprintf ("\n"); + + if (source_data->loaded && + !compare_ecal_lists (source_data->clients, loaded_clients)) + { + dprintf ("Emitting %s-sources-changed signal\n", + source_data->source_type == E_CAL_SOURCE_TYPE_EVENT ? "appointment" : "task"); + g_signal_emit (source_data->sources, source_data->changed_signal, 0); + } + + for (l = source_data->clients; l; l = l->next) + g_object_unref (l->data); + g_slist_free (source_data->clients); + source_data->clients = g_slist_reverse (loaded_clients); + + debug_dump_ecal_list (source_data->clients); +} + +static void +calendar_sources_esource_list_changed (ESourceList *source_list, + CalendarSourceData *source_data) + +{ + dprintf ("ESourceList changed, reloading\n"); + + calendar_sources_load_esource_list (source_data); +} + +static void +calendar_sources_selected_sources_notify (GConfClient *client, + guint cnx_id, + GConfEntry *entry, + CalendarSourceData *source_data) +{ + GSList *l; + + if (!entry->value || + entry->value->type != GCONF_VALUE_LIST || + gconf_value_get_list_type (entry->value) != GCONF_VALUE_STRING) + return; + + dprintf ("Selected sources key (%s) changed, reloading\n", entry->key); + + for (l = source_data->selected_sources; l; l = l->next) + g_free (l->data); + source_data->selected_sources = NULL; + + for (l = gconf_value_get_list (entry->value); l; l = l->next) + { + const char *source = gconf_value_get_string (l->data); + + source_data->selected_sources = + g_slist_prepend (source_data->selected_sources, + g_strdup (source)); + } + source_data->selected_sources = + g_slist_reverse (source_data->selected_sources); + + calendar_sources_load_esource_list (source_data); +} + +static void +calendar_sources_load_sources (CalendarSources *sources, + CalendarSourceData *source_data, + const char *sources_key, + const char *selected_sources_key, + const char *selected_sources_dir) +{ + GConfClient *gconf_client; + GError *error; + + dprintf ("---------------------------\n"); + dprintf ("Loading sources:\n"); + dprintf (" sources_key: %s\n", sources_key); + dprintf (" selected_sources_key: %s\n", selected_sources_key); + dprintf (" selected_sources_dir: %s\n", selected_sources_dir); + + gconf_client = sources->priv->gconf_client; + + error = NULL; + source_data->selected_sources = gconf_client_get_list (gconf_client, + selected_sources_key, + GCONF_VALUE_STRING, + &error); + if (error) + { + g_warning ("Failed to get selected sources from '%s': %s\n", + selected_sources_key, + error->message); + g_error_free (error); + return; + } + + gconf_client_add_dir (gconf_client, + selected_sources_dir, + GCONF_CLIENT_PRELOAD_NONE, + NULL); + source_data->selected_sources_dir = g_strdup (selected_sources_dir); + + source_data->selected_sources_listener = + gconf_client_notify_add (gconf_client, + selected_sources_dir, + (GConfClientNotifyFunc) calendar_sources_selected_sources_notify, + source_data, NULL, NULL); + + source_data->esource_list = e_source_list_new_for_gconf (gconf_client, sources_key); + g_signal_connect (source_data->esource_list, "changed", + G_CALLBACK (calendar_sources_esource_list_changed), + source_data); + + calendar_sources_load_esource_list (source_data); + + source_data->loaded = TRUE; + + dprintf ("---------------------------\n"); +} + +GSList * +calendar_sources_get_appointment_sources (CalendarSources *sources) +{ + g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL); + + if (!sources->priv->appointment_sources.loaded) + { + calendar_sources_load_sources (sources, + &sources->priv->appointment_sources, + CALENDAR_SOURCES_APPOINTMENT_SOURCES_KEY, + CALENDAR_SOURCES_SELECTED_APPOINTMENT_SOURCES_KEY, + CALENDAR_SOURCES_SELECTED_APPOINTMENT_SOURCES_DIR); + } + + return sources->priv->appointment_sources.clients; +} + +GSList * +calendar_sources_get_task_sources (CalendarSources *sources) +{ + g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL); + + if (!sources->priv->task_sources.loaded) + { + calendar_sources_load_sources (sources, + &sources->priv->task_sources, + CALENDAR_SOURCES_TASK_SOURCES_KEY, + CALENDAR_SOURCES_SELECTED_TASK_SOURCES_KEY, + CALENDAR_SOURCES_SELECTED_TASK_SOURCES_DIR); + } + + return sources->priv->task_sources.clients; +} Index: calendar-sources.h =================================================================== RCS file: calendar-sources.h diff -N calendar-sources.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ calendar-sources.h 12 Jan 2004 17:15:55 -0000 @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2004 Free Software Foundation, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Authors: + * Mark McLoughlin + * William Jon McCann + * Martin Grimme + * Christian Kellner + */ + +#ifndef __CALENDAR_SOURCES_H__ +#define __CALENDAR_SOURCES_H__ + +#include + +G_BEGIN_DECLS + +#define CALENDAR_TYPE_SOURCES (calendar_sources_get_type ()) +#define CALENDAR_SOURCES(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CALENDAR_TYPE_SOURCES, CalendarSources)) +#define CALENDAR_SOURCES_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), CALENDAR_TYPE_SOURCES, CalendarSourcesClass)) +#define CALENDAR_IS_SOURCES(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CALENDAR_TYPE_SOURCES)) +#define CALENDAR_IS_SOURCES_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CALENDAR_TYPE_SOURCES)) +#define CALENDAR_SOURCES_GET_CLASS(o)(G_TYPE_INSTANCE_GET_CLASS ((o), CALENDAR_TYPE_SOURCES, CalendarSourcesClass)) + +typedef struct _CalendarSources CalendarSources; +typedef struct _CalendarSourcesClass CalendarSourcesClass; +typedef struct _CalendarSourcesPrivate CalendarSourcesPrivate; + +struct _CalendarSources +{ + GObject parent; + CalendarSourcesPrivate *priv; +}; + +struct _CalendarSourcesClass +{ + GObjectClass parent_class; + + void (* appointment_sources_changed) (CalendarSources *sources); + void (* task_sources_changed) (CalendarSources *sources); +}; + + +GType calendar_sources_get_type (void) G_GNUC_CONST; +CalendarSources *calendar_sources_get (void); +GSList *calendar_sources_get_appointment_sources (CalendarSources *sources); +GSList *calendar_sources_get_task_sources (CalendarSources *sources); + +G_END_DECLS + +#endif /* __CALENDAR_SOURCES_H__ */ Index: clock.c =================================================================== RCS file: /cvs/gnome/gnome-panel/applets/clock/clock.c,v retrieving revision 1.123 diff -u -p -r1.123 clock.c --- clock.c 7 Jan 2004 00:57:17 -0000 1.123 +++ clock.c 12 Jan 2004 17:15:55 -0000 @@ -29,6 +29,16 @@ * Mark McLoughlin */ + /* + * Evolution calendar integration TODO: + * + Fix treeview scrolling and sizing + * + Tooltips for tasks/appointments + * + Do everything backwards if the clock is on the bottom + * + Double clicking appointments/tasks should open them in evo + * + Consider using different colours for different sources + * + Consider doing a GtkMenu tearoff type thing + */ + #include "config.h" #include @@ -48,6 +58,11 @@ #include #include +#ifdef HAVE_LIBECAL +#include "calendar-client.h" +#include "cut-n-paste/eggcellrenderertext.h" +#endif + #define INTERNETSECOND (864) #define INTERNETBEAT (86400) @@ -88,6 +103,19 @@ struct _ClockData { GtkWidget *toggle; GtkWidget *props; GtkWidget *about; + GtkWidget *calendar_popup; + GtkWidget *calendar; + +#ifdef HAVE_LIBECAL + GtkWidget *task_list; + GtkWidget *appointment_list; + + GtkListStore *appointments_model; + GtkListStore *tasks_model; + GtkTreeModelFilter *tasks_filter; + + CalendarClient *client; +#endif /* HAVE_LIBECAL */ /* preferences */ ClockFormat format; @@ -99,11 +127,12 @@ struct _ClockData { char *config_tool; /* runtime data */ - char *timeformat; - guint timeout; - int timeouttime; - PanelAppletOrient orient; - int size; + time_t current_time; + char *timeformat; + guint timeout; + int timeouttime; + PanelAppletOrient orient; + int size; int fixed_width; int fixed_height; @@ -111,7 +140,7 @@ struct _ClockData { guint listeners [N_GCONF_PREFS]; }; -static void update_clock (ClockData * cd, time_t current_time); +static void update_clock (ClockData * cd); static void set_atk_name_description (GtkWidget *widget, const char *name, @@ -158,15 +187,17 @@ static int clock_timeout_callback (gpointer data) { ClockData *cd = data; - time_t current_time; - time (¤t_time); + update_clock (cd); - update_clock (cd, current_time); +#ifdef HAVE_LIBECAL + if (cd->tasks_filter && cd->task_list) + gtk_tree_model_filter_refilter (cd->tasks_filter); +#endif if (!cd->showseconds && cd->format != CLOCK_FORMAT_UNIX) { if (cd->format != CLOCK_FORMAT_INTERNET) { - int sec = current_time % 60; + int sec = cd->current_time % 60; if (sec != 0 || cd->timeouttime != 60000) { /* ensure next update is exactly on 0 seconds */ cd->timeouttime = (60 - sec)*1000; @@ -181,7 +212,7 @@ clock_timeout_callback (gpointer data) long isec; /* BMT (Biel Mean Time) is GMT+1 */ - bmt = current_time + 3600; + bmt = cd->current_time + 3600; tm = gmtime (&bmt); isec = ((tm->tm_hour*3600 + tm->tm_min*60 + tm->tm_sec)*10) % 864; @@ -326,29 +357,31 @@ add_atk_relation (GtkWidget *widge } static void -update_clock (ClockData * cd, time_t current_time) +update_clock (ClockData * cd) { struct tm *tm; char date[256], hour[256]; char *utf8, *loc; + + time (&cd->current_time); if (cd->gmt_time) - tm = gmtime (¤t_time); + tm = gmtime (&cd->current_time); else - tm = localtime (¤t_time); + tm = localtime (&cd->current_time); if (cd->format == CLOCK_FORMAT_UNIX) { if ((cd->orient == PANEL_APPLET_ORIENT_LEFT || cd->orient == PANEL_APPLET_ORIENT_RIGHT) && cd->size >= GNOME_Vertigo_PANEL_MEDIUM) { g_snprintf (hour, sizeof(hour), "%lu\n%05lu", - (unsigned long)(current_time / 100000L), - (unsigned long)(current_time % 100000L)); + (unsigned long)(cd->current_time / 100000L), + (unsigned long)(cd->current_time % 100000L)); } else { - g_snprintf (hour, sizeof(hour), "%lu", (unsigned long)current_time); + g_snprintf (hour, sizeof(hour), "%lu", (unsigned long)cd->current_time); } } else if (cd->format == CLOCK_FORMAT_INTERNET) { - float itime = get_itime (current_time); + float itime = get_itime (cd->current_time); if (cd->showseconds) g_snprintf (hour, sizeof (hour), "@%3.2f", itime); else @@ -386,19 +419,13 @@ update_clock (ClockData * cd, time_t cur static void refresh_clock (ClockData *cd) { - time_t current_time; - unfix_size (cd); - - time (¤t_time); - update_clock (cd, current_time); + update_clock (cd); } static void refresh_clock_timeout(ClockData *cd) { - time_t current_time; - unfix_size (cd); update_timeformat (cd); @@ -406,8 +433,7 @@ refresh_clock_timeout(ClockData *cd) if (cd->timeout) g_source_remove (cd->timeout); - time (¤t_time); - update_clock (cd, current_time); + update_clock (cd); if (cd->format == CLOCK_FORMAT_INTERNET) { if (cd->showseconds) @@ -418,7 +444,7 @@ refresh_clock_timeout(ClockData *cd) long isec; /* BMT (Biel Mean Time) is GMT+1 */ - bmt = current_time + 3600; + bmt = cd->current_time + 3600; tm = gmtime (&bmt); isec = ((tm->tm_hour*3600 + tm->tm_min*60 + tm->tm_sec)*10) % 864; cd->timeouttime = (864 - isec)*100; @@ -429,7 +455,7 @@ refresh_clock_timeout(ClockData *cd) cd->showseconds) cd->timeouttime = 1000; else - cd->timeouttime = (60 - current_time % 60)*1000; + cd->timeouttime = (60 - cd->current_time % 60)*1000; cd->timeout = g_timeout_add (cd->timeouttime, clock_timeout_callback, @@ -450,18 +476,35 @@ destroy_clock(GtkWidget * widget, ClockD g_object_unref (G_OBJECT (client)); - if (cd->timeout > 0) { + if (cd->timeout) g_source_remove (cd->timeout); - cd->timeout = 0; - } + cd->timeout = 0; + +#ifdef HAVE_LIBECAL + if (cd->client) + g_object_unref (cd->client); + cd->client = NULL; + + if (cd->appointments_model) + g_object_unref (cd->appointments_model); + cd->appointments_model = NULL; + + if (cd->tasks_model) + g_object_unref (cd->tasks_model); + cd->tasks_model = NULL; + + if (cd->tasks_filter) + g_object_unref (cd->tasks_filter); + cd->tasks_filter = NULL; +#endif /* HAVE_LIBECAL */ if (cd->about) gtk_widget_destroy (cd->about); + cd->about = NULL; - if (cd->props) { + if (cd->props) gtk_widget_destroy (cd->props); - cd->props = NULL; - } + cd->props = NULL; g_free (cd->timeformat); g_free (cd->config_tool); @@ -491,15 +534,665 @@ delete_event (GtkWidget *widget, return TRUE; } +static inline ClockFormat +clock_locale_format (void) +{ + const char *am; + + am = nl_langinfo (AM_STR); + return (am[0] == '\0') ? CLOCK_FORMAT_24 : CLOCK_FORMAT_12; +} + +#ifdef HAVE_LIBECAL + +static void +update_frame_visibility (GtkWidget *frame, + GtkTreeModel *model) +{ + GtkTreeIter iter; + gboolean model_empty; + + if (!frame) + return; + + model_empty = !gtk_tree_model_get_iter_first (model, &iter); + + if (model_empty) + gtk_widget_hide (frame); + else + gtk_widget_show (frame); +} + +enum { + APPOINTMENT_COLUMN_UID, + APPOINTMENT_COLUMN_SUMMARY, + APPOINTMENT_COLUMN_DESCRIPTION, + APPOINTMENT_COLUMN_START_TIME, + APPOINTMENT_COLUMN_START_TEXT, + APPOINTMENT_COLUMN_END_TIME, + APPOINTMENT_COLUMN_ALL_DAY, + N_APPOINTMENT_COLUMNS +}; + +enum { + TASK_COLUMN_UID, + TASK_COLUMN_SUMMARY, + TASK_COLUMN_DESCRIPTION, + TASK_COLUMN_START_TIME, + TASK_COLUMN_DUE_TIME, + TASK_COLUMN_PERCENT_COMPLETE, + TASK_COLUMN_PERCENT_COMPLETE_TEXT, + TASK_COLUMN_COMPLETED, + TASK_COLUMN_COMPLETED_TIME, + TASK_COLUMN_OVERDUE_ATTR, + N_TASK_COLUMNS +}; + +static char * +format_time (ClockFormat format, + time_t t) +{ + struct tm *tm; + char *time_format; + char result [256] = { 0, }; + + if (!t) + return NULL; + + tm = localtime (&t); + if (!tm) + return NULL; + + if (format != CLOCK_FORMAT_12 && format != CLOCK_FORMAT_24) + format = clock_locale_format (); + + if (format == CLOCK_FORMAT_12) + time_format = g_locale_from_utf8 (_("%l:%M %p"), -1, NULL, NULL, NULL); + else + time_format = g_locale_from_utf8 (_("%H:%M"), -1, NULL, NULL, NULL); + + strftime (result, sizeof (result), time_format, tm); + + g_free (time_format); + + return g_strdup (result); +} + +static void +handle_tasks_changed (ClockData *cd) +{ + GSList *events, *l; + + gtk_list_store_clear (cd->tasks_model); + + events = calendar_client_get_events (cd->client, CALENDAR_EVENT_TASK); + for (l = events; l; l = l->next) { + CalendarTask *task = l->data; + GtkTreeIter iter; + char *percent_complete_text; + + g_assert (CALENDAR_EVENT (task)->type == CALENDAR_EVENT_TASK); + + /* FIXME: should this format be locale specific ? */ + percent_complete_text = g_strdup_printf ("%d%%", task->percent_complete); + + gtk_list_store_append (cd->tasks_model, &iter); + gtk_list_store_set (cd->tasks_model, &iter, + TASK_COLUMN_UID, task->uid, + TASK_COLUMN_SUMMARY, task->summary, + TASK_COLUMN_DESCRIPTION, task->description, + TASK_COLUMN_START_TIME, task->start_time, + TASK_COLUMN_DUE_TIME, task->due_time, + TASK_COLUMN_PERCENT_COMPLETE, task->percent_complete, + TASK_COLUMN_PERCENT_COMPLETE_TEXT, percent_complete_text, + TASK_COLUMN_COMPLETED, task->percent_complete == 100, + TASK_COLUMN_COMPLETED_TIME, task->completed_time, + -1); + + g_free (percent_complete_text); + calendar_event_free (CALENDAR_EVENT (task)); + } + g_slist_free (events); + + update_frame_visibility (cd->task_list, GTK_TREE_MODEL (cd->tasks_filter)); +} + +static void +handle_task_completed_toggled (ClockData *cd, + const char *path_str, + GtkCellRendererToggle *cell) +{ + GtkTreePath *path; + GtkTreeIter iter; + char *uid; + gboolean task_completed; + guint percent_complete; + + path = gtk_tree_path_new_from_string (path_str); + gtk_tree_model_get_iter (GTK_TREE_MODEL (cd->tasks_model), &iter, path); + gtk_tree_model_get (GTK_TREE_MODEL (cd->tasks_model), &iter, + TASK_COLUMN_UID, &uid, + TASK_COLUMN_COMPLETED, &task_completed, + TASK_COLUMN_PERCENT_COMPLETE, &percent_complete, + -1); + + task_completed = !task_completed; + percent_complete = task_completed ? 100 : 0; + + calendar_client_set_task_completed (cd->client, + uid, + task_completed, + percent_complete); + + g_free (uid); + gtk_tree_path_free (path); +} + +static void +handle_task_percent_complete_edited (ClockData *cd, + const char *path_str, + const char *text, + GtkCellRendererText *cell) +{ + GtkTreePath *path; + GtkTreeIter iter; + char *uid; + int percent_complete; + char *error = NULL; + + path = gtk_tree_path_new_from_string (path_str); + gtk_tree_model_get_iter (GTK_TREE_MODEL (cd->tasks_model), &iter, path); + gtk_tree_model_get (GTK_TREE_MODEL (cd->tasks_model), &iter, + TASK_COLUMN_UID, &uid, + -1); + + percent_complete = (int) g_strtod (text, &error); + if (!error || !error [0]) { + gboolean task_completed; + + percent_complete = CLAMP (percent_complete, 0, 100); + task_completed = (percent_complete == 100); + + calendar_client_set_task_completed (cd->client, + uid, + task_completed, + percent_complete); + } + + g_free (uid); + gtk_tree_path_free (path); +} + +static gboolean +filter_out_tasks (GtkTreeModel *model, + GtkTreeIter *iter, + ClockData *cd) +{ + GTime start_time; + GTime completed_time; + GTime one_day_ago; + gboolean visible; + + gtk_tree_model_get (model, iter, + TASK_COLUMN_START_TIME, &start_time, + TASK_COLUMN_COMPLETED_TIME, &completed_time, + -1); + + one_day_ago = cd->current_time - (24 * 60 * 60); + + visible = !start_time || start_time <= cd->current_time; + if (visible) + visible = !completed_time || completed_time >= one_day_ago; + + return visible; +} + +static void +modify_task_text_attributes (GtkTreeModel *model, + GtkTreeIter *iter, + GValue *value, + gint column, + ClockData *cd) +{ + GTime due_time; + PangoAttrList *attr_list; + PangoAttribute *attr; + GtkTreeIter child_iter; + + gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), + &child_iter, + iter); + + if (column != TASK_COLUMN_OVERDUE_ATTR) { + memset (value, 0, sizeof (GValue)); + gtk_tree_model_get_value (GTK_TREE_MODEL (cd->tasks_model), + &child_iter, column, value); + + return; + } + + gtk_tree_model_get (GTK_TREE_MODEL (cd->tasks_model), &child_iter, + TASK_COLUMN_DUE_TIME, &due_time, + -1); + if (due_time && due_time > cd->current_time) + return; + + attr_list = pango_attr_list_new (); + + attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD); + attr->start_index = 0; + attr->end_index = G_MAXINT; + pango_attr_list_insert (attr_list, attr); + + g_value_take_boxed (value, attr_list); +} + +static GtkWidget * +create_hig_frame (const char *title) +{ + GtkWidget *vbox; + GtkWidget *alignment; + GtkWidget *label; + char *bold_title; + + vbox = gtk_vbox_new (FALSE, 6); + + bold_title = g_strdup_printf ("%s", title); + + alignment = gtk_alignment_new (0, 0.5, 0, 0); + gtk_box_pack_start (GTK_BOX (vbox), alignment, FALSE, FALSE, 0); + gtk_widget_show (alignment); + + label = gtk_label_new (bold_title); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_container_add (GTK_CONTAINER (alignment), label); + gtk_widget_show (label); + + g_free (bold_title); + + return vbox; +} + +static GtkWidget * +create_task_list (ClockData *cd, + GtkWidget **tree_view) +{ + GtkWidget *vbox; + GtkWidget *scrolled_window; + GtkWidget *view; + GtkCellRenderer *cell; + GtkTreeViewColumn *column; + + vbox = create_hig_frame (_("Tasks")); + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_SHADOW_NONE); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (vbox), scrolled_window, TRUE, TRUE, 0); + gtk_widget_show (scrolled_window); + + if (!cd->tasks_model) { + GType column_types [N_TASK_COLUMNS] = { + G_TYPE_STRING, /* uid */ + G_TYPE_STRING, /* summary */ + G_TYPE_STRING, /* description */ + G_TYPE_LONG, /* start time */ + G_TYPE_LONG, /* due time */ + G_TYPE_UINT, /* percent complete */ + G_TYPE_STRING, /* percent complete text */ + G_TYPE_BOOLEAN, /* completed */ + G_TYPE_LONG, /* completed time */ + PANGO_TYPE_ATTR_LIST /* summary text attributes */ + }; + + cd->tasks_model = gtk_list_store_newv (N_TASK_COLUMNS, column_types); + + cd->tasks_filter = GTK_TREE_MODEL_FILTER ( + gtk_tree_model_filter_new (GTK_TREE_MODEL (cd->tasks_model), + NULL)); + gtk_tree_model_filter_set_visible_func ( + cd->tasks_filter, + (GtkTreeModelFilterVisibleFunc) filter_out_tasks, + cd, + NULL); + gtk_tree_model_filter_set_modify_func ( + cd->tasks_filter, + N_TASK_COLUMNS, + column_types, + (GtkTreeModelFilterModifyFunc) modify_task_text_attributes, + cd, + NULL); + } + + /* FIXME: implement sorting */ + + *tree_view = view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (cd->tasks_filter)); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE); + + /* Completed toggle */ + column = gtk_tree_view_column_new (); + cell = gtk_cell_renderer_toggle_new (); + g_object_set (cell, + "activatable", TRUE, + NULL); + g_signal_connect_swapped (cell, "toggled", + G_CALLBACK (handle_task_completed_toggled), cd); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_add_attribute (column, cell, + "active", TASK_COLUMN_COMPLETED); + gtk_tree_view_append_column (GTK_TREE_VIEW (view), column); + + /* Percent complete */ + column = gtk_tree_view_column_new (); + cell = gtk_cell_renderer_text_new (); + g_object_set (cell, + "editable", TRUE, + NULL); + g_signal_connect_swapped (cell, "edited", + G_CALLBACK (handle_task_percent_complete_edited), cd); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_add_attribute (column, cell, + "text", TASK_COLUMN_PERCENT_COMPLETE_TEXT); + gtk_tree_view_append_column (GTK_TREE_VIEW (view), column); + + /* Summary */ + column = gtk_tree_view_column_new (); + cell = egg_cell_renderer_text_new (); + egg_cell_renderer_text_set_ellipsize (EGG_CELL_RENDERER_TEXT (cell), TRUE); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_set_attributes (column, cell, + "text", TASK_COLUMN_SUMMARY, + "strikethrough", TASK_COLUMN_COMPLETED, + "attributes", TASK_COLUMN_OVERDUE_ATTR, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (view), column); + + gtk_container_add (GTK_CONTAINER (scrolled_window), view); + gtk_widget_show (view); + + return vbox; +} + +static void +mark_day_on_calendar (CalendarClient *client, + guint day, + ClockData *cd) +{ + gtk_calendar_mark_day (GTK_CALENDAR (cd->calendar), day); +} + +static void +handle_appointments_changed (ClockData *cd) +{ + GSList *events, *l; + + if (cd->calendar) { + gtk_calendar_clear_marks (GTK_CALENDAR (cd->calendar)); + + calendar_client_foreach_appointment_day (cd->client, + (CalendarDayIter) mark_day_on_calendar, + cd); + } + + gtk_list_store_clear (cd->appointments_model); + + events = calendar_client_get_events (cd->client, CALENDAR_EVENT_APPOINTMENT); + for (l = events; l; l = l->next) { + CalendarAppointment *appointment = l->data; + GtkTreeIter iter; + char *start_text; + + g_assert (CALENDAR_EVENT (appointment)->type == CALENDAR_EVENT_APPOINTMENT); + + if (!appointment->is_all_day) + start_text = format_time (cd->format, + appointment->start_time); + else + start_text = g_strdup (_("All Day")); + + gtk_list_store_append (cd->appointments_model, &iter); + gtk_list_store_set (cd->appointments_model, &iter, + APPOINTMENT_COLUMN_UID, appointment->uid, + APPOINTMENT_COLUMN_SUMMARY, appointment->summary, + APPOINTMENT_COLUMN_DESCRIPTION, appointment->description, + APPOINTMENT_COLUMN_START_TIME, appointment->start_time, + APPOINTMENT_COLUMN_START_TEXT, start_text, + APPOINTMENT_COLUMN_END_TIME, appointment->end_time, + APPOINTMENT_COLUMN_ALL_DAY, appointment->is_all_day, + -1); + + g_free (start_text); + calendar_event_free (CALENDAR_EVENT (appointment)); + } + g_slist_free (events); + + update_frame_visibility (cd->appointment_list, GTK_TREE_MODEL (cd->appointments_model)); +} + +static GtkWidget * +create_appointment_list (ClockData *cd, + GtkWidget **tree_view) +{ + GtkWidget *vbox; + GtkWidget *scrolled_window; + GtkWidget *view; + GtkCellRenderer *cell; + GtkTreeViewColumn *column; + + vbox = create_hig_frame ( _("Appointments")); + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_SHADOW_NONE); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (vbox), scrolled_window, TRUE, TRUE, 0); + gtk_widget_show (scrolled_window); + + if (!cd->appointments_model) { + cd->appointments_model = + gtk_list_store_new (N_APPOINTMENT_COLUMNS, + G_TYPE_STRING, /* uid */ + G_TYPE_STRING, /* summary */ + G_TYPE_STRING, /* description */ + G_TYPE_LONG, /* start time */ + G_TYPE_STRING, /* start time text */ + G_TYPE_LONG, /* end time */ + G_TYPE_BOOLEAN); /* all day */ + + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (cd->appointments_model), + APPOINTMENT_COLUMN_START_TIME, + GTK_SORT_ASCENDING); + + } + + *tree_view = view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (cd->appointments_model)); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE); + + /* Start time */ + column = gtk_tree_view_column_new (); + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_add_attribute (column, cell, + "text", APPOINTMENT_COLUMN_START_TEXT); + gtk_tree_view_append_column (GTK_TREE_VIEW (view), column); + + /* Summary */ + column = gtk_tree_view_column_new (); + cell = egg_cell_renderer_text_new (); + egg_cell_renderer_text_set_ellipsize (EGG_CELL_RENDERER_TEXT (cell), TRUE); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_add_attribute (column, cell, + "text", APPOINTMENT_COLUMN_SUMMARY); + gtk_tree_view_append_column (GTK_TREE_VIEW (view), column); + + gtk_container_add (GTK_CONTAINER (scrolled_window), view); + gtk_widget_show (view); + + return vbox; +} + +static void +calendar_day_activated (ClockData *cd) +{ + /* FIXME: should be able to launch the editor for + * the specific day + */ + calendar_client_launch_editor (cd->client, + CALENDAR_EVENT_APPOINTMENT, + gtk_widget_get_screen (cd->calendar), + NULL); +} + +static void +calendar_day_selected (ClockData *cd) +{ + guint day; + + gtk_calendar_get_date (GTK_CALENDAR (cd->calendar), NULL, NULL, &day); + + calendar_client_select_day (cd->client, day); + + handle_appointments_changed (cd); + handle_tasks_changed (cd); +} + +static void +calendar_month_selected (ClockData *cd) +{ + guint year, month; + + gtk_calendar_get_date (GTK_CALENDAR (cd->calendar), &year, &month, NULL); + + calendar_client_select_month (cd->client, month, year); + + handle_appointments_changed (cd); + handle_tasks_changed (cd); +} + + +/* FIXME: all this is a terrible hack */ +typedef struct +{ + GtkWidget *calendar; + GtkWidget *tree; +} ConstraintData; + +static void +constrain_list_size (GtkWidget *widget, + GtkRequisition *requisition, + ConstraintData *constraint) +{ + GtkRequisition req; + int max_height; + + /* constrain width to the calendar width */ + gtk_widget_size_request (constraint->calendar, &req); + /* g_print ("width: MIN (width = %d, calendar width = %d)\n", requisition->width, req.width); */ + requisition->width = MIN (requisition->width, req.width); + + /* constrain height to be the tree height up to a max */ + max_height = (gdk_screen_get_height (gtk_widget_get_screen (widget)) - req.height) / 3; + gtk_widget_size_request (constraint->tree, &req); + /* g_print ("height: MIN (tree height = %d, max_height = %d) old = %d\n", req.height, max_height, requisition->height); */ + requisition->height = MAX (requisition->height, req.height); + requisition->height = MIN (requisition->height, max_height); + requisition->height += 1; +} + +static void +setup_list_size_constraint (GtkWidget *widget, + GtkWidget *calendar, + GtkWidget *tree) +{ + ConstraintData *constraint; + + constraint = g_new0 (ConstraintData, 1); + constraint->calendar = calendar; + constraint->tree = tree; + + g_signal_connect_data (widget, "size-request", + G_CALLBACK (constrain_list_size), constraint, + (GClosureNotify) g_free, 0); +} + +#endif /* HAVE_LIBECAL */ + + +static void +add_appointments_and_tasks (ClockData *cd, + GtkWidget *vbox) +{ +#ifdef HAVE_LIBECAL + GtkWidget *tree_view; + guint year, month, day; + + cd->task_list = create_task_list (cd, &tree_view); + g_object_add_weak_pointer (G_OBJECT (cd->task_list), + (gpointer *) &cd->task_list); + gtk_box_pack_start (GTK_BOX (vbox), cd->task_list, TRUE, TRUE, 0); + setup_list_size_constraint (cd->task_list, cd->calendar, tree_view); + update_frame_visibility (cd->task_list, GTK_TREE_MODEL (cd->tasks_model)); + + cd->appointment_list = create_appointment_list (cd, &tree_view); + g_object_add_weak_pointer (G_OBJECT (cd->appointment_list), + (gpointer *) &cd->appointment_list); + gtk_box_pack_start (GTK_BOX (vbox), cd->appointment_list, TRUE, TRUE, 0); + setup_list_size_constraint (cd->appointment_list, cd->calendar, tree_view); + update_frame_visibility (cd->appointment_list, GTK_TREE_MODEL (cd->appointments_model)); + + if (!cd->client) { + cd->client = calendar_client_new (); + + g_signal_connect_swapped (cd->client, "tasks-changed", + G_CALLBACK (handle_tasks_changed), cd); + g_signal_connect_swapped (cd->client, "appointments-changed", + G_CALLBACK (handle_appointments_changed), cd); + } + + gtk_calendar_get_date (GTK_CALENDAR (cd->calendar), &year, &month, &day); + + calendar_client_select_day (cd->client, day); + calendar_client_select_month (cd->client, month, year); + + handle_tasks_changed (cd); + handle_appointments_changed (cd); + + g_signal_connect_swapped (cd->calendar, "day-selected-double-click", + G_CALLBACK (calendar_day_activated), cd); + g_signal_connect_swapped (cd->calendar, "day-selected", + G_CALLBACK (calendar_day_selected), cd); + g_signal_connect_swapped (cd->calendar, "month-changed", + G_CALLBACK (calendar_month_selected), cd); +#endif /* HAVE_LIBECAL */ +} + static GtkWidget * create_calendar (ClockData *cd, GdkScreen *screen) { - GtkWindow *window; - GtkWidget *calendar; - GtkCalendarDisplayOptions options; + GtkWindow *window; + GtkWidget *frame; + GtkWidget *vbox; + GtkCalendarDisplayOptions options; + struct tm *tm; window = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL)); + gtk_window_set_screen (window, screen); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN); + gtk_container_add (GTK_CONTAINER (window), frame); + gtk_widget_show (frame); + + vbox = gtk_vbox_new (FALSE, 6); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 6); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); gtk_window_set_type_hint (window, GDK_WINDOW_TYPE_HINT_DOCK); gtk_window_set_decorated (window, FALSE); @@ -513,29 +1206,24 @@ create_calendar (ClockData *cd, g_signal_connect (window, "key_press_event", G_CALLBACK (close_on_escape), cd); - calendar = gtk_calendar_new (); + cd->calendar = gtk_calendar_new (); + g_object_add_weak_pointer (G_OBJECT (cd->calendar), + (gpointer *) &cd->calendar); - options = gtk_calendar_get_display_options (GTK_CALENDAR (calendar)); + options = gtk_calendar_get_display_options (GTK_CALENDAR (cd->calendar)); options |= GTK_CALENDAR_SHOW_WEEK_NUMBERS; - gtk_calendar_set_display_options (GTK_CALENDAR (calendar), options); + gtk_calendar_set_display_options (GTK_CALENDAR (cd->calendar), options); - if (cd->gmt_time && - (cd->format != CLOCK_FORMAT_UNIX || - cd->format != CLOCK_FORMAT_INTERNET)) { - time_t current_time; - struct tm *tm; - - time (¤t_time); - tm = gmtime (¤t_time); - gtk_calendar_select_month (GTK_CALENDAR (calendar), - tm->tm_mon, - tm->tm_year + 1900); - gtk_calendar_select_day (GTK_CALENDAR (calendar), tm->tm_mday); - } + tm = localtime (&cd->current_time); + gtk_calendar_select_month (GTK_CALENDAR (cd->calendar), + tm->tm_mon, + tm->tm_year + 1900); + gtk_calendar_select_day (GTK_CALENDAR (cd->calendar), tm->tm_mday); - gtk_container_add (GTK_CONTAINER (window), calendar); + gtk_box_pack_start (GTK_BOX (vbox), cd->calendar, TRUE, FALSE, 0); + gtk_widget_show (cd->calendar); - gtk_widget_show (calendar); + add_appointments_and_tasks (cd, vbox); return GTK_WIDGET (window); } @@ -622,31 +1310,21 @@ present_calendar_popup (ClockData *cd, static void update_popup (ClockData *cd) { - GtkWidget *window; - GtkWidget *button; - - button = cd->toggle; - - window = g_object_get_data (G_OBJECT (button), "calendar"); - - if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) { - if (!window) { - window = create_calendar (cd, gtk_widget_get_screen (cd->applet)); - - g_object_set_data_full ( - G_OBJECT (button), "calendar", - window, (GDestroyNotify) gtk_widget_destroy); - } - } else { - if (window) { - /* Destroys the calendar */ - g_object_set_data (G_OBJECT (button), "calendar", NULL); - window = NULL; - } - } + if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (cd->toggle))) { + if (cd->calendar_popup) + gtk_widget_destroy (cd->calendar_popup); + cd->calendar_popup = NULL; + return; + } + + if (!cd->calendar_popup) { + cd->calendar_popup = create_calendar (cd, gtk_widget_get_screen (cd->applet)); + g_object_add_weak_pointer (G_OBJECT (cd->calendar_popup), + (gpointer *) &cd->calendar_popup); + } - if (window && GTK_WIDGET_REALIZED (button)) - present_calendar_popup (cd, window, button); + if (cd->calendar_popup && GTK_WIDGET_REALIZED (cd->toggle)) + present_calendar_popup (cd, cd->calendar_popup, cd->toggle); } static void @@ -746,11 +1424,9 @@ applet_change_orient (PanelApplet PanelAppletOrient orient, ClockData *cd) { - time_t current_time; - - time (¤t_time); cd->orient = orient; - update_clock (cd, current_time); + + update_clock (cd); update_popup (cd); } @@ -785,30 +1461,25 @@ applet_change_pixel_size (PanelApplet *a gint size, ClockData *cd) { - time_t current_time; - - time (¤t_time); cd->size = size; update_timeformat (cd); - update_clock (cd, current_time); + update_clock (cd); } - static void copy_time (BonoboUIComponent *uic, ClockData *cd, const gchar *verbname) { - time_t current_time = time (NULL); char string[256]; char *utf8; if (cd->format == CLOCK_FORMAT_UNIX) { g_snprintf (string, sizeof(string), "%lu", - (unsigned long)current_time); + (unsigned long)cd->current_time); } else if (cd->format == CLOCK_FORMAT_INTERNET) { - float itime = get_itime (current_time); + float itime = get_itime (cd->current_time); if (cd->showseconds) g_snprintf (string, sizeof (string), "@%3.2f", itime); else @@ -833,9 +1504,9 @@ copy_time (BonoboUIComponent *uic, } if (cd->gmt_time) - tm = gmtime (¤t_time); + tm = gmtime (&cd->current_time); else - tm = localtime (¤t_time); + tm = localtime (&cd->current_time); if (!format) strcpy (string, "???"); @@ -855,15 +1526,14 @@ copy_date (BonoboUIComponent *uic, ClockData *cd, const gchar *verbname) { - time_t current_time = time (NULL); struct tm *tm; char string[256]; char *utf8, *loc; if (cd->gmt_time) - tm = gmtime (¤t_time); + tm = gmtime (&cd->current_time); else - tm = localtime (¤t_time); + tm = localtime (&cd->current_time); loc = g_locale_from_utf8 (_("%A, %B %d %Y"), -1, NULL, NULL, NULL); if (!loc) @@ -1180,15 +1850,6 @@ clock_migrate_to_26 (ClockData *clock) NULL); } -static inline ClockFormat -clock_locale_format (void) -{ - const char *am; - - am = nl_langinfo (AM_STR); - return (am[0] == '\0') ? CLOCK_FORMAT_24 : CLOCK_FORMAT_12; -} - static gboolean fill_clock_applet (PanelApplet *applet) { @@ -1862,8 +2523,9 @@ clock_factory (PanelApplet *applet, return retval; } -PANEL_APPLET_BONOBO_SHLIB_FACTORY ("OAFIID:GNOME_ClockApplet_Factory", - PANEL_TYPE_APPLET, - "Clock Applet factory", - clock_factory, NULL); - +PANEL_APPLET_BONOBO_FACTORY ("OAFIID:GNOME_ClockApplet_Factory", + PANEL_TYPE_APPLET, + "Clock Applet factory", + "0", + clock_factory, + NULL); Index: cut-n-paste/.cvsignore =================================================================== RCS file: cut-n-paste/.cvsignore diff -N cut-n-paste/.cvsignore --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ cut-n-paste/.cvsignore 12 Jan 2004 17:15:55 -0000 @@ -0,0 +1,9 @@ +Makefile.in +Makefile +.deps +.libs +*.lo +*.la +libeelcnp.la +eggmarshalers.c +eggmarshalers.h Index: cut-n-paste/Makefile.am =================================================================== RCS file: cut-n-paste/Makefile.am diff -N cut-n-paste/Makefile.am --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ cut-n-paste/Makefile.am 12 Jan 2004 17:15:55 -0000 @@ -0,0 +1,30 @@ +INCLUDES = $(WARN_CFLAGS) $(CLOCK_CFLAGS) -DEEL_OMIT_SELF_CHECK + +noinst_LTLIBRARIES = libclockcnp.la + +libclockcnp_la_SOURCES = \ + eggcellrenderertext.h \ + eggcellrenderertext.c \ + $(EELFILES) \ + eggmarshalers.h \ + eggmarshalers.c + +eggmarshalers.h: eggmarshalers.list $(GLIB_GENMARSHAL) + $(GLIB_GENMARSHAL) $< --header --prefix=_egg_marshal > $@ + +eggmarshalers.c: eggmarshalers.list $(GLIB_GENMARSHAL) + echo "#include \"eggmarshalers.h\"" > $@ && \ + $(GLIB_GENMARSHAL) $< --body --prefix=_egg_marshal >> $@ + +BUILT_SOURCES = eggmarshalers.c eggmarshalers.h + +EXTRA_DIST = eggmarshalers.list + +EELFILES = \ + eel-pango-extensions.h \ + eel-pango-extensions.c + +EELDIR = $(srcdir)/../../../../eel/eel + +regenerate-built-sources: + EELFILES="$(EELFILES)" EELDIR="$(EELDIR)" $(srcdir)/update-from-eel.sh Index: cut-n-paste/eel-pango-extensions.c =================================================================== RCS file: cut-n-paste/eel-pango-extensions.c diff -N cut-n-paste/eel-pango-extensions.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ cut-n-paste/eel-pango-extensions.c 12 Jan 2004 17:15:55 -0000 @@ -0,0 +1,684 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* eel-pango-extensions.h - interface for new functions that conceptually + belong in pango. Perhaps some of these will be + actually rolled into pango someday. + + Copyright (C) 2001 Anders Carlsson + + The Eel Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Eel Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Eel Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: Anders Carlsson +*/ + +#include +#include "eel-pango-extensions.h" + +#if !defined (EEL_OMIT_SELF_CHECK) +#include "eel-lib-self-check-functions.h" +#endif + +#include +#include +#include +#include +#include +#include + +PangoAttrList * +eel_pango_attr_list_copy_or_create (PangoAttrList *attr_list) +{ + if (attr_list != NULL) { + return pango_attr_list_copy (attr_list); + } + return pango_attr_list_new (); +} + +PangoAttrList * +eel_pango_attr_list_apply_global_attribute (PangoAttrList *attr_list, + PangoAttribute *attr) +{ + PangoAttrList *new_attr_list; + + g_return_val_if_fail (attr != NULL, NULL); + + attr->start_index = 0; + attr->end_index = G_MAXINT; + + new_attr_list = eel_pango_attr_list_copy_or_create (attr_list); + pango_attr_list_change (new_attr_list, attr); + return new_attr_list; +} + +static void +apply_global_attribute (PangoLayout *layout, PangoAttribute *attr) +{ + PangoAttrList *attr_list; + PangoAttrList *old_attr_list; + + old_attr_list = pango_layout_get_attributes (layout); + attr_list = eel_pango_attr_list_apply_global_attribute (old_attr_list, attr); + pango_layout_set_attributes (layout, attr_list); + pango_attr_list_unref (attr_list); +} + +void +eel_pango_layout_set_underline (PangoLayout *layout, PangoUnderline underline) +{ + apply_global_attribute (layout, pango_attr_underline_new (underline)); +} + +void +eel_pango_layout_set_weight (PangoLayout *layout, PangoWeight weight) +{ + apply_global_attribute (layout, pango_attr_weight_new (weight)); +} + +void +eel_pango_layout_set_font_desc_from_string (PangoLayout *layout, const char *str) +{ + PangoFontDescription *desc; + + desc = pango_font_description_from_string (str); + pango_layout_set_font_description (layout, desc); + pango_font_description_free (desc); +} + + + +#define ELLIPSIS "..." + +/* Caution: this is an _expensive_ function */ +static int +measure_string_width (const char *string, + PangoLayout *layout) +{ + int width; + + pango_layout_set_text (layout, string, -1); + pango_layout_get_pixel_size (layout, &width, NULL); + + return width; +} + +/* this is also plenty slow */ +static void +compute_character_widths (const char *string, + PangoLayout *layout, + int *char_len_return, + int **widths_return, + int **cuts_return) +{ + int *widths; + int *offsets; + int *cuts; + int char_len; + int byte_len; + const char *p; + int i; + PangoLayoutIter *iter; + PangoLogAttr *attrs; + +#define BEGINS_UTF8_CHAR(x) (((x) & 0xc0) != 0x80) + + char_len = g_utf8_strlen (string, -1); + byte_len = strlen (string); + + widths = g_new (int, char_len); + offsets = g_new (int, byte_len); + + /* Create a translation table from byte index to char offset */ + p = string; + i = 0; + while (*p) { + int byte_index = p - string; + + if (BEGINS_UTF8_CHAR (*p)) { + offsets[byte_index] = i; + ++i; + } else { + offsets[byte_index] = G_MAXINT; /* segv if we try to use this */ + } + + ++p; + } + + /* Now fill in the widths array */ + pango_layout_set_text (layout, string, -1); + + iter = pango_layout_get_iter (layout); + + do { + PangoRectangle extents; + int byte_index; + + byte_index = pango_layout_iter_get_index (iter); + + if (byte_index < byte_len) { + pango_layout_iter_get_char_extents (iter, &extents); + + g_assert (BEGINS_UTF8_CHAR (string[byte_index])); + g_assert (offsets[byte_index] < char_len); + + widths[offsets[byte_index]] = PANGO_PIXELS (extents.width); + } + + } while (pango_layout_iter_next_char (iter)); + + pango_layout_iter_free (iter); + + g_free (offsets); + + *widths_return = widths; + + /* Now compute character offsets that are legitimate places to + * chop the string + */ + attrs = g_new (PangoLogAttr, char_len + 1); + + pango_get_log_attrs (string, byte_len, -1, + pango_context_get_language ( + pango_layout_get_context (layout)), + attrs, + char_len + 1); + + cuts = g_new (int, char_len); + i = 0; + while (i < char_len) { + cuts[i] = attrs[i].is_cursor_position; + + ++i; + } + + g_free (attrs); + + *cuts_return = cuts; + + *char_len_return = char_len; +} + + +static char * +eel_string_ellipsize_start (const char *string, PangoLayout *layout, int width) +{ + int resulting_width; + int *cuts; + int *widths; + int char_len; + const char *p; + int truncate_offset; + + /* Zero-length string can't get shorter - catch this here to + * avoid expensive calculations + */ + if (*string == '\0') + return g_strdup (""); + + /* I'm not sure if this short-circuit is a net win; it might be better + * to just dump this, and always do the compute_character_widths() etc. + * down below. + */ + resulting_width = measure_string_width (string, layout); + + if (resulting_width <= width) { + /* String is already short enough. */ + return g_strdup (string); + } + + /* Remove width of an ellipsis */ + width -= measure_string_width (ELLIPSIS, layout); + + if (width < 0) { + /* No room even for an ellipsis. */ + return g_strdup (""); + } + + /* Our algorithm involves removing enough chars from the string to bring + * the width to the required small size. However, due to ligatures, + * combining characters, etc., it's not guaranteed that the algorithm + * always works 100%. It's sort of a heuristic thing. It should work + * nearly all the time... but I wouldn't put in + * g_assert (width of resulting string < width). + * + * Hmm, another thing that this breaks with is explicit line breaks + * in "string" + */ + + compute_character_widths (string, layout, &char_len, &widths, &cuts); + + for (truncate_offset = 1; truncate_offset < char_len; truncate_offset++) { + + resulting_width -= widths[truncate_offset]; + + if (resulting_width <= width && + cuts[truncate_offset]) { + break; + } + } + + g_free (cuts); + g_free (widths); + + p = g_utf8_offset_to_pointer (string, truncate_offset); + + return g_strconcat (ELLIPSIS, p, NULL); +} + +static char * +eel_string_ellipsize_end (const char *string, PangoLayout *layout, int width) +{ + int resulting_width; + int *cuts; + int *widths; + int char_len; + const char *p; + int truncate_offset; + char *result; + + /* See explanatory comments in ellipsize_start */ + + if (*string == '\0') + return g_strdup (""); + + resulting_width = measure_string_width (string, layout); + + if (resulting_width <= width) { + return g_strdup (string); + } + + width -= measure_string_width (ELLIPSIS, layout); + + if (width < 0) { + return g_strdup (""); + } + + compute_character_widths (string, layout, &char_len, &widths, &cuts); + + for (truncate_offset = char_len - 1; truncate_offset > 0; truncate_offset--) { + resulting_width -= widths[truncate_offset]; + if (resulting_width <= width && + cuts[truncate_offset]) { + break; + } + } + + g_free (cuts); + g_free (widths); + + p = g_utf8_offset_to_pointer (string, truncate_offset); + + result = g_malloc ((p - string) + strlen (ELLIPSIS) + 1); + memcpy (result, string, (p - string)); + strcpy (result + (p - string), ELLIPSIS); + + return result; +} + +static char * +eel_string_ellipsize_middle (const char *string, PangoLayout *layout, int width) +{ + int resulting_width; + int *cuts; + int *widths; + int char_len; + int starting_fragment_byte_len; + int ending_fragment_byte_index; + int starting_fragment_length; + int ending_fragment_offset; + char *result; + + /* See explanatory comments in ellipsize_start */ + + if (*string == '\0') + return g_strdup (""); + + resulting_width = measure_string_width (string, layout); + + if (resulting_width <= width) { + return g_strdup (string); + } + + width -= measure_string_width (ELLIPSIS, layout); + + if (width < 0) { + return g_strdup (""); + } + + compute_character_widths (string, layout, &char_len, &widths, &cuts); + + starting_fragment_length = char_len / 2; + ending_fragment_offset = starting_fragment_length + 1; + + /* Shave off a character at a time from the first and the second half + * until we can fit + */ + resulting_width -= widths[ending_fragment_offset - 1]; + + /* depending on whether the original string length is odd or even, start by + * shaving off the characters from the starting or ending fragment + */ + if (char_len % 2) { + goto shave_end; + } + + while (starting_fragment_length > 0 || ending_fragment_offset < char_len) { + if (resulting_width <= width && + cuts[ending_fragment_offset] && + cuts[starting_fragment_length]) { + break; + } + + if (starting_fragment_length > 0) { + resulting_width -= widths[starting_fragment_length]; + starting_fragment_length--; + } + + shave_end: + if (resulting_width <= width && + cuts[ending_fragment_offset] && + cuts[starting_fragment_length]) { + break; + } + + if (ending_fragment_offset < char_len) { + resulting_width -= widths[ending_fragment_offset]; + ending_fragment_offset++; + } + } + + g_free (cuts); + g_free (widths); + + /* patch the two fragments together with an ellipsis */ + result = g_malloc (strlen (string) + strlen (ELLIPSIS) + 1); /* a bit wasteful, no biggie */ + + starting_fragment_byte_len = g_utf8_offset_to_pointer (string, starting_fragment_length) - string; + ending_fragment_byte_index = g_utf8_offset_to_pointer (string, ending_fragment_offset) - string; + + memcpy (result, string, starting_fragment_byte_len); + strcpy (result + starting_fragment_byte_len, ELLIPSIS); + strcpy (result + starting_fragment_byte_len + strlen (ELLIPSIS), string + ending_fragment_byte_index); + + return result; +} + + +/** + * eel_pango_layout_set_text_ellipsized + * + * @layout: a pango layout + * @string: A a string to be ellipsized. + * @width: Desired maximum width in points. + * @mode: The desired ellipsizing mode. + * + * Truncates a string if required to fit in @width and sets it on the + * layout. Truncation involves removing characters from the start, middle or end + * respectively and replacing them with "...". Algorithm is a bit + * fuzzy, won't work 100%. + * + */ +void +eel_pango_layout_set_text_ellipsized (PangoLayout *layout, + const char *string, + int width, + EelEllipsizeMode mode) +{ + char *s; + + g_return_if_fail (PANGO_IS_LAYOUT (layout)); + g_return_if_fail (string != NULL); + g_return_if_fail (width >= 0); + + switch (mode) { + case EEL_ELLIPSIZE_START: + s = eel_string_ellipsize_start (string, layout, width); + break; + case EEL_ELLIPSIZE_MIDDLE: + s = eel_string_ellipsize_middle (string, layout, width); + break; + case EEL_ELLIPSIZE_END: + s = eel_string_ellipsize_end (string, layout, width); + break; + default: + g_return_if_reached (); + s = NULL; + } + + pango_layout_set_text (layout, s, -1); + + g_free (s); +} + +/** + * eel_pango_layout_fit_to_dimensions: + * @layout: a pango layout + * @max_width: the maximum width allowed or -1 to match only height + * @max_height: the maximum height allowed or -1 to match only width + * + * This method adjusts the font attributes of the simple + * string in @layout until it fits inside the desired @max_size. + * + * Return value: the resulting dimensions. + **/ +PangoRectangle +eel_pango_layout_fit_to_dimensions (PangoLayout *layout, + int max_width, + int max_height) +{ + gint size; + PangoContext *context; + PangoRectangle logical_rect = { 0 }; + PangoFontDescription *font_desc; + + g_return_val_if_fail (PANGO_IS_LAYOUT (layout), logical_rect); + + context = pango_layout_get_context (layout); + font_desc = pango_font_description_copy (pango_context_get_font_description (context)); + size = pango_font_description_get_size (font_desc); + + while (size > 0) { + pango_font_description_set_size (font_desc, size); + pango_layout_set_font_description (layout, font_desc); + pango_layout_get_pixel_extents (layout, NULL, &logical_rect); + + if (!((((max_width > 0 && logical_rect.width > max_width) || max_width < 0) && + ((max_height > 0 && logical_rect.height > max_height) || max_height < 0)))) + break; + + size -= PANGO_SCALE / 2; + } + + pango_font_description_free (font_desc); + + return logical_rect; +} + +int +eel_pango_font_description_get_largest_fitting_font_size (const PangoFontDescription *font_desc, + PangoContext *context, + const char *text, + int available_width, + int minimum_acceptable_font_size, + int maximum_acceptable_font_size) +{ + int i; + int width; + PangoLayout *layout; + PangoFontDescription *font; + + g_return_val_if_fail (text != NULL, 0); + g_return_val_if_fail (text[0] != '\0', 0); + g_return_val_if_fail (available_width > 0, 0); + g_return_val_if_fail (minimum_acceptable_font_size > 0, 0); + g_return_val_if_fail (maximum_acceptable_font_size > 0, 0); + g_return_val_if_fail (maximum_acceptable_font_size > minimum_acceptable_font_size, 0); + + layout = pango_layout_new (context); + pango_layout_set_text (layout, text, -1); + pango_layout_set_font_description (layout, font_desc); + + font = pango_font_description_new (); + + for (i = maximum_acceptable_font_size; i >= minimum_acceptable_font_size; i--) { + + pango_font_description_set_size (font, i * PANGO_SCALE); + pango_layout_set_font_description (layout, font); + pango_layout_get_pixel_size (layout, &width, NULL); + + if (width <= available_width) { + pango_font_description_free (font); + g_object_unref (layout); + return i; + } + } + + pango_font_description_free (font); + g_object_unref (layout); + return i; +} + + +PangoContext * +eel_pango_ft2_get_context (void) +{ + PangoContext *context; + double dpi_x, dpi_y; + + dpi_x = gdk_screen_width () * 25.4 / gdk_screen_width_mm (); + dpi_y = gdk_screen_height () * 25.4 / gdk_screen_height_mm (); + context = pango_ft2_get_context (dpi_x, dpi_y); + + pango_context_set_language (context, gtk_get_default_language ()); + + return context; +} + +#if !defined (EEL_OMIT_SELF_CHECK) + +static PangoContext * +eel_create_bogus_test_pango_context (void) +{ + PangoContext *context; + + context = gdk_pango_context_get (); + pango_context_set_language (context, gtk_get_default_language ()); + + return context; +} + +/* Testing string truncation is tough because we do not know what font/ + * font metrics to expect on a given system. To work around this we use + * a substring of the original, measure it's length using the given font, + * add the length of the "..." string and use that for truncation. + * The result should then be the substring prepended with a "..." + */ +static char * +eel_self_check_ellipsize (const char *string, const char *truncate_to_length_string, EelEllipsizeMode mode) +{ + PangoContext *context; + int truncation_length; + char *result; + PangoLayout *layout; + + context = eel_create_bogus_test_pango_context (); + layout = pango_layout_new (context); + + /* measure the length we want to truncate to */ + truncation_length = measure_string_width (truncate_to_length_string, layout); + + eel_pango_layout_set_text_ellipsized (layout, string, truncation_length, mode); + + result = g_strdup (pango_layout_get_text (layout)); + + g_object_unref (G_OBJECT (context)); + g_object_unref (G_OBJECT (layout)); + + return result; +} + +static char * +eel_self_check_ellipsize_start (const char *string, const char *truncate_to_length_string) +{ + return eel_self_check_ellipsize (string, truncate_to_length_string, EEL_ELLIPSIZE_START); +} + +static char * +eel_self_check_ellipsize_middle (const char *string, const char *truncate_to_length_string) +{ + return eel_self_check_ellipsize (string, truncate_to_length_string, EEL_ELLIPSIZE_MIDDLE); +} + +static char * +eel_self_check_ellipsize_end (const char *string, const char *truncate_to_length_string) +{ + return eel_self_check_ellipsize (string, truncate_to_length_string, EEL_ELLIPSIZE_END); +} + +void +eel_self_check_pango_extensions (void) +{ + PangoContext *context; + + /* used to test ellipsize routines */ + context = eel_create_bogus_test_pango_context (); + + /* Turned off these tests because they are failing for me and I + * want the release to be able to pass "make check". We'll have + * to revisit this at some point. The failures started because + * I changed my default font and enabled Xft support. I presume + * this is simply the "fuzziness" Havoc mentions in his comments + * above. + * - Darin + */ + + if (0) { + /* eel_string_ellipsize_start */ + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_start ("012345678", "0012345678"), "012345678"); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_start ("012345678", "012345678"), "012345678"); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_start ("012345678", "...45678"), "...45678"); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_start ("012345678", "...5678"), "...5678"); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_start ("012345678", "...678"), "...678"); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_start ("012345678", "...78"), "...78"); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_start ("012345678", "...8"), "...8"); + + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_middle ("012345678", "0123456789"), "012345678"); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_middle ("012345678", "012345678"), "012345678"); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_middle ("012345678", "012...78"), "012...78"); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_middle ("012345678", "01...78"), "01...78"); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_middle ("012345678", "01...8"), "01...8"); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_middle ("012345678", "0...8"), "0...8"); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_middle ("012345678", "0..."), "0..."); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_middle ("0123456789", "0123456789"), "0123456789"); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_middle ("0123456789", "012...789"), "012...789"); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_middle ("0123456789", "012...89"), "012...89"); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_middle ("0123456789", "01...89"), "01...89"); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_middle ("0123456789", "01...9"), "01...9"); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_middle ("0123456789", "0...9"), "0...9"); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_middle ("0123456789", "0..."), "0..."); + + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_end ("012345678", "0123456789"), "012345678"); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_end ("012345678", "012345678"), "012345678"); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_end ("012345678", "01234..."), "01234..."); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_end ("012345678", "0123..."), "0123..."); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_end ("012345678", "012..."), "012..."); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_end ("012345678", "01..."), "01..."); + EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_end ("012345678", "0..."), "0..."); + } + + g_object_unref (context); +} + +#endif /* !EEL_OMIT_SELF_CHECK */ Index: cut-n-paste/eel-pango-extensions.h =================================================================== RCS file: cut-n-paste/eel-pango-extensions.h diff -N cut-n-paste/eel-pango-extensions.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ cut-n-paste/eel-pango-extensions.h 12 Jan 2004 17:15:55 -0000 @@ -0,0 +1,67 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* eel-pango-extensions.h - interface for new functions that conceptually + belong in pango. Perhaps some of these will be + actually rolled into pango someday. + + Copyright (C) 2001 Anders Carlsson + + The Eel Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Eel Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Eel Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: Anders Carlsson +*/ + +#ifndef EEL_PANGO_EXTENSIONS_H +#define EEL_PANGO_EXTENSIONS_H + +#include + +typedef enum { + EEL_ELLIPSIZE_START, + EEL_ELLIPSIZE_MIDDLE, + EEL_ELLIPSIZE_END +} EelEllipsizeMode; + +PangoAttrList *eel_pango_attr_list_copy_or_create (PangoAttrList *attr_list); +PangoAttrList *eel_pango_attr_list_apply_global_attribute (PangoAttrList *attr_list, + PangoAttribute *attr); +void eel_pango_layout_set_weight (PangoLayout *layout, + PangoWeight weight); +void eel_pango_layout_set_underline (PangoLayout *layout, + PangoUnderline underline); +void eel_pango_layout_set_font_desc_from_string (PangoLayout *layout, + const char *str); +int eel_pango_font_description_get_largest_fitting_font_size (const PangoFontDescription *font_desc, + PangoContext *context, + const char *text, + int available_width, + int minimum_acceptable_font_size, + int maximum_acceptable_font_size); + +PangoRectangle eel_pango_layout_fit_to_dimensions (PangoLayout *layout, + int max_width, + int max_height); +/* caution: this function is expensive. */ +void eel_pango_layout_set_text_ellipsized (PangoLayout *layout, + const char *string, + int width, + EelEllipsizeMode mode); +PangoContext * eel_pango_ft2_get_context (void); + + + + +#endif /* EEL_PANGO_EXTENSIONS_H */ Index: cut-n-paste/eggcellrenderertext.c =================================================================== RCS file: cut-n-paste/eggcellrenderertext.c diff -N cut-n-paste/eggcellrenderertext.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ cut-n-paste/eggcellrenderertext.c 12 Jan 2004 17:15:55 -0000 @@ -0,0 +1,1519 @@ +/* eggcellrenderertext.c + * Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include "eggcellrenderertext.h" +#include +#include +#include "eggtreeprivate.h" +#include "eggmarshalers.h" +#include "eggintl.h" +#include "eel-pango-extensions.h" + +/* Disable editing because it the code uses + * treeview internals + */ +#define DISABLE_EDITING + +static void egg_cell_renderer_text_init (EggCellRendererText *celltext); +static void egg_cell_renderer_text_class_init (EggCellRendererTextClass *class); +static void egg_cell_renderer_text_finalize (GObject *object); + +static void egg_cell_renderer_text_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); +static void egg_cell_renderer_text_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); +static void egg_cell_renderer_text_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *cell_area, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height); +static void egg_cell_renderer_text_render (GtkCellRenderer *cell, + GdkWindow *window, + GtkWidget *widget, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GdkRectangle *expose_area, + GtkCellRendererState flags); + +#ifndef DISABLE_EDITING +static GtkCellEditable *egg_cell_renderer_text_start_editing (GtkCellRenderer *cell, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GtkCellRendererState flags); +#endif /* DISABLE_EDITING */ + +enum { + EDITED, + LAST_SIGNAL +}; + +enum { + PROP_0, + + PROP_TEXT, + PROP_MARKUP, + PROP_ATTRIBUTES, + PROP_ELLIPSIZE, + + /* Style args */ + PROP_BACKGROUND, + PROP_FOREGROUND, + PROP_BACKGROUND_GDK, + PROP_FOREGROUND_GDK, + PROP_FONT, + PROP_FONT_DESC, + PROP_FAMILY, + PROP_STYLE, + PROP_VARIANT, + PROP_WEIGHT, + PROP_STRETCH, + PROP_SIZE, + PROP_SIZE_POINTS, + PROP_SCALE, + PROP_EDITABLE, + PROP_STRIKETHROUGH, + PROP_UNDERLINE, + PROP_RISE, + + /* Whether-a-style-arg-is-set args */ + PROP_BACKGROUND_SET, + PROP_FOREGROUND_SET, + PROP_FAMILY_SET, + PROP_STYLE_SET, + PROP_VARIANT_SET, + PROP_WEIGHT_SET, + PROP_STRETCH_SET, + PROP_SIZE_SET, + PROP_SCALE_SET, + PROP_EDITABLE_SET, + PROP_STRIKETHROUGH_SET, + PROP_UNDERLINE_SET, + PROP_RISE_SET +}; + +static gpointer parent_class; +static guint text_cell_renderer_signals [LAST_SIGNAL]; + +#define EGG_CELL_RENDERER_TEXT_PATH "egg-cell-renderer-text-path" + +GType +egg_cell_renderer_text_get_type (void) +{ + static GType cell_text_type = 0; + + if (!cell_text_type) + { + static const GTypeInfo cell_text_info = + { + sizeof (EggCellRendererTextClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) egg_cell_renderer_text_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (EggCellRendererText), + 0, /* n_preallocs */ + (GInstanceInitFunc) egg_cell_renderer_text_init, + }; + + cell_text_type = + g_type_register_static (GTK_TYPE_CELL_RENDERER, "EggCellRendererText", + &cell_text_info, 0); + } + + return cell_text_type; +} + +static void +egg_cell_renderer_text_init (EggCellRendererText *celltext) +{ + GTK_CELL_RENDERER (celltext)->xalign = 0.0; + GTK_CELL_RENDERER (celltext)->yalign = 0.5; + GTK_CELL_RENDERER (celltext)->xpad = 2; + GTK_CELL_RENDERER (celltext)->ypad = 2; + celltext->fixed_height_rows = -1; + celltext->font = pango_font_description_new (); +} + +static void +egg_cell_renderer_text_class_init (EggCellRendererTextClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (class); + + parent_class = g_type_class_peek_parent (class); + + object_class->finalize = egg_cell_renderer_text_finalize; + + object_class->get_property = egg_cell_renderer_text_get_property; + object_class->set_property = egg_cell_renderer_text_set_property; + + cell_class->get_size = egg_cell_renderer_text_get_size; + cell_class->render = egg_cell_renderer_text_render; +#ifndef DISABLE_EDITING + cell_class->start_editing = egg_cell_renderer_text_start_editing; +#endif /* DISABLE_EDITING */ + + g_object_class_install_property (object_class, + PROP_TEXT, + g_param_spec_string ("text", + _("Text"), + _("Text to render"), + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_MARKUP, + g_param_spec_string ("markup", + _("Markup"), + _("Marked up text to render"), + NULL, + G_PARAM_WRITABLE)); + + g_object_class_install_property (object_class, + PROP_ATTRIBUTES, + g_param_spec_boxed ("attributes", + _("Attributes"), + _("A list of style attributes to apply to the text of the renderer"), + PANGO_TYPE_ATTR_LIST, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_ELLIPSIZE, + g_param_spec_boolean ("ellipsize", + _("Ellipsize"), + _("Whether to crop the text and display \"...\" when space is limited"), + FALSE, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_BACKGROUND, + g_param_spec_string ("background", + _("Background color name"), + _("Background color as a string"), + NULL, + G_PARAM_WRITABLE)); + + g_object_class_install_property (object_class, + PROP_BACKGROUND_GDK, + g_param_spec_boxed ("background_gdk", + _("Background color"), + _("Background color as a GdkColor"), + GDK_TYPE_COLOR, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property (object_class, + PROP_FOREGROUND, + g_param_spec_string ("foreground", + _("Foreground color name"), + _("Foreground color as a string"), + NULL, + G_PARAM_WRITABLE)); + + g_object_class_install_property (object_class, + PROP_FOREGROUND_GDK, + g_param_spec_boxed ("foreground_gdk", + _("Foreground color"), + _("Foreground color as a GdkColor"), + GDK_TYPE_COLOR, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + + g_object_class_install_property (object_class, + PROP_EDITABLE, + g_param_spec_boolean ("editable", + _("Editable"), + _("Whether the text can be modified by the user"), + FALSE, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property (object_class, + PROP_FONT, + g_param_spec_string ("font", + _("Font"), + _("Font description as a string"), + NULL, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property (object_class, + PROP_FONT_DESC, + g_param_spec_boxed ("font_desc", + _("Font"), + _("Font description as a PangoFontDescription struct"), + PANGO_TYPE_FONT_DESCRIPTION, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + + g_object_class_install_property (object_class, + PROP_FAMILY, + g_param_spec_string ("family", + _("Font family"), + _("Name of the font family, e.g. Sans, Helvetica, Times, Monospace"), + NULL, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property (object_class, + PROP_STYLE, + g_param_spec_enum ("style", + _("Font style"), + _("Font style"), + PANGO_TYPE_STYLE, + PANGO_STYLE_NORMAL, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property (object_class, + PROP_VARIANT, + g_param_spec_enum ("variant", + _("Font variant"), + _("Font variant"), + PANGO_TYPE_VARIANT, + PANGO_VARIANT_NORMAL, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property (object_class, + PROP_WEIGHT, + g_param_spec_int ("weight", + _("Font weight"), + _("Font weight"), + 0, + G_MAXINT, + PANGO_WEIGHT_NORMAL, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property (object_class, + PROP_STRETCH, + g_param_spec_enum ("stretch", + _("Font stretch"), + _("Font stretch"), + PANGO_TYPE_STRETCH, + PANGO_STRETCH_NORMAL, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property (object_class, + PROP_SIZE, + g_param_spec_int ("size", + _("Font size"), + _("Font size"), + 0, + G_MAXINT, + 0, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property (object_class, + PROP_SIZE_POINTS, + g_param_spec_double ("size_points", + _("Font points"), + _("Font size in points"), + 0.0, + G_MAXDOUBLE, + 0.0, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property (object_class, + PROP_SCALE, + g_param_spec_double ("scale", + _("Font scale"), + _("Font scaling factor"), + 0.0, + G_MAXDOUBLE, + 1.0, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property (object_class, + PROP_RISE, + g_param_spec_int ("rise", + _("Rise"), + _("Offset of text above the baseline (below the baseline if rise is negative)"), + -G_MAXINT, + G_MAXINT, + 0, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + + g_object_class_install_property (object_class, + PROP_STRIKETHROUGH, + g_param_spec_boolean ("strikethrough", + _("Strikethrough"), + _("Whether to strike through the text"), + FALSE, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property (object_class, + PROP_UNDERLINE, + g_param_spec_enum ("underline", + _("Underline"), + _("Style of underline for this text"), + PANGO_TYPE_UNDERLINE, + PANGO_UNDERLINE_NONE, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + /* Style props are set or not */ + +#define ADD_SET_PROP(propname, propval, nick, blurb) g_object_class_install_property (object_class, propval, g_param_spec_boolean (propname, nick, blurb, FALSE, G_PARAM_READABLE | G_PARAM_WRITABLE)) + + ADD_SET_PROP ("background_set", PROP_BACKGROUND_SET, + _("Background set"), + _("Whether this tag affects the background color")); + + ADD_SET_PROP ("foreground_set", PROP_FOREGROUND_SET, + _("Foreground set"), + _("Whether this tag affects the foreground color")); + + ADD_SET_PROP ("editable_set", PROP_EDITABLE_SET, + _("Editability set"), + _("Whether this tag affects text editability")); + + ADD_SET_PROP ("family_set", PROP_FAMILY_SET, + _("Font family set"), + _("Whether this tag affects the font family")); + + ADD_SET_PROP ("style_set", PROP_STYLE_SET, + _("Font style set"), + _("Whether this tag affects the font style")); + + ADD_SET_PROP ("variant_set", PROP_VARIANT_SET, + _("Font variant set"), + _("Whether this tag affects the font variant")); + + ADD_SET_PROP ("weight_set", PROP_WEIGHT_SET, + _("Font weight set"), + _("Whether this tag affects the font weight")); + + ADD_SET_PROP ("stretch_set", PROP_STRETCH_SET, + _("Font stretch set"), + _("Whether this tag affects the font stretch")); + + ADD_SET_PROP ("size_set", PROP_SIZE_SET, + _("Font size set"), + _("Whether this tag affects the font size")); + + ADD_SET_PROP ("scale_set", PROP_SCALE_SET, + _("Font scale set"), + _("Whether this tag scales the font size by a factor")); + + ADD_SET_PROP ("rise_set", PROP_RISE_SET, + _("Rise set"), + _("Whether this tag affects the rise")); + + ADD_SET_PROP ("strikethrough_set", PROP_STRIKETHROUGH_SET, + _("Strikethrough set"), + _("Whether this tag affects strikethrough")); + + ADD_SET_PROP ("underline_set", PROP_UNDERLINE_SET, + _("Underline set"), + _("Whether this tag affects underlining")); + + text_cell_renderer_signals [EDITED] = + g_signal_new ("edited", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggCellRendererTextClass, edited), + NULL, NULL, + _egg_marshal_VOID__STRING_STRING, + G_TYPE_NONE, 2, + G_TYPE_STRING, + G_TYPE_STRING); + +} + +static void +egg_cell_renderer_text_finalize (GObject *object) +{ + EggCellRendererText *celltext = EGG_CELL_RENDERER_TEXT (object); + + pango_font_description_free (celltext->font); + + if (celltext->text) + g_free (celltext->text); + + if (celltext->extra_attrs) + pango_attr_list_unref (celltext->extra_attrs); + + (* G_OBJECT_CLASS (parent_class)->finalize) (object); +} + +static PangoFontMask +get_property_font_set_mask (guint prop_id) +{ + switch (prop_id) + { + case PROP_FAMILY_SET: + return PANGO_FONT_MASK_FAMILY; + case PROP_STYLE_SET: + return PANGO_FONT_MASK_STYLE; + case PROP_VARIANT_SET: + return PANGO_FONT_MASK_VARIANT; + case PROP_WEIGHT_SET: + return PANGO_FONT_MASK_WEIGHT; + case PROP_STRETCH_SET: + return PANGO_FONT_MASK_STRETCH; + case PROP_SIZE_SET: + return PANGO_FONT_MASK_SIZE; + } + + return 0; +} + +static void +egg_cell_renderer_text_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + EggCellRendererText *celltext = EGG_CELL_RENDERER_TEXT (object); + + switch (param_id) + { + case PROP_TEXT: + g_value_set_string (value, celltext->text); + break; + + case PROP_ATTRIBUTES: + g_value_set_boxed (value, celltext->extra_attrs); + break; + + case PROP_ELLIPSIZE: + g_value_set_boolean (value, celltext->ellipsize); + break; + + case PROP_BACKGROUND_GDK: + { + GdkColor color; + + color.red = celltext->background.red; + color.green = celltext->background.green; + color.blue = celltext->background.blue; + + g_value_set_boxed (value, &color); + } + break; + + case PROP_FOREGROUND_GDK: + { + GdkColor color; + + color.red = celltext->foreground.red; + color.green = celltext->foreground.green; + color.blue = celltext->foreground.blue; + + g_value_set_boxed (value, &color); + } + break; + + case PROP_FONT: + { + /* FIXME GValue imposes a totally gratuitous string copy + * here, we could just hand off string ownership + */ + gchar *str = pango_font_description_to_string (celltext->font); + g_value_set_string (value, str); + g_free (str); + } + break; + + case PROP_FONT_DESC: + g_value_set_boxed (value, celltext->font); + break; + + case PROP_FAMILY: + g_value_set_string (value, pango_font_description_get_family (celltext->font)); + break; + + case PROP_STYLE: + g_value_set_enum (value, pango_font_description_get_style (celltext->font)); + break; + + case PROP_VARIANT: + g_value_set_enum (value, pango_font_description_get_variant (celltext->font)); + break; + + case PROP_WEIGHT: + g_value_set_int (value, pango_font_description_get_weight (celltext->font)); + break; + + case PROP_STRETCH: + g_value_set_enum (value, pango_font_description_get_stretch (celltext->font)); + break; + + case PROP_SIZE: + g_value_set_int (value, pango_font_description_get_size (celltext->font)); + break; + + case PROP_SIZE_POINTS: + g_value_set_double (value, ((double)pango_font_description_get_size (celltext->font)) / (double)PANGO_SCALE); + break; + + case PROP_SCALE: + g_value_set_double (value, celltext->font_scale); + break; + + case PROP_EDITABLE: + g_value_set_boolean (value, celltext->editable); + break; + + case PROP_STRIKETHROUGH: + g_value_set_boolean (value, celltext->strikethrough); + break; + + case PROP_UNDERLINE: + g_value_set_enum (value, celltext->underline_style); + break; + + case PROP_RISE: + g_value_set_int (value, celltext->rise); + break; + + case PROP_BACKGROUND_SET: + g_value_set_boolean (value, celltext->background_set); + break; + + case PROP_FOREGROUND_SET: + g_value_set_boolean (value, celltext->foreground_set); + break; + + case PROP_FAMILY_SET: + case PROP_STYLE_SET: + case PROP_VARIANT_SET: + case PROP_WEIGHT_SET: + case PROP_STRETCH_SET: + case PROP_SIZE_SET: + { + PangoFontMask mask = get_property_font_set_mask (param_id); + g_value_set_boolean (value, (pango_font_description_get_set_fields (celltext->font) & mask) != 0); + + break; + } + + case PROP_SCALE_SET: + g_value_set_boolean (value, celltext->scale_set); + break; + + case PROP_EDITABLE_SET: + g_value_set_boolean (value, celltext->editable_set); + break; + + case PROP_STRIKETHROUGH_SET: + g_value_set_boolean (value, celltext->strikethrough_set); + break; + + case PROP_UNDERLINE_SET: + g_value_set_boolean (value, celltext->underline_set); + break; + + case PROP_RISE_SET: + g_value_set_boolean (value, celltext->rise_set); + break; + + case PROP_BACKGROUND: + case PROP_FOREGROUND: + case PROP_MARKUP: + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + + +static void +set_bg_color (EggCellRendererText *celltext, + GdkColor *color) +{ + if (color) + { + if (!celltext->background_set) + { + celltext->background_set = TRUE; + g_object_notify (G_OBJECT (celltext), "background_set"); + } + + celltext->background.red = color->red; + celltext->background.green = color->green; + celltext->background.blue = color->blue; + } + else + { + if (celltext->background_set) + { + celltext->background_set = FALSE; + g_object_notify (G_OBJECT (celltext), "background_set"); + } + } +} + + +static void +set_fg_color (EggCellRendererText *celltext, + GdkColor *color) +{ + if (color) + { + if (!celltext->foreground_set) + { + celltext->foreground_set = TRUE; + g_object_notify (G_OBJECT (celltext), "foreground_set"); + } + + celltext->foreground.red = color->red; + celltext->foreground.green = color->green; + celltext->foreground.blue = color->blue; + } + else + { + if (celltext->foreground_set) + { + celltext->foreground_set = FALSE; + g_object_notify (G_OBJECT (celltext), "foreground_set"); + } + } +} + +static PangoFontMask +set_font_desc_fields (PangoFontDescription *desc, + PangoFontMask to_set) +{ + PangoFontMask changed_mask = 0; + + if (to_set & PANGO_FONT_MASK_FAMILY) + { + const char *family = pango_font_description_get_family (desc); + if (!family) + { + family = "sans"; + changed_mask |= PANGO_FONT_MASK_FAMILY; + } + + pango_font_description_set_family (desc, family); + } + if (to_set & PANGO_FONT_MASK_STYLE) + pango_font_description_set_style (desc, pango_font_description_get_style (desc)); + if (to_set & PANGO_FONT_MASK_VARIANT) + pango_font_description_set_variant (desc, pango_font_description_get_variant (desc)); + if (to_set & PANGO_FONT_MASK_WEIGHT) + pango_font_description_set_weight (desc, pango_font_description_get_weight (desc)); + if (to_set & PANGO_FONT_MASK_STRETCH) + pango_font_description_set_stretch (desc, pango_font_description_get_stretch (desc)); + if (to_set & PANGO_FONT_MASK_SIZE) + { + gint size = pango_font_description_get_size (desc); + if (size <= 0) + { + size = 10 * PANGO_SCALE; + changed_mask |= PANGO_FONT_MASK_SIZE; + } + + pango_font_description_set_size (desc, size); + } + + return changed_mask; +} + +static void +notify_set_changed (GObject *object, + PangoFontMask changed_mask) +{ + if (changed_mask & PANGO_FONT_MASK_FAMILY) + g_object_notify (object, "family_set"); + if (changed_mask & PANGO_FONT_MASK_STYLE) + g_object_notify (object, "style_set"); + if (changed_mask & PANGO_FONT_MASK_VARIANT) + g_object_notify (object, "variant_set"); + if (changed_mask & PANGO_FONT_MASK_WEIGHT) + g_object_notify (object, "weight_set"); + if (changed_mask & PANGO_FONT_MASK_STRETCH) + g_object_notify (object, "stretch_set"); + if (changed_mask & PANGO_FONT_MASK_SIZE) + g_object_notify (object, "size_set"); +} + +static void +notify_fields_changed (GObject *object, + PangoFontMask changed_mask) +{ + if (changed_mask & PANGO_FONT_MASK_FAMILY) + g_object_notify (object, "family"); + if (changed_mask & PANGO_FONT_MASK_STYLE) + g_object_notify (object, "style"); + if (changed_mask & PANGO_FONT_MASK_VARIANT) + g_object_notify (object, "variant"); + if (changed_mask & PANGO_FONT_MASK_WEIGHT) + g_object_notify (object, "weight"); + if (changed_mask & PANGO_FONT_MASK_STRETCH) + g_object_notify (object, "stretch"); + if (changed_mask & PANGO_FONT_MASK_SIZE) + g_object_notify (object, "size"); +} + +static void +set_font_description (EggCellRendererText *celltext, + PangoFontDescription *font_desc) +{ + GObject *object = G_OBJECT (celltext); + PangoFontDescription *new_font_desc; + PangoFontMask old_mask, new_mask, changed_mask, set_changed_mask; + + if (font_desc) + new_font_desc = pango_font_description_copy (font_desc); + else + new_font_desc = pango_font_description_new (); + + old_mask = pango_font_description_get_set_fields (celltext->font); + new_mask = pango_font_description_get_set_fields (new_font_desc); + + changed_mask = old_mask | new_mask; + set_changed_mask = old_mask ^ new_mask; + + pango_font_description_free (celltext->font); + celltext->font = new_font_desc; + + g_object_freeze_notify (object); + + g_object_notify (object, "font_desc"); + g_object_notify (object, "font"); + + if (changed_mask & PANGO_FONT_MASK_FAMILY) + g_object_notify (object, "family"); + if (changed_mask & PANGO_FONT_MASK_STYLE) + g_object_notify (object, "style"); + if (changed_mask & PANGO_FONT_MASK_VARIANT) + g_object_notify (object, "variant"); + if (changed_mask & PANGO_FONT_MASK_WEIGHT) + g_object_notify (object, "weight"); + if (changed_mask & PANGO_FONT_MASK_STRETCH) + g_object_notify (object, "stretch"); + if (changed_mask & PANGO_FONT_MASK_SIZE) + { + g_object_notify (object, "size"); + g_object_notify (object, "size_points"); + } + + notify_set_changed (object, set_changed_mask); + + g_object_thaw_notify (object); +} + +static void +egg_cell_renderer_text_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + EggCellRendererText *celltext = EGG_CELL_RENDERER_TEXT (object); + + switch (param_id) + { + case PROP_TEXT: + if (celltext->text) + g_free (celltext->text); + celltext->text = g_strdup (g_value_get_string (value)); + g_object_notify (object, "text"); + break; + + case PROP_ATTRIBUTES: + if (celltext->extra_attrs) + pango_attr_list_unref (celltext->extra_attrs); + + celltext->extra_attrs = g_value_get_boxed (value); + if (celltext->extra_attrs) + pango_attr_list_ref (celltext->extra_attrs); + break; + + case PROP_ELLIPSIZE: + egg_cell_renderer_text_set_ellipsize (celltext, + g_value_get_boolean (value)); + break; + + case PROP_MARKUP: + { + const gchar *str; + gchar *text = NULL; + GError *error = NULL; + PangoAttrList *attrs = NULL; + + str = g_value_get_string (value); + if (str && !pango_parse_markup (str, + -1, + 0, + &attrs, + &text, + NULL, + &error)) + { + g_warning ("Failed to set cell text from markup due to error parsing markup: %s", + error->message); + g_error_free (error); + return; + } + + if (celltext->text) + g_free (celltext->text); + + if (celltext->extra_attrs) + pango_attr_list_unref (celltext->extra_attrs); + + celltext->text = text; + celltext->extra_attrs = attrs; + } + break; + + case PROP_BACKGROUND: + { + GdkColor color; + + if (!g_value_get_string (value)) + set_bg_color (celltext, NULL); /* reset to backgrounmd_set to FALSE */ + else if (gdk_color_parse (g_value_get_string (value), &color)) + set_bg_color (celltext, &color); + else + g_warning ("Don't know color `%s'", g_value_get_string (value)); + + g_object_notify (object, "background_gdk"); + } + break; + + case PROP_FOREGROUND: + { + GdkColor color; + + if (!g_value_get_string (value)) + set_fg_color (celltext, NULL); /* reset to foreground_set to FALSE */ + else if (gdk_color_parse (g_value_get_string (value), &color)) + set_fg_color (celltext, &color); + else + g_warning ("Don't know color `%s'", g_value_get_string (value)); + + g_object_notify (object, "foreground_gdk"); + } + break; + + case PROP_BACKGROUND_GDK: + /* This notifies the GObject itself. */ + set_bg_color (celltext, g_value_get_boxed (value)); + break; + + case PROP_FOREGROUND_GDK: + /* This notifies the GObject itself. */ + set_fg_color (celltext, g_value_get_boxed (value)); + break; + + case PROP_FONT: + { + PangoFontDescription *font_desc = NULL; + const gchar *name; + + name = g_value_get_string (value); + + if (name) + font_desc = pango_font_description_from_string (name); + + set_font_description (celltext, font_desc); + + if (celltext->fixed_height_rows != -1) + celltext->calc_fixed_height = TRUE; + } + break; + + case PROP_FONT_DESC: + set_font_description (celltext, g_value_get_boxed (value)); + + if (celltext->fixed_height_rows != -1) + celltext->calc_fixed_height = TRUE; + break; + + case PROP_FAMILY: + case PROP_STYLE: + case PROP_VARIANT: + case PROP_WEIGHT: + case PROP_STRETCH: + case PROP_SIZE: + case PROP_SIZE_POINTS: + { + PangoFontMask old_set_mask = pango_font_description_get_set_fields (celltext->font); + + switch (param_id) + { + case PROP_FAMILY: + pango_font_description_set_family (celltext->font, + g_value_get_string (value)); + break; + case PROP_STYLE: + pango_font_description_set_style (celltext->font, + g_value_get_enum (value)); + break; + case PROP_VARIANT: + pango_font_description_set_variant (celltext->font, + g_value_get_enum (value)); + break; + case PROP_WEIGHT: + pango_font_description_set_weight (celltext->font, + g_value_get_int (value)); + break; + case PROP_STRETCH: + pango_font_description_set_stretch (celltext->font, + g_value_get_enum (value)); + break; + case PROP_SIZE: + pango_font_description_set_size (celltext->font, + g_value_get_int (value)); + g_object_notify (object, "size_points"); + break; + case PROP_SIZE_POINTS: + pango_font_description_set_size (celltext->font, + g_value_get_double (value) * PANGO_SCALE); + g_object_notify (object, "size"); + break; + } + + if (celltext->fixed_height_rows != -1) + celltext->calc_fixed_height = TRUE; + + notify_set_changed (object, old_set_mask & pango_font_description_get_set_fields (celltext->font)); + g_object_notify (object, "font_desc"); + g_object_notify (object, "font"); + + break; + } + + case PROP_SCALE: + celltext->font_scale = g_value_get_double (value); + celltext->scale_set = TRUE; + if (celltext->fixed_height_rows != -1) + celltext->calc_fixed_height = TRUE; + g_object_notify (object, "scale_set"); + break; + + case PROP_EDITABLE: + celltext->editable = g_value_get_boolean (value); + celltext->editable_set = TRUE; + if (celltext->editable) + GTK_CELL_RENDERER (celltext)->mode = GTK_CELL_RENDERER_MODE_EDITABLE; + else + GTK_CELL_RENDERER (celltext)->mode = GTK_CELL_RENDERER_MODE_INERT; + g_object_notify (object, "editable_set"); + break; + + case PROP_STRIKETHROUGH: + celltext->strikethrough = g_value_get_boolean (value); + celltext->strikethrough_set = TRUE; + g_object_notify (object, "strikethrough_set"); + break; + + case PROP_UNDERLINE: + celltext->underline_style = g_value_get_enum (value); + celltext->underline_set = TRUE; + g_object_notify (object, "underline_set"); + + break; + + case PROP_RISE: + celltext->rise = g_value_get_int (value); + celltext->rise_set = TRUE; + g_object_notify (object, "rise_set"); + if (celltext->fixed_height_rows != -1) + celltext->calc_fixed_height = TRUE; + break; + + case PROP_BACKGROUND_SET: + celltext->background_set = g_value_get_boolean (value); + break; + + case PROP_FOREGROUND_SET: + celltext->foreground_set = g_value_get_boolean (value); + break; + + case PROP_FAMILY_SET: + case PROP_STYLE_SET: + case PROP_VARIANT_SET: + case PROP_WEIGHT_SET: + case PROP_STRETCH_SET: + case PROP_SIZE_SET: + if (!g_value_get_boolean (value)) + { + pango_font_description_unset_fields (celltext->font, + get_property_font_set_mask (param_id)); + } + else + { + PangoFontMask changed_mask; + + changed_mask = set_font_desc_fields (celltext->font, + get_property_font_set_mask (param_id)); + notify_fields_changed (G_OBJECT (celltext), changed_mask); + } + break; + + case PROP_SCALE_SET: + celltext->scale_set = g_value_get_boolean (value); + break; + + case PROP_EDITABLE_SET: + celltext->editable_set = g_value_get_boolean (value); + break; + + case PROP_STRIKETHROUGH_SET: + celltext->strikethrough_set = g_value_get_boolean (value); + break; + + case PROP_UNDERLINE_SET: + celltext->underline_set = g_value_get_boolean (value); + break; + + case PROP_RISE_SET: + celltext->rise_set = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +/** + * egg_cell_renderer_text_new: + * + * Creates a new #EggCellRendererText. Adjust how text is drawn using + * object properties. Object properties can be + * set globally (with g_object_set()). Also, with #GtkTreeViewColumn, + * you can bind a property to a value in a #GtkTreeModel. For example, + * you can bind the "text" property on the cell renderer to a string + * value in the model, thus rendering a different string in each row + * of the #GtkTreeView + * + * Return value: the new cell renderer + **/ +GtkCellRenderer * +egg_cell_renderer_text_new (void) +{ + return g_object_new (EGG_TYPE_CELL_RENDERER_TEXT, NULL); +} + +static void +add_attr (PangoAttrList *attr_list, + PangoAttribute *attr) +{ + attr->start_index = 0; + attr->end_index = G_MAXINT; + + pango_attr_list_insert (attr_list, attr); +} + +static PangoLayout* +get_layout (EggCellRendererText *celltext, + GtkWidget *widget, + gboolean will_render, + gboolean ellipsize, + gint alloc_width, + GtkCellRendererState flags) +{ + PangoAttrList *attr_list; + PangoLayout *layout; + PangoUnderline uline; + + layout = gtk_widget_create_pango_layout (widget, NULL); + + if (!ellipsize) + { + pango_layout_set_text (layout, celltext->text, -1); + } + else + { + eel_pango_layout_set_text_ellipsized (layout, + celltext->text, + alloc_width, + EEL_ELLIPSIZE_END); + } + + if (celltext->extra_attrs) + attr_list = pango_attr_list_copy (celltext->extra_attrs); + else + attr_list = pango_attr_list_new (); + + if (will_render) + { + /* Add options that affect appearance but not size */ + + /* note that background doesn't go here, since it affects + * background_area not the PangoLayout area + */ + + if (celltext->foreground_set) + { + PangoColor color; + + color = celltext->foreground; + + add_attr (attr_list, + pango_attr_foreground_new (color.red, color.green, color.blue)); + } + + if (celltext->strikethrough_set) + add_attr (attr_list, + pango_attr_strikethrough_new (celltext->strikethrough)); + } + + add_attr (attr_list, pango_attr_font_desc_new (celltext->font)); + + if (celltext->scale_set && + celltext->font_scale != 1.0) + add_attr (attr_list, pango_attr_scale_new (celltext->font_scale)); + + if (celltext->underline_set) + uline = celltext->underline_style; + else + uline = PANGO_UNDERLINE_NONE; + + if ((flags & GTK_CELL_RENDERER_PRELIT) == GTK_CELL_RENDERER_PRELIT) + { + switch (uline) + { + case PANGO_UNDERLINE_NONE: + uline = PANGO_UNDERLINE_SINGLE; + break; + + case PANGO_UNDERLINE_SINGLE: + uline = PANGO_UNDERLINE_DOUBLE; + break; + + default: + break; + } + } + + if (uline != PANGO_UNDERLINE_NONE) + add_attr (attr_list, pango_attr_underline_new (celltext->underline_style)); + + if (celltext->rise_set) + add_attr (attr_list, pango_attr_rise_new (celltext->rise)); + + pango_layout_set_attributes (layout, attr_list); + pango_layout_set_width (layout, -1); + + pango_attr_list_unref (attr_list); + + return layout; +} + +static void +egg_cell_renderer_text_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *cell_area, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height) +{ + EggCellRendererText *celltext = (EggCellRendererText *) cell; + PangoRectangle rect; + PangoLayout *layout; + + if (celltext->calc_fixed_height) + { + PangoContext *context; + PangoFontMetrics *metrics; + PangoFontDescription *font_desc; + gint row_height; + + font_desc = pango_font_description_copy (widget->style->font_desc); + pango_font_description_merge (font_desc, celltext->font, TRUE); + + if (celltext->scale_set) + pango_font_description_set_size (font_desc, + celltext->font_scale * pango_font_description_get_size (font_desc)); + + context = gtk_widget_get_pango_context (widget); + + metrics = pango_context_get_metrics (context, + font_desc, + pango_context_get_language (context)); + row_height = (pango_font_metrics_get_ascent (metrics) + + pango_font_metrics_get_descent (metrics)); + pango_font_metrics_unref (metrics); + + gtk_cell_renderer_set_fixed_size (cell, + cell->width, 2*cell->ypad + + celltext->fixed_height_rows * PANGO_PIXELS (row_height)); + + if (height) + { + *height = cell->height; + height = NULL; + } + celltext->calc_fixed_height = FALSE; + if (width == NULL) + return; + } + layout = get_layout (celltext, widget, FALSE, FALSE, -1, 0); + pango_layout_get_pixel_extents (layout, NULL, &rect); + + if (width) + *width = GTK_CELL_RENDERER (celltext)->xpad * 2 + rect.width; + + if (height) + *height = GTK_CELL_RENDERER (celltext)->ypad * 2 + rect.height; + + if (cell_area) + { + if (x_offset) + { + *x_offset = ((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) ? + (1.0 - cell->xalign) : cell->xalign) * (cell_area->width - rect.width - (2 * cell->xpad)); + *x_offset = MAX (*x_offset, 0); + } + if (y_offset) + { + *y_offset = cell->yalign * (cell_area->height - rect.height - (2 * cell->ypad)); + *y_offset = MAX (*y_offset, 0); + } + } + + g_object_unref (layout); +} + +static void +egg_cell_renderer_text_render (GtkCellRenderer *cell, + GdkDrawable *window, + GtkWidget *widget, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GdkRectangle *expose_area, + GtkCellRendererState flags) + +{ + EggCellRendererText *celltext = (EggCellRendererText *) cell; + PangoLayout *layout; + GtkStateType state; + gint x_offset; + gint y_offset; + gboolean ellipsize; + gint alloc_width; + + g_assert (GTK_IS_TREE_VIEW (widget)); + + /* FIXME: here's the hacky bit - don't think this is guaranteed to work */ + alloc_width = widget->allocation.width - cell_area->x; + ellipsize = celltext->ellipsize && (alloc_width < cell_area->width); + + layout = get_layout (celltext, widget, TRUE, ellipsize, alloc_width, flags); + + egg_cell_renderer_text_get_size (cell, widget, cell_area, &x_offset, &y_offset, NULL, NULL); + + if ((flags & GTK_CELL_RENDERER_SELECTED) == GTK_CELL_RENDERER_SELECTED) + { + if (GTK_WIDGET_HAS_FOCUS (widget)) + state = GTK_STATE_SELECTED; + else + state = GTK_STATE_ACTIVE; + } + else + { + if (GTK_WIDGET_STATE (widget) == GTK_STATE_INSENSITIVE) + state = GTK_STATE_INSENSITIVE; + else + state = GTK_STATE_NORMAL; + } + + if (celltext->background_set && state != GTK_STATE_SELECTED) + { + GdkColor color; + GdkGC *gc; + + color.red = celltext->background.red; + color.green = celltext->background.green; + color.blue = celltext->background.blue; + + gc = gdk_gc_new (window); + + gdk_gc_set_rgb_fg_color (gc, &color); + + gdk_draw_rectangle (window, + gc, + TRUE, + background_area->x, + background_area->y, + background_area->width, + background_area->height); + + g_object_unref (gc); + } + + gtk_paint_layout (widget->style, + window, + state, + TRUE, + cell_area, + widget, + "cellrenderertext", + cell_area->x + x_offset + cell->xpad, + cell_area->y + y_offset + cell->ypad, + layout); + + g_object_unref (layout); +} + +#ifndef DISABLE_EDITING +static void +egg_cell_renderer_text_editing_done (GtkCellEditable *entry, + gpointer data) +{ + const gchar *path; + const gchar *new_text; + EggCellRendererInfo *info; + + info = g_object_get_data (G_OBJECT (data), + EGG_CELL_RENDERER_INFO_KEY); + + if (info->focus_out_id > 0) + { + g_signal_handler_disconnect (entry, info->focus_out_id); + info->focus_out_id = 0; + } + + if (GTK_ENTRY (entry)->editing_canceled) + return; + + path = g_object_get_data (G_OBJECT (entry), EGG_CELL_RENDERER_TEXT_PATH); + new_text = gtk_entry_get_text (GTK_ENTRY (entry)); + + g_signal_emit (data, text_cell_renderer_signals[EDITED], 0, path, new_text); +} + +static gboolean +egg_cell_renderer_text_focus_out_event (GtkWidget *entry, + GdkEvent *event, + gpointer data) +{ + egg_cell_renderer_text_editing_done (GTK_CELL_EDITABLE (entry), data); + + /* entry needs focus-out-event */ + return FALSE; +} + +static GtkCellEditable * +egg_cell_renderer_text_start_editing (GtkCellRenderer *cell, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GtkCellRendererState flags) +{ + EggCellRendererText *celltext; + GtkWidget *entry; + EggCellRendererInfo *info; + + celltext = EGG_CELL_RENDERER_TEXT (cell); + + /* If the cell isn't editable we return NULL. */ + if (celltext->editable == FALSE) + return NULL; + + entry = g_object_new (GTK_TYPE_ENTRY, + "has_frame", FALSE, + NULL); + + if (celltext->text) + gtk_entry_set_text (GTK_ENTRY (entry), celltext->text); + g_object_set_data_full (G_OBJECT (entry), EGG_CELL_RENDERER_TEXT_PATH, g_strdup (path), g_free); + + gtk_editable_select_region (GTK_EDITABLE (entry), 0, -1); + + info = g_object_get_data (G_OBJECT (cell), + EGG_CELL_RENDERER_INFO_KEY); + + gtk_widget_show (entry); + g_signal_connect (entry, + "editing_done", + G_CALLBACK (egg_cell_renderer_text_editing_done), + celltext); + info->focus_out_id = g_signal_connect (entry, "focus_out_event", + G_CALLBACK (egg_cell_renderer_text_focus_out_event), + celltext); + + return GTK_CELL_EDITABLE (entry); + +} +#endif /* DISABLE_EDITING */ + +/** + * egg_cell_renderer_text_set_fixed_height_from_font: + * @renderer: A #EggCellRendererText + * @number_of_rows: Number of rows of text each cell renderer is allocated, or -1 + * + * Sets the height of a renderer to explicitly be determined by the "font" and + * "y_pad" property set on it. Further changes in these properties do not + * affect the height, so they must be accompanied by a subsequent call to this + * function. Using this function is unflexible, and should really only be used + * if calculating the size of a cell is too slow (ie, a massive number of cells + * displayed). If @number_of_rows is -1, then the fixed height is unset, and + * the height is determined by the properties again. + **/ +void +egg_cell_renderer_text_set_fixed_height_from_font (EggCellRendererText *renderer, + gint number_of_rows) +{ + g_return_if_fail (EGG_IS_CELL_RENDERER_TEXT (renderer)); + g_return_if_fail (number_of_rows == -1 || number_of_rows > 0); + + if (number_of_rows == -1) + { + gtk_cell_renderer_set_fixed_size (GTK_CELL_RENDERER (renderer), + GTK_CELL_RENDERER (renderer)->width, + -1); + } + else + { + renderer->fixed_height_rows = number_of_rows; + renderer->calc_fixed_height = TRUE; + } +} + +void +egg_cell_renderer_text_set_ellipsize (EggCellRendererText *renderer, + gboolean ellipsize) +{ + g_return_if_fail (EGG_IS_CELL_RENDERER_TEXT (renderer)); + + ellipsize = ellipsize != FALSE; + + if (renderer->ellipsize != ellipsize) + { + renderer->ellipsize = ellipsize; + + /* FIXME: update */ + + g_object_notify (G_OBJECT (renderer), "ellipsize"); + } +} + +gboolean +egg_cell_renderer_text_get_ellipsize (EggCellRendererText *renderer) +{ + g_return_val_if_fail (EGG_IS_CELL_RENDERER_TEXT (renderer), FALSE); + + return renderer->ellipsize; +} Index: cut-n-paste/eggcellrenderertext.h =================================================================== RCS file: cut-n-paste/eggcellrenderertext.h diff -N cut-n-paste/eggcellrenderertext.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ cut-n-paste/eggcellrenderertext.h 12 Jan 2004 17:15:55 -0000 @@ -0,0 +1,109 @@ +/* eggcellrenderertext.h + * Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __EGG_CELL_RENDERER_TEXT_H__ +#define __EGG_CELL_RENDERER_TEXT_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#define EGG_TYPE_CELL_RENDERER_TEXT (egg_cell_renderer_text_get_type ()) +#define EGG_CELL_RENDERER_TEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_CELL_RENDERER_TEXT, EggCellRendererText)) +#define EGG_CELL_RENDERER_TEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_CELL_RENDERER_TEXT, EggCellRendererTextClass)) +#define EGG_IS_CELL_RENDERER_TEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_CELL_RENDERER_TEXT)) +#define EGG_IS_CELL_RENDERER_TEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_CELL_RENDERER_TEXT)) +#define EGG_CELL_RENDERER_TEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_CELL_RENDERER_TEXT, EggCellRendererTextClass)) + +typedef struct _EggCellRendererText EggCellRendererText; +typedef struct _EggCellRendererTextClass EggCellRendererTextClass; + +struct _EggCellRendererText +{ + GtkCellRenderer parent; + + /*< private >*/ + gchar *text; + PangoFontDescription *font; + gdouble font_scale; + PangoColor foreground; + PangoColor background; + + PangoAttrList *extra_attrs; + + PangoUnderline underline_style; + + gint rise; + gint fixed_height_rows; + + guint strikethrough : 1; + guint ellipsize : 1; + + guint editable : 1; + + guint scale_set : 1; + + guint foreground_set : 1; + guint background_set : 1; + + guint underline_set : 1; + + guint rise_set : 1; + + guint strikethrough_set : 1; + + guint editable_set : 1; + guint calc_fixed_height : 1; +}; + +struct _EggCellRendererTextClass +{ + GtkCellRendererClass parent_class; + + void (* edited) (EggCellRendererText *cell_renderer_text, + const gchar *path, + const gchar *new_text); + + /* Padding for future expansion */ + void (*_gtk_reserved1) (void); + void (*_gtk_reserved2) (void); + void (*_gtk_reserved3) (void); + void (*_gtk_reserved4) (void); +}; + +GType egg_cell_renderer_text_get_type (void); +GtkCellRenderer *egg_cell_renderer_text_new (void); + +void egg_cell_renderer_text_set_fixed_height_from_font (EggCellRendererText *renderer, + gint number_of_rows); + +void egg_cell_renderer_text_set_ellipsize (EggCellRendererText *renderer, + gboolean ellipsize); +gboolean egg_cell_renderer_text_get_ellipsize (EggCellRendererText *renderer); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif /* __EGG_CELL_RENDERER_TEXT_H__ */ Index: cut-n-paste/eggintl.h =================================================================== RCS file: cut-n-paste/eggintl.h diff -N cut-n-paste/eggintl.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ cut-n-paste/eggintl.h 12 Jan 2004 17:15:55 -0000 @@ -0,0 +1,14 @@ +#ifndef __EGG_INTL_H__ +#define __EGG_INTL_H__ + +#include + +#ifndef _ +#define _(x) gettext(x) +#endif + +#ifndef N_ +#define N_(x) x +#endif + +#endif /* __EGG_INTL_H__ */ Index: cut-n-paste/eggmarshalers.list =================================================================== RCS file: cut-n-paste/eggmarshalers.list diff -N cut-n-paste/eggmarshalers.list --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ cut-n-paste/eggmarshalers.list 12 Jan 2004 17:15:55 -0000 @@ -0,0 +1 @@ +VOID:STRING,STRING Index: cut-n-paste/eggtreeprivate.h =================================================================== RCS file: cut-n-paste/eggtreeprivate.h diff -N cut-n-paste/eggtreeprivate.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ cut-n-paste/eggtreeprivate.h 12 Jan 2004 17:15:55 -0000 @@ -0,0 +1,53 @@ +/* eggtreeprivate.h + * Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __EGG_TREE_PRIVATE_H__ +#define __EGG_TREE_PRIVATE_H__ + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include + +/* FIXME: do something to make sure we're not relying on gtk+ internals */ + +/* cool ABI compat hack */ +#define EGG_CELL_RENDERER_INFO_KEY "gtk-cell-renderer-info" + +typedef struct _EggCellRendererInfo EggCellRendererInfo; +struct _EggCellRendererInfo +{ + GdkColor cell_background; + + /* text renderer */ + gulong focus_out_id; + + /* toggle renderer */ + guint inconsistent :1; +}; + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif /* __EGG_TREE_PRIVATE_H__ */ + Index: cut-n-paste/update-from-eel.sh =================================================================== RCS file: cut-n-paste/update-from-eel.sh diff -N cut-n-paste/update-from-eel.sh --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ cut-n-paste/update-from-eel.sh 12 Jan 2004 17:15:55 -0000 @@ -0,0 +1,25 @@ +#!/bin/sh + +function die() { + echo $* + exit 1 +} + +if test -z "$EELDIR"; then + echo "Must set EELDIR" + exit 1 +fi + +if test -z "$EELFILES"; then + echo "Must set EELFILES" + exit 1 +fi + +for FILE in $EELFILES; do + if cmp -s $EELDIR/$FILE $FILE; then + echo "File $FILE is unchanged" + else + cp $EELDIR/$FILE $FILE || die "Could not move $EELDIR/$FILE to $FILE" + echo "Updated $FILE" + fi +done