[gnome-calendar] multi-choice: Add the popover property



commit b031c9591a4a391b3ec783102b299cd87a518dfd
Author: Adrien Plazas <kekun plazas laposte net>
Date:   Fri Apr 15 14:33:19 2022 +0200

    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 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <template class="GcalMultiChoice" parent="GtkBox">
+    <property name="spacing">6</property>
     <child>
       <object class="GtkButton" id="down_button">
         <property name="icon-name">pan-start-symbolic</property>
@@ -12,6 +13,18 @@
         </style>
       </object>
     </child>
+    <child>
+      <object class="GtkToggleButton" id="button">
+        <signal name="toggled" handler="button_toggled_cb" swapped="yes"/>
+        <style>
+          <class name="flat"/>
+          <class name="pill"/>
+          <class name="popup"/>
+        </style>
+        <accessibility>
+          <relation name="labelled-by">GcalMultiChoice</relation>
+          <relation name="described-by">GcalMultiChoice</relation>
+        </accessibility>
     <child>
       <object class="GtkStack" id="stack">
         <property name="hexpand">True</property>
@@ -37,6 +50,8 @@
 
       </object>
     </child>
+      </object>
+    </child>
     <child>
       <object class="GtkButton" id="up_button">
         <property name="icon-name">pan-end-symbolic</property>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]