From b031c9591a4a391b3ec783102b299cd87a518dfd Mon Sep 17 00:00:00 2001 From: Adrien Plazas Date: Fri, 15 Apr 2022 14:33:19 +0200 Subject: [PATCH] multi-choice: Add the popover property This allows a multi choice's label to be clicked to reveal a popover. It is needed by the date chooser, to have a combined month and year choice that can be split in two via a popover. This leaves the indendation purposefully broken to ease reviewing the actual changes. --- src/gui/event-editor/gcal-multi-choice.c | 240 ++++++++++++++++++++++ src/gui/event-editor/gcal-multi-choice.h | 5 + src/gui/event-editor/gcal-multi-choice.ui | 15 ++ 3 files changed, 260 insertions(+) diff --git a/src/gui/event-editor/gcal-multi-choice.c b/src/gui/event-editor/gcal-multi-choice.c index 4199b9b5..9a4e271e 100644 --- a/src/gui/event-editor/gcal-multi-choice.c +++ b/src/gui/event-editor/gcal-multi-choice.c @@ -26,6 +26,7 @@ struct _GcalMultiChoice { GtkBox parent; GtkWidget *down_button; + GtkWidget *button; GtkStack *stack; GtkWidget *up_button; gint value; @@ -38,6 +39,7 @@ struct _GcalMultiChoice GtkWidget *active; GtkWidget *label1; GtkWidget *label2; + GtkWidget *popover; GcalMultiChoiceFormatCallback format_cb; gpointer format_data; GDestroyNotify format_destroy; @@ -51,12 +53,14 @@ enum PROP_WRAP, PROP_ANIMATE, PROP_CHOICES, + PROP_POPOVER, NUM_PROPERTIES }; enum { WRAPPED, + ACTIVATE, LAST_SIGNAL }; @@ -172,6 +176,27 @@ go_down (GcalMultiChoice *self) g_signal_emit (self, signals[WRAPPED], 0); } +static void +update_sensitivity (GcalMultiChoice *self) +{ + gboolean has_popup; + + has_popup = self->popover != NULL; + + gtk_widget_set_can_target (self->button, has_popup); + + gtk_accessible_update_property (GTK_ACCESSIBLE (self->button), + GTK_ACCESSIBLE_PROPERTY_HAS_POPUP, has_popup, + -1); + if (self->popover != NULL) + gtk_accessible_update_relation (GTK_ACCESSIBLE (self->button), + GTK_ACCESSIBLE_RELATION_CONTROLS, self->popover, NULL, + -1); + else + gtk_accessible_reset_relation (GTK_ACCESSIBLE (self->button), + GTK_ACCESSIBLE_RELATION_CONTROLS); +} + static void button_clicked_cb (GtkWidget *button, GcalMultiChoice *self) @@ -184,10 +209,49 @@ button_clicked_cb (GtkWidget *button, g_assert_not_reached (); } +static void +button_toggled_cb (GcalMultiChoice *self) +{ + const gboolean active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->button)); + + if (self->popover) + { + if (active) + { + gtk_popover_popup (GTK_POPOVER (self->popover)); + gtk_accessible_update_state (GTK_ACCESSIBLE (self), + GTK_ACCESSIBLE_STATE_EXPANDED, TRUE, + -1); + } + else + { + gtk_popover_popdown (GTK_POPOVER (self->popover)); + gtk_accessible_reset_state (GTK_ACCESSIBLE (self), + GTK_ACCESSIBLE_STATE_EXPANDED); + } + } +} + +static gboolean +menu_deactivate_cb (GcalMultiChoice *self) +{ + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->button), FALSE); + + return TRUE; +} + +static void +popover_destroy_cb (GcalMultiChoice *menu_button) +{ + gcal_multi_choice_set_popover (menu_button, NULL); +} + static void gcal_multi_choice_init (GcalMultiChoice *self) { gtk_widget_init_template (GTK_WIDGET (self)); + + update_sensitivity (self); } static void @@ -204,6 +268,18 @@ gcal_multi_choice_dispose (GObject *object) self->format_destroy = NULL; } + if (self->popover) + { + g_signal_handlers_disconnect_by_func (self->popover, + menu_deactivate_cb, + object); + g_signal_handlers_disconnect_by_func (self->popover, + popover_destroy_cb, + object); + gtk_widget_unparent (self->popover); + self->popover = NULL; + } + G_OBJECT_CLASS (gcal_multi_choice_parent_class)->dispose (object); } @@ -237,6 +313,10 @@ gcal_multi_choice_get_property (GObject *object, g_value_set_boolean (value, self->animate); break; + case PROP_POPOVER: + g_value_set_object (value, self->popover); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -283,22 +363,116 @@ gcal_multi_choice_set_property (GObject *object, gcal_multi_choice_set_choices (self, (const gchar **)g_value_get_boxed (value)); break; + case PROP_POPOVER: + gcal_multi_choice_set_popover (self, g_value_get_object (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } +static void +gcal_multi_choice_notify (GObject *object, + GParamSpec *pspec) +{ + if (strcmp (pspec->name, "focus-on-click") == 0) + { + GcalMultiChoice *self = GCAL_MULTI_CHOICE (object); + + gtk_widget_set_focus_on_click (self->button, + gtk_widget_get_focus_on_click (GTK_WIDGET (self))); + } + + if (G_OBJECT_CLASS (gcal_multi_choice_parent_class)->notify) + G_OBJECT_CLASS (gcal_multi_choice_parent_class)->notify (object, pspec); +} + +static void +gcal_multi_choice_state_flags_changed (GtkWidget *widget, + GtkStateFlags previous_state_flags) +{ + GcalMultiChoice *self = GCAL_MULTI_CHOICE (widget); + + if (!gtk_widget_is_sensitive (widget)) + { + if (self->popover) + gtk_widget_hide (self->popover); + } +} + +static void +gcal_multi_choice_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + GcalMultiChoice *self = GCAL_MULTI_CHOICE (widget); + + gtk_widget_measure (self->button, + orientation, + for_size, + minimum, natural, + minimum_baseline, natural_baseline); + +} + +static void +gcal_multi_choice_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + GcalMultiChoice *self= GCAL_MULTI_CHOICE (widget); + + gtk_widget_size_allocate (self->button, + &(GtkAllocation) { 0, 0, width, height }, + baseline); + if (self->popover) + gtk_popover_present (GTK_POPOVER (self->popover)); +} + +static gboolean +gcal_multi_choice_focus (GtkWidget *widget, + GtkDirectionType direction) +{ + GcalMultiChoice *self = GCAL_MULTI_CHOICE (widget); + + if (self->popover && gtk_widget_get_visible (self->popover)) + return gtk_widget_child_focus (self->popover, direction); + else + return gtk_widget_child_focus (self->button, direction); +} + +static gboolean +gcal_multi_choice_grab_focus (GtkWidget *widget) +{ + GcalMultiChoice *self = GCAL_MULTI_CHOICE (widget); + + return gtk_widget_grab_focus (self->button); +} + static void gcal_multi_choice_class_init (GcalMultiChoiceClass *class) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); GObjectClass *object_class = G_OBJECT_CLASS (class); + object_class->notify = gcal_multi_choice_notify; object_class->dispose = gcal_multi_choice_dispose; object_class->set_property = gcal_multi_choice_set_property; object_class->get_property = gcal_multi_choice_get_property; + widget_class->measure = gcal_multi_choice_measure; + widget_class->size_allocate = gcal_multi_choice_size_allocate; + widget_class->state_flags_changed = gcal_multi_choice_state_flags_changed; + widget_class->focus = gcal_multi_choice_focus; + widget_class->grab_focus = gcal_multi_choice_grab_focus; + properties[PROP_VALUE] = g_param_spec_int ("value", "Value", "Value", G_MININT, G_MAXINT, 0, @@ -323,6 +497,10 @@ gcal_multi_choice_class_init (GcalMultiChoiceClass *class) g_param_spec_boxed ("choices", "Choices", "Choices", G_TYPE_STRV, G_PARAM_WRITABLE|G_PARAM_EXPLICIT_NOTIFY); + properties[PROP_POPOVER] = + g_param_spec_object ("popover", "Popover", "Popover", + GTK_TYPE_POPOVER, + G_PARAM_READWRITE); g_object_class_install_properties (object_class, NUM_PROPERTIES, properties); @@ -334,16 +512,28 @@ gcal_multi_choice_class_init (GcalMultiChoiceClass *class) NULL, NULL, NULL, G_TYPE_NONE, 0); + signals[ACTIVATE] = + g_signal_new ("activate", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + 0, + NULL, NULL, + NULL, + G_TYPE_NONE, 0); + + gtk_widget_class_set_activate_signal (widget_class, signals[ACTIVATE]); gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/ui/event-editor/gcal-multi-choice.ui"); gtk_widget_class_bind_template_child (widget_class, GcalMultiChoice, down_button); gtk_widget_class_bind_template_child (widget_class, GcalMultiChoice, up_button); + gtk_widget_class_bind_template_child (widget_class, GcalMultiChoice, button); gtk_widget_class_bind_template_child (widget_class, GcalMultiChoice, stack); gtk_widget_class_bind_template_child (widget_class, GcalMultiChoice, label1); gtk_widget_class_bind_template_child (widget_class, GcalMultiChoice, label2); gtk_widget_class_bind_template_callback (widget_class, button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, button_toggled_cb); gtk_widget_class_set_css_name (widget_class, "navigator"); } @@ -406,3 +596,53 @@ gcal_multi_choice_set_format_callback (GcalMultiChoice *self, apply_value (self, GTK_STACK_TRANSITION_TYPE_NONE); } + +void +gcal_multi_choice_set_popover (GcalMultiChoice *self, + GtkWidget *popover) +{ + g_return_if_fail (GCAL_IS_MULTI_CHOICE (self)); + g_return_if_fail (popover == NULL || GTK_IS_POPOVER (popover)); + + g_object_freeze_notify (G_OBJECT (self)); + + if (self->popover) + { + if (gtk_widget_get_visible (self->popover)) + gtk_widget_hide (self->popover); + + g_signal_handlers_disconnect_by_func (self->popover, + menu_deactivate_cb, + self); + g_signal_handlers_disconnect_by_func (self->popover, + popover_destroy_cb, + self); + + gtk_widget_unparent (self->popover); + } + + self->popover = popover; + + if (popover) + { + gtk_widget_set_parent (self->popover, GTK_WIDGET (self)); + g_signal_connect_swapped (self->popover, "closed", + G_CALLBACK (menu_deactivate_cb), self); + g_signal_connect_swapped (self->popover, "destroy", + G_CALLBACK (popover_destroy_cb), self); + gtk_popover_set_position (GTK_POPOVER (self->popover), GTK_POS_BOTTOM); + } + + update_sensitivity (self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_POPOVER]); + g_object_thaw_notify (G_OBJECT (self)); +} + +GtkPopover * +gcal_multi_choice_get_popover (GcalMultiChoice *self) +{ + g_return_val_if_fail (GCAL_IS_MULTI_CHOICE (self), NULL); + + return GTK_POPOVER (self->popover); +} diff --git a/src/gui/event-editor/gcal-multi-choice.h b/src/gui/event-editor/gcal-multi-choice.h index 9b3377ef..e2abfb73 100644 --- a/src/gui/event-editor/gcal-multi-choice.h +++ b/src/gui/event-editor/gcal-multi-choice.h @@ -34,6 +34,11 @@ gint gcal_multi_choice_get_value (GcalMultiChoic void gcal_multi_choice_set_value (GcalMultiChoice *self, gint value); +GtkPopover* gcal_multi_choice_get_popover (GcalMultiChoice *self); + +void gcal_multi_choice_set_popover (GcalMultiChoice *self, + GtkWidget *popover); + void gcal_multi_choice_set_choices (GcalMultiChoice *self, const gchar **selfs); diff --git a/src/gui/event-editor/gcal-multi-choice.ui b/src/gui/event-editor/gcal-multi-choice.ui index 5f0460f8..5d11136f 100644 --- a/src/gui/event-editor/gcal-multi-choice.ui +++ b/src/gui/event-editor/gcal-multi-choice.ui @@ -1,6 +1,7 @@