[gtk/wip/matthiasc/popup2: 6/103] Introduce GtkPopup



commit 34a019414e8979920b361317970c92779488091d
Author: Matthias Clasen <mclasen redhat com>
Date:   Sun Feb 24 08:17:10 2019 -0500

    Introduce GtkPopup
    
    This is a from-scratch GtkRoot implementation
    that is meant to eventually replace popovers.

 gtk/gtkpopup.c | 525 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gtk/gtkpopup.h |  64 +++++++
 2 files changed, 589 insertions(+)
---
diff --git a/gtk/gtkpopup.c b/gtk/gtkpopup.c
new file mode 100644
index 0000000000..bdff7a1707
--- /dev/null
+++ b/gtk/gtkpopup.c
@@ -0,0 +1,525 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2019 Red Hat, Inc.
+ *
+ * Authors:
+ * - Matthias Clasen <mclasen redhat com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkpopup.h"
+#include "gtkroot.h"
+#include "gtkwidgetprivate.h"
+#include "gtkeventcontrollerkey.h"
+#include "gtkcssnodeprivate.h"
+#include "gtkbindings.h"
+#include "gtkenums.h"
+#include "gtktypebuiltins.h"
+#include "gdk/gdkeventsprivate.h"
+
+typedef struct {
+  GdkDisplay *display;
+  GskRenderer *renderer;
+  GdkSurface *surface;
+  GtkWidget *relative_to;
+  GtkWidget *focus_widget;
+  gboolean active;
+} GtkPopupPrivate;
+
+
+static void gtk_popup_root_interface_init (GtkRootInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GtkPopup, gtk_popup, GTK_TYPE_BIN,
+                         G_ADD_PRIVATE (GtkPopup)
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_ROOT,
+                                                gtk_popup_root_interface_init))
+
+
+static GdkDisplay *
+gtk_popup_root_get_display (GtkRoot *root)
+{
+  GtkPopup *popup = GTK_POPUP (root);
+  GtkPopupPrivate *priv = gtk_popup_get_instance_private (popup);
+
+  return priv->display;
+}
+
+static GskRenderer *
+gtk_popup_root_get_renderer (GtkRoot *root)
+{
+  GtkPopup *popup = GTK_POPUP (root);
+  GtkPopupPrivate *priv = gtk_popup_get_instance_private (popup);
+
+  return priv->renderer;
+}
+
+static void
+gtk_popup_root_get_surface_transform (GtkRoot *root,
+                                      int     *x,
+                                      int     *y)
+{
+  GtkStyleContext *context;
+  GtkBorder margin, border, padding;
+
+  context = gtk_widget_get_style_context (GTK_WIDGET (root));
+  gtk_style_context_get_margin (context, &margin);
+  gtk_style_context_get_border (context, &border);
+  gtk_style_context_get_padding (context, &padding);
+
+  *x = margin.left + border.left + padding.left;
+  *y = margin.top + border.top + padding.top;
+}
+
+static void
+gtk_popup_move_resize (GtkPopup *popup)
+{
+  GtkPopupPrivate *priv = gtk_popup_get_instance_private (popup);
+  GdkRectangle rect;
+ 
+  rect.x = 0;
+  rect.y = 0;
+  rect.width = gtk_widget_get_width (priv->relative_to);
+  rect.height = gtk_widget_get_height (priv->relative_to);
+  gtk_widget_translate_coordinates (priv->relative_to, gtk_widget_get_toplevel (priv->relative_to),
+                                    rect.x, rect.y, &rect.x, &rect.y);
+
+  gdk_surface_move_to_rect (priv->surface,
+                            &rect,
+                            GDK_GRAVITY_SOUTH,
+                            GDK_GRAVITY_NORTH,
+                            GDK_ANCHOR_FLIP_Y,
+                            0, 10);
+}
+
+static void
+gtk_popup_root_check_resize (GtkRoot *root)
+{
+  GtkPopup *popup = GTK_POPUP (root);
+  GtkPopupPrivate *priv = gtk_popup_get_instance_private (popup);
+  GtkWidget *widget = GTK_WIDGET (popup);
+
+  if (!_gtk_widget_get_alloc_needed (widget))
+    gtk_widget_ensure_allocate (widget);
+  else if (gtk_widget_get_visible (widget))
+    {
+      gtk_popup_move_resize (popup);
+      gtk_widget_allocate (GTK_WIDGET (popup),
+                           gdk_surface_get_width (priv->surface),
+                           gdk_surface_get_height (priv->surface),
+                           -1, NULL);
+    }
+}
+
+static void
+gtk_popup_root_interface_init (GtkRootInterface *iface)
+{
+  iface->get_display = gtk_popup_root_get_display;
+  iface->get_renderer = gtk_popup_root_get_renderer;
+  iface->get_surface_transform = gtk_popup_root_get_surface_transform;
+  iface->check_resize = gtk_popup_root_check_resize;
+}
+
+static void gtk_popup_set_is_active (GtkPopup *popup, gboolean active);
+
+static void
+gtk_popup_focus_in (GtkWidget *widget)
+{
+  gtk_popup_set_is_active (GTK_POPUP (widget), TRUE);
+}
+
+static void
+gtk_popup_focus_out (GtkWidget *widget)
+{
+  gtk_popup_set_is_active (GTK_POPUP (widget), FALSE);
+}
+
+
+static void
+gtk_popup_init (GtkPopup *popup)
+{
+  GtkEventController *controller;
+
+  gtk_widget_set_has_surface (GTK_WIDGET (popup), TRUE);
+
+  controller = gtk_event_controller_key_new ();
+  g_signal_connect_swapped (controller, "focus-in", G_CALLBACK (gtk_popup_focus_in), popup);
+  g_signal_connect_swapped (controller, "focus-out", G_CALLBACK (gtk_popup_focus_out), popup);
+  gtk_widget_add_controller (GTK_WIDGET (popup), controller);
+}
+
+static void
+gtk_popup_realize (GtkWidget *widget)
+{
+  GtkPopup *popup = GTK_POPUP (widget);
+  GtkPopupPrivate *priv = gtk_popup_get_instance_private (popup);
+  GdkRectangle allocation;
+
+  if (_gtk_widget_get_alloc_needed (widget))
+    {
+      allocation.x = 0;
+      allocation.y = 0;
+      allocation.width = 20; // FIXME
+      allocation.height = 20;
+      gtk_widget_size_allocate (widget, &allocation, -1);
+      gtk_widget_queue_resize (widget);
+    }
+
+  gtk_widget_get_allocation (widget, &allocation);
+
+#if 0
+  priv->surface = gdk_surface_new_popup (priv->display, &allocation);
+  // TODO xdg-popop window type
+  gdk_surface_set_transient_for (priv->surface, gtk_widget_get_surface (priv->relative_to));
+  gdk_surface_set_type_hint (priv->surface, GDK_SURFACE_TYPE_HINT_POPUP_MENU);
+  gdk_surface_move_to_rect (priv->surface,
+                            &allocation,
+                            GDK_GRAVITY_SOUTH,
+                            GDK_GRAVITY_NORTH,
+                            GDK_ANCHOR_FLIP_Y,
+                            0, 10);
+#else
+  priv->surface = gdk_surface_new_toplevel (priv->display, 20, 20);
+#endif
+
+  gtk_widget_set_surface (widget, priv->surface);
+  gtk_widget_register_surface (widget, priv->surface);
+
+  GTK_WIDGET_CLASS (gtk_popup_parent_class)->realize (widget);
+
+  priv->renderer = gsk_renderer_new_for_surface (priv->surface);
+}
+
+static void
+gtk_popup_unrealize (GtkWidget *widget)
+{
+  GtkPopup *popup = GTK_POPUP (widget);
+  GtkPopupPrivate *priv = gtk_popup_get_instance_private (popup);
+
+  GTK_WIDGET_CLASS (gtk_popup_parent_class)->unrealize (widget);
+
+  gsk_renderer_unrealize (priv->renderer);
+  g_clear_object (&priv->renderer);
+
+  g_clear_object (&priv->surface);
+}
+
+static void
+gtk_popup_move_focus (GtkWidget         *widget,
+                      GtkDirectionType   dir)
+{
+  gtk_widget_child_focus (widget, dir);
+
+  if (!gtk_widget_get_focus_child (widget))
+    gtk_root_set_focus (GTK_ROOT (widget), NULL);
+}
+
+static void
+gtk_popup_show (GtkWidget *widget)
+{
+  _gtk_widget_set_visible_flag (widget, TRUE);
+  gtk_css_node_validate (gtk_widget_get_css_node (widget));
+  gtk_widget_realize (widget);
+  gtk_widget_map (widget);
+
+  gtk_popup_move_focus (widget, GTK_DIR_TAB_FORWARD);
+}
+
+static void
+gtk_popup_hide (GtkWidget *widget)
+{
+  _gtk_widget_set_visible_flag (widget, FALSE);
+  gtk_widget_unmap (widget);
+}
+
+static void
+gtk_popup_map (GtkWidget *widget)
+{
+  GtkPopup *popup = GTK_POPUP (widget);
+  GtkPopupPrivate *priv = gtk_popup_get_instance_private (popup);
+  GtkWidget *child;
+
+  GTK_WIDGET_CLASS (gtk_popup_parent_class)->map (widget);
+
+  child = gtk_bin_get_child (GTK_BIN (widget));
+  if (child != NULL && gtk_widget_get_visible (child))
+    gtk_widget_map (child);
+
+  gdk_surface_show (priv->surface);
+}
+
+static void
+gtk_popup_unmap (GtkWidget *widget)
+{
+  GtkPopup *popup = GTK_POPUP (widget);
+  GtkPopupPrivate *priv = gtk_popup_get_instance_private (popup);
+  GtkWidget *child;
+
+  GTK_WIDGET_CLASS (gtk_popup_parent_class)->unmap (widget);
+
+  gdk_surface_hide (priv->surface);
+
+  child = gtk_bin_get_child (GTK_BIN (widget));
+  if (child != NULL)
+    gtk_widget_unmap (child);
+}
+
+static void
+gtk_popup_dispose (GObject *object)
+{
+  G_OBJECT_CLASS (gtk_popup_parent_class)->dispose (object);
+}
+
+static void
+gtk_popup_finalize (GObject *object)
+{
+  G_OBJECT_CLASS (gtk_popup_parent_class)->finalize (object);
+}
+
+static void
+gtk_popup_measure (GtkWidget      *widget,
+                   GtkOrientation  orientation,
+                   int             for_size,
+                   int            *minimum,
+                   int            *natural,
+                   int            *minimum_baseline,
+                   int            *natural_baseline)
+{
+  GtkWidget *child;
+
+  child = gtk_bin_get_child (GTK_BIN (widget));
+  gtk_widget_measure (child, orientation, for_size,
+                      minimum, natural,
+                      minimum_baseline, natural_baseline);
+}
+
+static void
+gtk_popup_size_allocate (GtkWidget *widget,
+                         int        width,
+                         int        height,
+                         int        baseline)
+{
+  GtkWidget *child;
+  GtkPopup *popup = GTK_POPUP (widget);
+  GtkPopupPrivate *priv = gtk_popup_get_instance_private (popup);
+
+  if (priv->surface)
+    {
+      // FIXME why is this needed ?
+      gdk_surface_move_resize (priv->surface, 0, 0, width, height);
+      gtk_popup_move_resize (popup);
+    }
+
+  child = gtk_bin_get_child (GTK_BIN (widget));
+  gtk_widget_size_allocate (child, &(GtkAllocation) { 0, 0, width, height }, baseline);
+}
+
+static void gtk_popup_set_focus (GtkPopup  *popup,
+                                 GtkWidget *widget);
+
+static void
+gtk_popup_set_property (GObject       *object,
+                         guint         prop_id,
+                         const GValue *value,
+                         GParamSpec   *pspec)
+{
+  GtkPopup *popup = GTK_POPUP (object);
+
+  switch (prop_id)
+    {
+    case 1 + GTK_ROOT_PROP_FOCUS_WIDGET:
+      gtk_popup_set_focus (popup, g_value_get_object (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_popup_get_property (GObject      *object,
+                        guint         prop_id,
+                        GValue       *value,
+                        GParamSpec   *pspec)
+{
+  GtkPopup *popup = GTK_POPUP (object);
+  GtkPopupPrivate *priv = gtk_popup_get_instance_private (popup);
+
+  switch (prop_id)
+    {
+    case 1 + GTK_ROOT_PROP_FOCUS_WIDGET:
+      g_value_set_object (value, priv->focus_widget);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+add_tab_bindings (GtkBindingSet    *binding_set,
+                  GdkModifierType   modifiers,
+                  GtkDirectionType  direction)
+{
+  gtk_binding_entry_add_signal (binding_set, GDK_KEY_Tab, modifiers,
+                                "move-focus", 1,
+                                GTK_TYPE_DIRECTION_TYPE, direction);
+  gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Tab, modifiers,
+                                "move-focus", 1,
+                                GTK_TYPE_DIRECTION_TYPE, direction);
+}
+
+static void
+gtk_popup_class_init (GtkPopupClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GtkBindingSet *binding_set;
+
+  object_class->dispose = gtk_popup_dispose;
+  object_class->finalize = gtk_popup_finalize;
+  object_class->set_property = gtk_popup_set_property;
+  object_class->get_property = gtk_popup_get_property;
+
+  widget_class->realize = gtk_popup_realize;
+  widget_class->unrealize = gtk_popup_unrealize;
+  widget_class->map = gtk_popup_map;
+  widget_class->unmap = gtk_popup_unmap;
+  widget_class->show = gtk_popup_show;
+  widget_class->hide = gtk_popup_hide;
+  widget_class->measure = gtk_popup_measure;
+  widget_class->size_allocate = gtk_popup_size_allocate;
+  widget_class->move_focus = gtk_popup_move_focus;
+
+  gtk_root_install_properties (object_class, 1);
+
+  binding_set = gtk_binding_set_by_class (klass);
+
+  add_tab_bindings (binding_set, 0, GTK_DIR_TAB_FORWARD);
+  add_tab_bindings (binding_set, GDK_CONTROL_MASK, GTK_DIR_TAB_FORWARD);
+  add_tab_bindings (binding_set, GDK_SHIFT_MASK, GTK_DIR_TAB_BACKWARD);
+  add_tab_bindings (binding_set, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_DIR_TAB_BACKWARD);
+}
+
+GtkWidget *
+gtk_popup_new (void)
+{
+  return GTK_WIDGET (g_object_new (GTK_TYPE_POPUP, NULL));
+}
+
+static void
+size_changed (GtkWidget *widget,
+              int        width,
+              int        height,
+              int        baseline,
+              GtkPopup  *popup)
+{
+  gtk_popup_move_resize (popup);
+}
+
+void
+gtk_popup_set_relative_to (GtkPopup  *popup,
+                           GtkWidget *relative_to)
+{
+  GtkPopupPrivate *priv = gtk_popup_get_instance_private (popup);
+  
+  priv->relative_to = relative_to;
+  g_signal_connect (priv->relative_to, "size-allocate", G_CALLBACK (size_changed), popup);
+  priv->display = gtk_widget_get_display (relative_to);
+}
+
+static void
+gtk_popup_set_focus (GtkPopup  *popup,
+                     GtkWidget *focus)
+{
+  GtkPopupPrivate *priv = gtk_popup_get_instance_private (popup);
+  GtkWidget *old_focus = NULL;
+  GdkSeat *seat;
+  GdkDevice *device;
+  GdkEvent *event;
+
+  if (focus && !gtk_widget_is_sensitive (focus))
+    return;
+
+  if (priv->focus_widget)
+    old_focus = g_object_ref (priv->focus_widget);
+  g_set_object (&priv->focus_widget, NULL);
+
+  seat = gdk_display_get_default_seat (gtk_widget_get_display (GTK_WIDGET (popup)));
+  device = gdk_seat_get_keyboard (seat);
+
+  event = gdk_event_new (GDK_FOCUS_CHANGE);
+  gdk_event_set_display (event, gtk_widget_get_display (GTK_WIDGET (popup)));
+  gdk_event_set_device (event, device);
+  event->any.surface = _gtk_widget_get_surface (GTK_WIDGET (popup));
+  if (event->any.surface)
+    g_object_ref (event->any.surface);
+
+  gtk_synthesize_crossing_events (GTK_ROOT (popup), old_focus, focus, event, GDK_CROSSING_NORMAL);
+
+  g_object_unref (event);
+
+  g_set_object (&priv->focus_widget, focus);
+
+  g_clear_object (&old_focus);
+
+  g_object_notify (G_OBJECT (popup), "focus-widget");
+}
+
+static void
+do_focus_change (GtkWidget *widget,
+                 gboolean   in)
+{
+  GdkSeat *seat;
+  GdkDevice *device;
+  GdkEvent *event;
+
+  seat = gdk_display_get_default_seat (gtk_widget_get_display (widget));
+  device = gdk_seat_get_keyboard (seat);
+
+  event = gdk_event_new (GDK_FOCUS_CHANGE);
+  gdk_event_set_display (event, gtk_widget_get_display (widget));
+  gdk_event_set_device (event, device);
+
+  event->any.type = GDK_FOCUS_CHANGE;
+  event->any.surface = _gtk_widget_get_surface (widget);
+  if (event->any.surface)
+    g_object_ref (event->any.surface);
+  event->focus_change.in = in;
+  event->focus_change.mode = GDK_CROSSING_STATE_CHANGED;
+  event->focus_change.detail = GDK_NOTIFY_ANCESTOR;
+
+  gtk_widget_set_has_focus (widget, in);
+  gtk_widget_event (widget, event);
+
+  g_object_unref (event);
+}
+
+static void
+gtk_popup_set_is_active (GtkPopup *popup,
+                         gboolean  active)
+{
+  GtkPopupPrivate *priv = gtk_popup_get_instance_private (popup);
+
+  if (priv->active == active)
+    return;
+
+  priv->active = active;
+
+  if (priv->focus_widget &&
+      priv->focus_widget != GTK_WIDGET (popup) &&
+      gtk_widget_has_focus (priv->focus_widget) != active)
+    do_focus_change (priv->focus_widget, active);
+}
diff --git a/gtk/gtkpopup.h b/gtk/gtkpopup.h
new file mode 100644
index 0000000000..a316741933
--- /dev/null
+++ b/gtk/gtkpopup.h
@@ -0,0 +1,64 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2019 Red Hat, Inc.
+ *
+ * Authors:
+ * - Matthias Clasen <mclasen redhat com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GTK_POPUP_H__
+#define __GTK_POPUP_H__
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gtk/gtkbin.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_POPUP                 (gtk_popup_get_type ())
+#define GTK_POPUP(obj)                 (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_POPUP, GtkPopup))
+#define GTK_POPUP_CLASS(klass)         (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_POPUP, GtkPopupClass))
+#define GTK_IS_POPUP(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_POPUP))
+#define GTK_IS_POPUP_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_POPUP))
+#define GTK_POPUP_GET_CLASS(obj)       (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_POPUP, GtkPopupClass))
+
+typedef struct _GtkPopup       GtkPopup;
+typedef struct _GtkPopupClass  GtkPopupClass;
+
+struct _GtkPopup
+{
+  GtkBin parent;
+};
+
+struct _GtkPopupClass
+{
+  GtkBinClass parent_class;
+};
+
+GDK_AVAILABLE_IN_ALL
+GType           gtk_popup_get_type (void) G_GNUC_CONST;
+
+GDK_AVAILABLE_IN_ALL
+GtkWidget *     gtk_popup_new      (void);
+
+GDK_AVAILABLE_IN_ALL
+void            gtk_popup_set_relative_to (GtkPopup   *popup,
+                                           GtkWidget  *relative_to);
+
+G_END_DECLS
+
+#endif /* __GTK_POPUP_H__ */


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