From fe7678d90e194d06ccd29c0bb7f3e03e1b5d299a Mon Sep 17 00:00:00 2001 From: Douglas Fuller Date: Thu, 4 Jun 2020 16:13:41 -0400 Subject: [PATCH 1/5] event: Add and manage event attendees On event creation, fetch the attendees list from the underlying calendar event and make them available in GcalEvent --- src/core/gcal-event.c | 29 +++++++++++++++++++++++++++++ src/core/gcal-event.h | 5 +++++ 2 files changed, 34 insertions(+) diff --git a/src/core/gcal-event.c b/src/core/gcal-event.c index e44fa6878..a257ab5b5 100644 --- a/src/core/gcal-event.c +++ b/src/core/gcal-event.c @@ -26,6 +26,7 @@ #include "gcal-utils.h" #include "gcal-recurrence.h" +#include #include #define LIBICAL_TZID_PREFIX "/freeassociation.sourceforge.net/" @@ -87,6 +88,8 @@ struct _GcalEvent */ gchar *description; + GSList *attendees; + GDateTime *dt_start; GDateTime *dt_end; GcalRange *range; @@ -308,6 +311,7 @@ gcal_event_set_component_internal (GcalEvent *self, GDateTime *date_end; gboolean start_is_all_day, end_is_all_day; gchar *description, *location; + GSList *attendees; g_assert (self->component == NULL); @@ -386,6 +390,10 @@ gcal_event_set_component_internal (GcalEvent *self, else gcal_event_set_summary (self, ""); + /* Attendees */ + attendees = e_cal_component_get_attendees (component); + gcal_event_set_attendees (self, attendees); + /* Location */ location = e_cal_component_get_location (component); gcal_event_set_location (self, location ? location : ""); @@ -459,6 +467,7 @@ gcal_event_finalize (GObject *object) g_clear_pointer (&self->range, gcal_range_unref); g_clear_pointer (&self->summary, g_free); g_clear_pointer (&self->location, g_free); + g_clear_slist (&self->attendees, e_cal_component_attendee_free); g_clear_pointer (&self->description, g_free); g_clear_pointer (&self->alarms, g_hash_table_unref); g_clear_pointer (&self->uid, g_free); @@ -1034,6 +1043,26 @@ gcal_event_get_range (GcalEvent *self) return self->range; } +GSList * +gcal_event_get_attendees (GcalEvent *self) +{ + g_return_val_if_fail (GCAL_IS_EVENT (self), NULL); + + return self->attendees; +} + +void +gcal_event_set_attendees (GcalEvent *self, + GSList *attendees) +{ + g_return_if_fail (GCAL_IS_EVENT (self)); + + if (self->attendees) + g_clear_slist (&self->attendees, e_cal_component_attendee_free); + + self->attendees = attendees; +} + /** * gcal_event_get_description: * @self a #GcalEvent diff --git a/src/core/gcal-event.h b/src/core/gcal-event.h index a5859db9c..e058fed00 100644 --- a/src/core/gcal-event.h +++ b/src/core/gcal-event.h @@ -79,6 +79,11 @@ void gcal_event_set_date_start (GcalEvent GcalRange* gcal_event_get_range (GcalEvent *self); +GSList * gcal_event_get_attendees (GcalEvent *self); + +void gcal_event_set_attendees (GcalEvent *self, + GSList *attendees); + const gchar* gcal_event_get_description (GcalEvent *self); void gcal_event_set_description (GcalEvent *self, -- GitLab From 7b3db45b9de9481a9abaf299d1df23549dc083d7 Mon Sep 17 00:00:00 2001 From: Douglas Fuller Date: Thu, 4 Jun 2020 16:21:02 -0400 Subject: [PATCH 2/5] contacts: introduce gcal-contacts Add a class, GcalContacts, to provide an interface between event attendees and the evolution-data-server address book. When activated, connect to the user's backend address books. Searches query all of the user's active address books and always return the first match found in any address book. The order in which the address books are searched is undefined. --- meson.build | 1 + src/core/gcal-contacts.c | 204 +++++++++++++++++++++++++++++++++++++++ src/core/gcal-contacts.h | 22 +++++ src/core/meson.build | 1 + src/gcal-types.h | 1 + src/meson.build | 1 + 6 files changed, 230 insertions(+) create mode 100644 src/core/gcal-contacts.c create mode 100644 src/core/gcal-contacts.h diff --git a/meson.build b/meson.build index b81b95c97..cd42f218c 100644 --- a/meson.build +++ b/meson.build @@ -163,6 +163,7 @@ gsettings_desktop_schemas_dep = dependency('gsettings-desktop-schemas', version: libedataserverui_dep = dependency('libedataserverui-1.2', version: '>= 3.17.1') libedataserver_dep = dependency('libedataserver-1.2', version: '>= 3.17.1') libecal_dep = dependency('libecal-2.0', version: '>= 3.33.2') +libebook_dep = dependency('libebook-1.2', version: '>= 3.36.2') libsoup_dep = dependency('libsoup-2.4') libdazzle_dep = dependency('libdazzle-1.0', version: '>= 3.33.1') libhandy_dep = dependency('libhandy-1', version: '>= 1.0.0') diff --git a/src/core/gcal-contacts.c b/src/core/gcal-contacts.c new file mode 100644 index 000000000..ae9859580 --- /dev/null +++ b/src/core/gcal-contacts.c @@ -0,0 +1,204 @@ +#include "gcal-contacts.h" + +#include + +struct _GcalContacts +{ + GObject parent; + + /* type EBookClient */ + GSList *clients; +}; + +struct async_op +{ + GSList *client; + gchar *query_string; + + void (*user_callback)(EContact *, gpointer); + gpointer user_data; +}; + +G_DEFINE_TYPE (GcalContacts, gcal_contacts, G_TYPE_OBJECT); + +static void +gcal_contacts_init (GcalContacts *self) +{ +} + +static void +gcal_contacts_finalize (GObject *object) +{ + GcalContacts *self = GCAL_CONTACTS (object); + + g_clear_slist (&self->clients, g_object_unref); + + G_OBJECT_CLASS (gcal_contacts_parent_class)->finalize (object); +} + +static void +gcal_contacts_class_init (GcalContactsClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gcal_contacts_finalize; +} + +GcalContacts * +gcal_contacts_new () +{ + return g_object_new (GCAL_TYPE_CONTACTS, NULL); +} + +static void +on_client_connected (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GcalContacts *self = GCAL_CONTACTS (user_data); + GError *error = NULL; + EClient *client; + + client = e_book_client_connect_finish (result, &error); + if (error != NULL) + { + g_warning ("error connecting to contacts registry: %s", error->message); + g_error_free (error); + return; + } + + if (client == NULL) + { + return; + } + + self->clients = g_slist_prepend (self->clients, E_BOOK_CLIENT (client)); +} + +static void +on_registry_added (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GcalContacts *self = GCAL_CONTACTS (user_data); + ESourceRegistry *reg; + GList *sources; + GError *error = NULL; + + reg = e_source_registry_new_finish (result, &error); + if (error != NULL) + { + g_warning ("error creating contacts registry: %s", error->message); + g_error_free (error); + } + + if (reg == NULL) + { + return; + } + + sources = e_source_registry_list_enabled (reg, E_SOURCE_EXTENSION_ADDRESS_BOOK); + while (sources != NULL) + { + e_book_client_connect (sources->data, 15, NULL, + on_client_connected, self); + sources = g_list_delete_link (sources, sources); + } +} + +void +gcal_contacts_start (GcalContacts *self) +{ + e_source_registry_new (NULL, on_registry_added, self); +} + +static void on_search_result (GObject *source_object, + GAsyncResult *result, gpointer user_data); + +static void +async_search_continue (struct async_op *op) +{ + e_book_client_get_contacts (op->client->data, op->query_string, NULL, + on_search_result, op); +} + +static void +async_search_complete (struct async_op *op, GSList *results) +{ + void (*cb)(EContact *, gpointer) = op->user_callback; + gpointer user_data = op->user_data; + EContact *result = NULL; + + g_free (op->query_string); + g_free (op); + + if (results != NULL) + { + result = g_object_ref (results->data); + g_slist_free_full (results, g_object_unref); + } + + cb (result, user_data); +} + +static void +on_search_result (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + struct async_op *op = user_data; + GSList *results = NULL; + GError *error = NULL; + + e_book_client_get_contacts_finish (op->client->data, result, &results, &error); + + op->client = op->client->next; + + if (results != NULL || op->client == NULL) + { + async_search_complete (op, results); + } + else + { + async_search_continue (op); + } +} + +gchar * +setup_query_string (ECalComponentAttendee *person) +{ + EBookQuery *query; + gchar *ret = NULL; + const gchar *email = e_cal_component_attendee_get_value (person); + + if (email == NULL) + { + return NULL; + } + if (g_str_has_prefix (email, "mailto:")) + { + email += strlen ("mailto:"); + } + + query = e_book_query_field_test (E_CONTACT_EMAIL, + E_BOOK_QUERY_IS, email); + ret = e_book_query_to_string (query); + g_free (query); + return ret; +} + +void +gcal_contacts_find (GcalContacts *self, ECalComponentAttendee *person, + void (*callback)(EContact *result, gpointer user_data), + gpointer user_data) +{ + struct async_op *op; + + op = g_malloc (sizeof (struct async_op)); + op->client = self->clients; + op->user_callback = callback; + op->user_data = user_data; + + op->query_string = setup_query_string (person); + async_search_continue (op); +} diff --git a/src/core/gcal-contacts.h b/src/core/gcal-contacts.h new file mode 100644 index 000000000..cfc9e2761 --- /dev/null +++ b/src/core/gcal-contacts.h @@ -0,0 +1,22 @@ +#pragma once + +#include "gcal-utils.h" +#include "gcal-types.h" + +#include + +G_BEGIN_DECLS + +#define GCAL_TYPE_CONTACTS (gcal_contacts_get_type ()) +G_DECLARE_FINAL_TYPE (GcalContacts, gcal_contacts, GCAL, CONTACTS, GObject) +GcalContacts * gcal_contacts_new (void); + +void gcal_contacts_start (GcalContacts *self); + +void gcal_contacts_find (GcalContacts *self, + ECalComponentAttendee *person, + void (*callback)(EContact *result, + gpointer user_data), + gpointer user_data); + +G_END_DECLS \ No newline at end of file diff --git a/src/core/meson.build b/src/core/meson.build index 90c0624be..04e1779b5 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -4,6 +4,7 @@ sources += files( 'gcal-calendar.c', 'gcal-calendar-monitor.c', 'gcal-clock.c', + 'gcal-contacts.c', 'gcal-context.c', 'gcal-event.c', 'gcal-log.c', diff --git a/src/gcal-types.h b/src/gcal-types.h index fc3a2941b..7dc603ee9 100644 --- a/src/gcal-types.h +++ b/src/gcal-types.h @@ -29,5 +29,6 @@ typedef struct _GcalContext GcalContext; typedef struct _GcalEvent GcalEvent; typedef struct _GcalTimeline GcalTimeline; typedef struct _GcalTimelineSubscriber GcalTimelineSubscriber; +typedef struct _GcalContacts GcalContacts; G_END_DECLS diff --git a/src/meson.build b/src/meson.build index 55d4f8554..0af71dba8 100644 --- a/src/meson.build +++ b/src/meson.build @@ -5,6 +5,7 @@ gcal_deps = [ libedataserverui_dep, libedataserver_dep, libecal_dep, + libebook_dep, libdazzle_dep, libsoup_dep, libical_dep, -- GitLab From 05b6e9ea45782688d7464c48f415e4f7d46b3867 Mon Sep 17 00:00:00 2001 From: Douglas Fuller Date: Thu, 4 Jun 2020 16:29:59 -0400 Subject: [PATCH 3/5] manager: add GcalContacts object Add the new GcalContacts object to GcalManager and initialize it when the manager is initialized. Expose the contacts via a getter method. --- src/core/gcal-manager.c | 20 ++++++++++++++++++-- src/core/gcal-manager.h | 1 + 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/core/gcal-manager.c b/src/core/gcal-manager.c index 626cbf77a..de71a66ff 100644 --- a/src/core/gcal-manager.c +++ b/src/core/gcal-manager.c @@ -20,6 +20,7 @@ #include "gcal-application.h" #include "gcal-context.h" +#include "gcal-contacts.h" #include "gcal-debug.h" #include "gcal-manager.h" #include "gcal-timeline.h" @@ -74,6 +75,8 @@ struct _GcalManager GcalTimeline *timeline; + GcalContacts *contacts; + GcalContext *context; }; @@ -538,7 +541,7 @@ source_get_last_credentials_required_arguments_cb (GObject *source_object, static void gcal_manager_finalize (GObject *object) { - GcalManager *self =GCAL_MANAGER (object); + GcalManager *self = GCAL_MANAGER (object); GCAL_ENTRY; @@ -552,6 +555,8 @@ gcal_manager_finalize (GObject *object) g_clear_pointer (&self->clients, g_hash_table_destroy); + g_clear_object (&self->contacts); + G_OBJECT_CLASS (gcal_manager_parent_class)->finalize (object); GCAL_EXIT; @@ -794,6 +799,14 @@ gcal_manager_get_timeline (GcalManager *self) return self->timeline; } +GcalContacts* +gcal_manager_get_contacts (GcalManager *self) +{ + g_return_val_if_fail (GCAL_IS_MANAGER (self), NULL); + + return self->contacts; +} + /** * gcal_manager_add_source: * @self: a #GcalManager @@ -1265,7 +1278,7 @@ gcal_manager_startup (GcalManager *self) cred_source = e_source_credentials_provider_ref_credentials_source (credentials_provider, source); if (cred_source && !e_source_equal (source, cred_source)) - { + { e_credentials_prompter_set_auto_prompt_disabled_for (self->credentials_prompter, cred_source, FALSE); /* Only consider SSL errors */ @@ -1300,5 +1313,8 @@ gcal_manager_startup (GcalManager *self) g_list_free (sources); + self->contacts = gcal_contacts_new (); + gcal_contacts_start (self->contacts); + GCAL_EXIT; } diff --git a/src/core/gcal-manager.h b/src/core/gcal-manager.h index 22684e865..7ad7238b5 100644 --- a/src/core/gcal-manager.h +++ b/src/core/gcal-manager.h @@ -44,6 +44,7 @@ void gcal_manager_set_default_calendar (GcalManager GcalTimeline* gcal_manager_get_timeline (GcalManager *self); +GcalContacts* gcal_manager_get_contacts (GcalManager *self); void gcal_manager_refresh (GcalManager *self); void gcal_manager_create_event (GcalManager *self, -- GitLab From 63a67e3e7caec884970367735267f7bc82fad214 Mon Sep 17 00:00:00 2001 From: Douglas Fuller Date: Thu, 4 Jun 2020 16:34:13 -0400 Subject: [PATCH 4/5] attendee: Introduce GcalAttendee Add a widget to represent a meeting attendee. The attendee will search the address book to determine the attendee's name and photo. --- .../event-editor/event-editor.gresource.xml | 1 + src/gui/event-editor/gcal-attendee.c | 228 ++++++++++++++++++ src/gui/event-editor/gcal-attendee.h | 12 + src/gui/event-editor/gcal-attendee.ui | 22 ++ src/gui/event-editor/meson.build | 1 + 5 files changed, 264 insertions(+) create mode 100644 src/gui/event-editor/gcal-attendee.c create mode 100644 src/gui/event-editor/gcal-attendee.h create mode 100644 src/gui/event-editor/gcal-attendee.ui diff --git a/src/gui/event-editor/event-editor.gresource.xml b/src/gui/event-editor/event-editor.gresource.xml index cec9f0ff2..73aa96ab4 100644 --- a/src/gui/event-editor/event-editor.gresource.xml +++ b/src/gui/event-editor/event-editor.gresource.xml @@ -2,6 +2,7 @@ gcal-alarm-row.ui + gcal-attendee.ui gcal-date-chooser.ui gcal-date-selector.ui gcal-event-editor-dialog.ui diff --git a/src/gui/event-editor/gcal-attendee.c b/src/gui/event-editor/gcal-attendee.c new file mode 100644 index 000000000..558751fa1 --- /dev/null +++ b/src/gui/event-editor/gcal-attendee.c @@ -0,0 +1,228 @@ +#include "config.h" + +#include "gcal-utils.h" +#include "gcal-contacts.h" +#include "gcal-attendee.h" + +#include + +struct _GcalAttendee +{ + GtkBox parent; + + GtkImage *icon; + GtkLabel *label; + + GcalContacts *contacts; + ECalComponentAttendee *attendee; +}; + +G_DEFINE_TYPE (GcalAttendee, gcal_attendee, GTK_TYPE_BOX) + +enum +{ + PROP_0, + PROP_CONTACTS, + PROP_ATTENDEE, + N_PROPS, +}; + +static GParamSpec *properties [N_PROPS] = { NULL, }; + +static void +load_photo (GcalAttendee *self, EContactPhoto *photo) +{ + g_autoptr (GdkPixbufLoader) loader = NULL; + + if (photo == NULL) + return; + + loader = gdk_pixbuf_loader_new (); + gdk_pixbuf_loader_set_size (loader, 24, 24); + + switch (photo->type) + { + case E_CONTACT_PHOTO_TYPE_INLINED: + gdk_pixbuf_loader_write (loader, photo->data.inlined.data, + photo->data.inlined.length, NULL); + break; + case E_CONTACT_PHOTO_TYPE_URI: + { + g_autofree gchar *filename = NULL; + g_autofree gchar *data = NULL; + gsize len; + + filename = g_filename_from_uri (photo->data.uri, NULL, NULL); + if (filename == NULL) + { + g_warning ("unsupported contact photo URI: %s", photo->data.uri); + return; + } + + if (g_file_get_contents (filename, &data, &len, NULL) == FALSE) + return; + + gdk_pixbuf_loader_write (loader, (const guchar *)data, len, NULL); + } + break; + + default: + g_warning_once ("unsupported contact photo type: %d", photo->type); + } + + gdk_pixbuf_loader_close (loader, NULL); + gtk_image_set_from_pixbuf (self->icon, + gdk_pixbuf_loader_get_pixbuf (loader)); +} + +static void +update (GcalAttendee *self, EContact *contact) +{ + gchar *name = NULL; + EContactPhoto *photo = NULL; + + if (contact == NULL) + return; + + name = e_contact_get (contact, E_CONTACT_FULL_NAME); + if (name == NULL) + return; + if (strlen (name)) + { + gtk_label_set_text (self->label, name); + } + + photo = e_contact_get (contact, E_CONTACT_PHOTO); + load_photo (self, photo); + + g_object_unref (contact); + g_free (name); + g_free (photo); +} + +static void +on_contact_found (EContact *result, gpointer user_data) +{ + GcalAttendee *self = GCAL_ATTENDEE (user_data); + update (self, result); +} + +static void +setup_widgets (GcalAttendee *self) +{ + const gchar *cn, *email; + + g_return_if_fail (self->contacts != NULL); + g_return_if_fail (self->attendee != NULL); + + cn = e_cal_component_attendee_get_cn (self->attendee); + gtk_label_set_text (self->label, cn); + + email = e_cal_component_attendee_get_value (self->attendee); + if (g_str_has_prefix (email, "mailto:")) + { + email += strlen("mailto:"); + } + gtk_widget_set_tooltip_markup (GTK_WIDGET (self), email); + + gcal_contacts_find (self->contacts, self->attendee, on_contact_found, self); +} + +static void +gcal_attendee_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GcalAttendee *self = GCAL_ATTENDEE (object); + + switch (prop_id) + { + case PROP_CONTACTS: + g_value_set_pointer (value, self->contacts); + break; + case PROP_ATTENDEE: + g_value_set_object (value, self->attendee); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_attendee_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GcalAttendee *self = GCAL_ATTENDEE (object); + + switch (prop_id) + { + case PROP_CONTACTS: + g_assert (self->contacts == NULL); + self->contacts = g_value_dup_object (value); + break; + case PROP_ATTENDEE: + g_assert (self->attendee == NULL); + self->attendee = g_value_get_pointer (value); + setup_widgets (self); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_attendee_finalize (GObject *object) +{ + GcalAttendee *self = GCAL_ATTENDEE (object); + + g_clear_object (&self->contacts); + + G_OBJECT_CLASS (gcal_attendee_parent_class)->finalize (object); +} + +static void +gcal_attendee_class_init (GcalAttendeeClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = gcal_attendee_finalize; + object_class->get_property = gcal_attendee_get_property; + object_class->set_property = gcal_attendee_set_property; + + properties[PROP_ATTENDEE] = g_param_spec_pointer ("attendee", + "Attendee", + "Attendee calendar object", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + properties[PROP_CONTACTS] = g_param_spec_object ("contacts", + "contacts directory", + "Contacts directory", + GCAL_TYPE_CONTACTS, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/ui/event-editor/gcal-attendee.ui"); + gtk_widget_class_bind_template_child (widget_class, GcalAttendee, icon); + gtk_widget_class_bind_template_child (widget_class, GcalAttendee, label); +} + +static void +gcal_attendee_init (GcalAttendee *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +GtkWidget * +gcal_attendee_new (GcalContacts *contacts, ECalComponentAttendee *attendee) +{ + return g_object_new (GCAL_TYPE_ATTENDEE, + "contacts", contacts, + "attendee", attendee, + NULL); +} diff --git a/src/gui/event-editor/gcal-attendee.h b/src/gui/event-editor/gcal-attendee.h new file mode 100644 index 000000000..22124ddc4 --- /dev/null +++ b/src/gui/event-editor/gcal-attendee.h @@ -0,0 +1,12 @@ +#pragma once + +G_BEGIN_DECLS + +#define GCAL_TYPE_ATTENDEE (gcal_attendee_get_type()) + +G_DECLARE_FINAL_TYPE (GcalAttendee, gcal_attendee, GCAL, ATTENDEE, GtkBox) + +GtkWidget * gcal_attendee_new (GcalContacts *contacts, + ECalComponentAttendee *attendee); + +G_END_DECLS \ No newline at end of file diff --git a/src/gui/event-editor/gcal-attendee.ui b/src/gui/event-editor/gcal-attendee.ui new file mode 100644 index 000000000..171aa756e --- /dev/null +++ b/src/gui/event-editor/gcal-attendee.ui @@ -0,0 +1,22 @@ + + + + diff --git a/src/gui/event-editor/meson.build b/src/gui/event-editor/meson.build index 1c9707ff7..ba9185229 100644 --- a/src/gui/event-editor/meson.build +++ b/src/gui/event-editor/meson.build @@ -8,6 +8,7 @@ built_sources += gnome.compile_resources( sources += files( 'gcal-alarm-row.c', + 'gcal-attendee.c', 'gcal-date-chooser.c', 'gcal-date-chooser-day.c', 'gcal-date-selector.c', -- GitLab From c512d82d240f501db898604fbdf4567f20066565 Mon Sep 17 00:00:00 2001 From: Douglas Fuller Date: Sat, 7 Nov 2020 14:01:29 -0500 Subject: [PATCH 5/5] event-editor: Show event attendees Add a GtkFlowBox to hold attendee widgets and populate it when the edit dialog is loaded. This is read-only for now. --- .../event-editor/event-editor.gresource.xml | 1 + src/gui/event-editor/gcal-attendees-section.c | 182 ++++++++++++++++++ src/gui/event-editor/gcal-attendees-section.h | 28 +++ .../event-editor/gcal-attendees-section.ui | 22 +++ .../event-editor/gcal-event-editor-dialog.c | 7 +- .../event-editor/gcal-event-editor-dialog.ui | 22 +++ src/gui/event-editor/meson.build | 1 + 7 files changed, 262 insertions(+), 1 deletion(-) create mode 100644 src/gui/event-editor/gcal-attendees-section.c create mode 100644 src/gui/event-editor/gcal-attendees-section.h create mode 100644 src/gui/event-editor/gcal-attendees-section.ui diff --git a/src/gui/event-editor/event-editor.gresource.xml b/src/gui/event-editor/event-editor.gresource.xml index 73aa96ab4..aae218088 100644 --- a/src/gui/event-editor/event-editor.gresource.xml +++ b/src/gui/event-editor/event-editor.gresource.xml @@ -3,6 +3,7 @@ gcal-alarm-row.ui gcal-attendee.ui + gcal-attendees-section.ui gcal-date-chooser.ui gcal-date-selector.ui gcal-event-editor-dialog.ui diff --git a/src/gui/event-editor/gcal-attendees-section.c b/src/gui/event-editor/gcal-attendees-section.c new file mode 100644 index 000000000..04e369957 --- /dev/null +++ b/src/gui/event-editor/gcal-attendees-section.c @@ -0,0 +1,182 @@ +/* gcal-attendees-section.c + * + * 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 3 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, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#define G_LOG_DOMAIN "GcalAttendeesSection" + +#include "gcal-context.h" +#include "gcal-debug.h" +#include "gcal-event-editor-section.h" +#include "gcal-attendees-section.h" +#include "gcal-attendee.h" + +struct _GcalAttendeesSection +{ + GtkBox parent; + + GtkWidget *attendees_list; + + GcalContext *context; + GcalEvent *event; +}; + +static void gcal_event_editor_section_iface_init (GcalEventEditorSectionInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (GcalAttendeesSection, gcal_attendees_section, + GTK_TYPE_BOX, + G_IMPLEMENT_INTERFACE (GCAL_TYPE_EVENT_EDITOR_SECTION, gcal_event_editor_section_iface_init)) + +enum +{ + PROP_0, + PROP_CONTEXT, + N_PROPS +}; + +/* + * GcalEventEditorSection interface + */ + +static void +gcal_attendees_section_set_event (GcalEventEditorSection *section, + GcalEvent *event, + GcalEventEditorFlags flags) +{ + GcalAttendeesSection *self; + GcalManager *manager; + GcalContacts *contacts; + GList *attendee_widgets = NULL, *p; + GSList *q; + + GCAL_ENTRY; + + self = GCAL_ATTENDEES_SECTION (section); + + g_set_object (&self->event, event); + + attendee_widgets = gtk_container_get_children (GTK_CONTAINER (self->attendees_list)); + for (p = attendee_widgets; p != NULL; p = p->next) + { + gtk_widget_destroy (GTK_WIDGET (p->data)); + } + g_list_free (attendee_widgets); + + if (!event) + GCAL_RETURN (); + + manager = gcal_context_get_manager (self->context); + contacts = gcal_manager_get_contacts (manager); + for (q = gcal_event_get_attendees (event); q != NULL; q = q->next) + { + gtk_container_add (GTK_CONTAINER (self->attendees_list), + gcal_attendee_new (contacts, q->data)); + } + gtk_widget_show_all (GTK_WIDGET (self->attendees_list)); + + GCAL_EXIT; +} + +static void +gcal_attendees_section_apply (GcalEventEditorSection *section) +{ + /* Write support not implemented yet. */ +} + +static void +gcal_event_editor_section_iface_init (GcalEventEditorSectionInterface *iface) +{ + iface->set_event = gcal_attendees_section_set_event; + iface->apply = gcal_attendees_section_apply; +} + + +/* + * GObject overrides + */ + +static void +gcal_attendees_section_finalize (GObject *object) +{ + GcalAttendeesSection *self = (GcalAttendeesSection *)object; + + g_clear_object (&self->context); + g_clear_object (&self->event); + + G_OBJECT_CLASS (gcal_attendees_section_parent_class)->finalize (object); +} + +static void +gcal_attendees_section_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GcalAttendeesSection *self = GCAL_ATTENDEES_SECTION (object); + + switch (prop_id) + { + case PROP_CONTEXT: + g_value_set_object (value, self->context); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_attendees_section_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GcalAttendeesSection *self = GCAL_ATTENDEES_SECTION (object); + + switch (prop_id) + { + case PROP_CONTEXT: + g_assert (self->context == NULL); + self->context = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_attendees_section_class_init (GcalAttendeesSectionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = gcal_attendees_section_finalize; + object_class->get_property = gcal_attendees_section_get_property; + object_class->set_property = gcal_attendees_section_set_property; + + g_object_class_override_property (object_class, PROP_CONTEXT, "context"); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/ui/event-editor/gcal-attendees-section.ui"); + + gtk_widget_class_bind_template_child (widget_class, GcalAttendeesSection, attendees_list); +} + +static void +gcal_attendees_section_init (GcalAttendeesSection *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} diff --git a/src/gui/event-editor/gcal-attendees-section.h b/src/gui/event-editor/gcal-attendees-section.h new file mode 100644 index 000000000..6136a8223 --- /dev/null +++ b/src/gui/event-editor/gcal-attendees-section.h @@ -0,0 +1,28 @@ +/* gcal-attendees-section.h + * + * 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 3 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, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define GCAL_TYPE_ATTENDEES_SECTION (gcal_attendees_section_get_type()) +G_DECLARE_FINAL_TYPE (GcalAttendeesSection, gcal_attendees_section, GCAL, ATTENDEES_SECTION, GtkBox) + +G_END_DECLS diff --git a/src/gui/event-editor/gcal-attendees-section.ui b/src/gui/event-editor/gcal-attendees-section.ui new file mode 100644 index 000000000..a4aaaa594 --- /dev/null +++ b/src/gui/event-editor/gcal-attendees-section.ui @@ -0,0 +1,22 @@ + + + + diff --git a/src/gui/event-editor/gcal-event-editor-dialog.c b/src/gui/event-editor/gcal-event-editor-dialog.c index 0d9eb0859..7d7bc8487 100644 --- a/src/gui/event-editor/gcal-event-editor-dialog.c +++ b/src/gui/event-editor/gcal-event-editor-dialog.c @@ -26,6 +26,7 @@ #include "gcal-event-editor-section.h" #include "gcal-utils.h" #include "gcal-event.h" +#include "gcal-attendees-section.h" #include "gcal-notes-section.h" #include "gcal-reminders-section.h" #include "gcal-schedule-section.h" @@ -55,6 +56,7 @@ struct _GcalEventEditorDialog GtkWidget *delete_button; GtkWidget *done_button; GtkWidget *lock; + GcalEventEditorSection *attendees_section; GcalEventEditorSection *notes_section; GcalEventEditorSection *reminders_section; GcalEventEditorSection *schedule_section; @@ -69,7 +71,7 @@ struct _GcalEventEditorDialog GtkWidget *title_label; - GcalEventEditorSection *sections[4]; + GcalEventEditorSection *sections[5]; GMenu *sources_menu; @@ -479,6 +481,7 @@ gcal_event_editor_dialog_class_init (GcalEventEditorDialogClass *klass) GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + g_type_ensure (GCAL_TYPE_ATTENDEES_SECTION); g_type_ensure (GCAL_TYPE_NOTES_SECTION); g_type_ensure (GCAL_TYPE_REMINDERS_SECTION); g_type_ensure (GCAL_TYPE_SCHEDULE_SECTION); @@ -535,6 +538,7 @@ gcal_event_editor_dialog_class_init (GcalEventEditorDialogClass *klass) gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/ui/event-editor/gcal-event-editor-dialog.ui"); + gtk_widget_class_bind_template_child (widget_class, GcalEventEditorDialog, attendees_section); gtk_widget_class_bind_template_child (widget_class, GcalEventEditorDialog, cancel_button); gtk_widget_class_bind_template_child (widget_class, GcalEventEditorDialog, delete_button); gtk_widget_class_bind_template_child (widget_class, GcalEventEditorDialog, done_button); @@ -567,6 +571,7 @@ gcal_event_editor_dialog_init (GcalEventEditorDialog *self) gtk_widget_init_template (GTK_WIDGET (self)); + self->sections[i++] = self->attendees_section; self->sections[i++] = self->notes_section; self->sections[i++] = self->reminders_section; self->sections[i++] = self->schedule_section; diff --git a/src/gui/event-editor/gcal-event-editor-dialog.ui b/src/gui/event-editor/gcal-event-editor-dialog.ui index d75831214..34858e547 100644 --- a/src/gui/event-editor/gcal-event-editor-dialog.ui +++ b/src/gui/event-editor/gcal-event-editor-dialog.ui @@ -212,6 +212,28 @@ + + + + True + False + True + 12 + 0.0 + Attendees + + + + + + + + + True + + + + diff --git a/src/gui/event-editor/meson.build b/src/gui/event-editor/meson.build index ba9185229..1c5b53f83 100644 --- a/src/gui/event-editor/meson.build +++ b/src/gui/event-editor/meson.build @@ -9,6 +9,7 @@ built_sources += gnome.compile_resources( sources += files( 'gcal-alarm-row.c', 'gcal-attendee.c', + 'gcal-attendees-section.c', 'gcal-date-chooser.c', 'gcal-date-chooser-day.c', 'gcal-date-selector.c', -- GitLab