diff --git a/src/gui/event-editor/event-editor.gresource.xml b/src/gui/event-editor/event-editor.gresource.xml index c933afd3..a2349c39 100644 --- a/src/gui/event-editor/event-editor.gresource.xml +++ b/src/gui/event-editor/event-editor.gresource.xml @@ -17,5 +17,6 @@ gcal-attendee-row.ui gcal-attendee-details-page.ui gcal-attendee-summary-row.ui + gcal-organizer-row.ui diff --git a/src/gui/event-editor/gcal-attendees-section.blp b/src/gui/event-editor/gcal-attendees-section.blp index e6b980bf..a7d0170a 100644 --- a/src/gui/event-editor/gcal-attendees-section.blp +++ b/src/gui/event-editor/gcal-attendees-section.blp @@ -1,5 +1,4 @@ using Gtk 4.0; -using Adw 1; template $GcalAttendeesSection: Gtk.Box { orientation: vertical; @@ -15,12 +14,8 @@ template $GcalAttendeesSection: Gtk.Box { "boxed-list", ] - Adw.ActionRow organizer_row { - title: _("Organizer"); - - styles [ - "property", - ] + $GcalOrganizerRow organizer_row { + organizer: bind template.organizer; } $GcalAttendeeSummaryRow summary_row { diff --git a/src/gui/event-editor/gcal-attendees-section.c b/src/gui/event-editor/gcal-attendees-section.c index 929d37c6..7f63ccde 100644 --- a/src/gui/event-editor/gcal-attendees-section.c +++ b/src/gui/event-editor/gcal-attendees-section.c @@ -28,7 +28,7 @@ #include "gcal-event-editor-section.h" #include "gcal-event-organizer.h" #include "gcal-event.h" -#include "gcal-utils.h" +#include "gcal-organizer-row.h" #include "glib-object.h" struct _GcalAttendeesSection @@ -37,8 +37,8 @@ struct _GcalAttendeesSection GcalContext *context; GcalEvent *event; + GcalEventOrganizer *organizer; - AdwActionRow *organizer_row; AdwActionRow *summary_row; GListStore *attendees; @@ -54,6 +54,7 @@ enum PROP_0, PROP_CONTEXT, PROP_ATTENDEES, + PROP_ORGANIZER, N_PROPS }; @@ -66,7 +67,6 @@ reset_section (GcalAttendeesSection *self, g_set_object (&self->event, event); g_list_store_remove_all (self->attendees); - adw_action_row_set_subtitle (self->organizer_row, NULL); } static void @@ -75,7 +75,6 @@ gcal_attendees_section_set_event (GcalEventEditorSection *section, GcalEventEditorFlags flags) { GcalAttendeesSection *self = GCAL_ATTENDEES_SECTION (section); - GcalEventOrganizer *organizer; g_autoptr (GSList) attendees = NULL; if (event == self->event) @@ -87,14 +86,7 @@ gcal_attendees_section_set_event (GcalEventEditorSection *section, return; /* set organizer */ - organizer = gcal_event_get_organizer (self->event); - if (organizer) - { - g_autofree const gchar *email = gcal_get_email_from_mailto_uri (gcal_event_organizer_get_uri (organizer)); - g_autofree const gchar *name_with_email = g_strdup_printf ("%s (%s)", gcal_event_organizer_get_name (organizer), email); - - adw_action_row_set_subtitle (self->organizer_row, name_with_email); - } + self->organizer = gcal_event_get_organizer (self->event); /* set attendees and summarize attendance status */ attendees = gcal_event_get_attendees (self->event); @@ -102,6 +94,7 @@ gcal_attendees_section_set_event (GcalEventEditorSection *section, g_list_store_append (self->attendees, GCAL_EVENT_ATTENDEE (l->data)); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ATTENDEES]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ORGANIZER]); } static void @@ -143,6 +136,10 @@ gcal_attendees_section_get_property (GObject *object, g_value_set_object (value, self->attendees); break; + case PROP_ORGANIZER: + g_value_set_object (value, self->organizer); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -163,6 +160,10 @@ gcal_attendees_section_set_property (GObject *object, self->context = g_value_dup_object (value); break; + case PROP_ORGANIZER: + self->organizer = g_value_get_object (value); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -193,6 +194,7 @@ gcal_attendees_section_class_init (GcalAttendeesSectionClass *klass) GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + g_type_ensure (GCAL_TYPE_ORGANIZER_ROW); g_type_ensure (GCAL_TYPE_ATTENDEE_SUMMARY_ROW); object_class->finalize = gcal_attendees_section_finalize; @@ -209,15 +211,31 @@ gcal_attendees_section_class_init (GcalAttendeesSectionClass *klass) GCAL_TYPE_CONTEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + /** + * GcalAttendeesSection:attendees: + * + * ListStore of #GcalEventAttendee. + * Passed to the summary rows #GcalAttendeeSummaryRow. + */ properties[PROP_ATTENDEES] = g_param_spec_object ("attendees", NULL, NULL, G_TYPE_LIST_STORE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + /** + * GcalAttendeesSection:organizer: + * + * Organizer of the event. + * #GcalOrganizerRow binds to this property. + */ + properties[PROP_ORGANIZER] = + g_param_spec_object ("organizer", NULL, NULL, + GCAL_TYPE_EVENT_ORGANIZER, + G_PARAM_READWRITE | 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-attendees-section.ui"); - gtk_widget_class_bind_template_child (widget_class, GcalAttendeesSection, organizer_row); gtk_widget_class_bind_template_child (widget_class, GcalAttendeesSection, summary_row); } diff --git a/src/gui/event-editor/gcal-organizer-row.blp b/src/gui/event-editor/gcal-organizer-row.blp new file mode 100644 index 00000000..229b1843 --- /dev/null +++ b/src/gui/event-editor/gcal-organizer-row.blp @@ -0,0 +1,28 @@ +using Gtk 4.0; +using Adw 1; + +template $GcalOrganizerRow: Adw.ActionRow { + title: _("Organizer"); + subtitle: bind $generate_subtitle(template.organizer) as ; + + styles [ + "property", + ] + + GestureClick gesture_click { + button: 3; + } +} + +PopoverMenu context_menu { + menu-model: context_menu_model; + has-arrow: false; + halign: start; +} + +menu context_menu_model { + item { + label: _("Copy email"); + action: "organizer-row.copy-email"; + } +} diff --git a/src/gui/event-editor/gcal-organizer-row.c b/src/gui/event-editor/gcal-organizer-row.c new file mode 100644 index 00000000..9fa0718b --- /dev/null +++ b/src/gui/event-editor/gcal-organizer-row.c @@ -0,0 +1,209 @@ +/* gcal-organizer-row.c + * + * Copyright (C) 2026 Alen Galinec + * + * 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 . + */ + +#include "gcal-organizer-row.h" + +#include +#include +#include +#include + +#include "gcal-event-organizer.h" +#include "gcal-utils.h" + +struct _GcalOrganizerRow +{ + AdwActionRow parent_instance; + + GcalEventOrganizer *organizer; + + GtkWidget *context_menu; + GtkGestureClick *gesture_click; + + GSimpleActionGroup *action_group; +}; + +G_DEFINE_FINAL_TYPE (GcalOrganizerRow, gcal_organizer_row, ADW_TYPE_ACTION_ROW) + +enum +{ + PROP_0, + PROP_ORGANIZER, + N_PROPS, +}; + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +static void +on_copy_email_cb (GSimpleAction *action, + GVariant *param, + gpointer user_data) +{ + GcalOrganizerRow *self = GCAL_ORGANIZER_ROW (user_data); + + GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (self)); + GdkClipboard *clipboard = gdk_display_get_clipboard (display); + + g_autofree const gchar *email = + gcal_get_email_from_mailto_uri (gcal_event_organizer_get_uri (self->organizer)); + + gdk_clipboard_set_text (clipboard, email); +} + +static void +show_context_menu (GcalOrganizerRow *self, + guint npress, + double x, + double y) +{ + g_assert (GCAL_IS_ORGANIZER_ROW (self)); + + const GdkRectangle position = { .x = x, .y = y }; + gtk_popover_set_pointing_to (GTK_POPOVER (self->context_menu), &position); + gtk_popover_popup (GTK_POPOVER (self->context_menu)); +} + +static gchar* +generate_subtitle (GcalOrganizerRow *self, + GcalEventOrganizer *organizer) +{ + g_autofree const gchar *email = NULL; + g_autofree const gchar *name_with_email = g_strdup_printf ("%s (%s)", gcal_event_organizer_get_name (organizer), email); + + if (organizer == NULL) + return NULL; + + email = gcal_get_email_from_mailto_uri (gcal_event_organizer_get_uri (organizer)); + + return g_strdup (name_with_email); +} + + +static void +gcal_organizer_row_dispose (GObject *object) +{ + GcalOrganizerRow *self = GCAL_ORGANIZER_ROW (object); + + g_clear_pointer (&self->context_menu, gtk_widget_unparent); + + G_OBJECT_CLASS (gcal_organizer_row_parent_class)->dispose (object); +} + +static void +gcal_organizer_row_finalize (GObject *object) +{ + GcalOrganizerRow *self = GCAL_ORGANIZER_ROW (object); + + g_clear_object (&self->gesture_click); + g_clear_object (&self->action_group); + + G_OBJECT_CLASS (gcal_organizer_row_parent_class)->finalize (object); +} + +static void +gcal_organizer_row_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GcalOrganizerRow *self = GCAL_ORGANIZER_ROW (object); + + switch (property_id) + { + case PROP_ORGANIZER: + self->organizer = g_value_get_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +gcal_organizer_row_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GcalOrganizerRow *self = GCAL_ORGANIZER_ROW (object); + + switch (property_id) + { + case PROP_ORGANIZER: + g_value_set_object (value, self->organizer); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +gcal_organizer_row_init (GcalOrganizerRow *self) +{ + static const GActionEntry actions[] = { + { "copy-email", on_copy_email_cb } + }; + + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_widget_set_parent (GTK_WIDGET (self->context_menu), GTK_WIDGET (self)); + + self->action_group = g_simple_action_group_new (); + g_action_map_add_action_entries (G_ACTION_MAP (self->action_group), + actions, + G_N_ELEMENTS (actions), + self); + + gtk_widget_insert_action_group (self->context_menu, + "organizer-row", + G_ACTION_GROUP (self->action_group)); + + g_signal_connect_swapped (self->gesture_click, "pressed", G_CALLBACK (show_context_menu), self); +} + +static void +gcal_organizer_row_class_init (GcalOrganizerRowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = gcal_organizer_row_dispose; + object_class->finalize = gcal_organizer_row_finalize; + object_class->set_property = gcal_organizer_row_set_property; + object_class->get_property = gcal_organizer_row_get_property; + + /** + * GcalOrganizerRow:organizer + * + * The #GcalEventOrganizer of the event for which the row is displayed. + */ + properties[PROP_ORGANIZER] = + g_param_spec_object ("organizer", NULL, NULL, + GCAL_TYPE_EVENT_ORGANIZER, + G_PARAM_READWRITE | 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-organizer-row.ui"); + + gtk_widget_class_bind_template_child (widget_class, GcalOrganizerRow, context_menu); + gtk_widget_class_bind_template_child (widget_class, GcalOrganizerRow, gesture_click); + + gtk_widget_class_bind_template_callback (widget_class, generate_subtitle); +} diff --git a/src/gui/event-editor/gcal-organizer-row.h b/src/gui/event-editor/gcal-organizer-row.h new file mode 100644 index 00000000..fdbbae03 --- /dev/null +++ b/src/gui/event-editor/gcal-organizer-row.h @@ -0,0 +1,32 @@ +/* gcal-organizer-row.h + * + * Copyright (C) 2026 Alen Galinec + * + * 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 . + */ + +#ifndef GCAL_ORGANIZER_ROW_H +#define GCAL_ORGANIZER_ROW_H + +#include +#include + +G_BEGIN_DECLS + +#define GCAL_TYPE_ORGANIZER_ROW (gcal_organizer_row_get_type ()) +G_DECLARE_FINAL_TYPE (GcalOrganizerRow, gcal_organizer_row, GCAL, ORGANIZER_ROW, AdwActionRow) + +G_END_DECLS + +#endif diff --git a/src/gui/event-editor/meson.build b/src/gui/event-editor/meson.build index eadba486..0ffcd907 100644 --- a/src/gui/event-editor/meson.build +++ b/src/gui/event-editor/meson.build @@ -20,6 +20,7 @@ blueprints = custom_target( 'gcal-attendee-row.blp', 'gcal-attendee-details-page.blp', 'gcal-attendee-summary-row.blp', + 'gcal-organizer-row.blp', ), output: '.', command: [ @@ -57,4 +58,5 @@ sources += files( 'gcal-attendee-row.c', 'gcal-attendee-details-page.c', 'gcal-attendee-summary-row.c', + 'gcal-organizer-row.c', )