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',
)