[gtk/wip/exalm/headerbar-dragging: 23/27] Add GtkWindowHandle



commit eda0ff635a7b638fcba1200261bff7b64c8fe8ad
Author: Alexander Mikhaylenko <alexm gnome org>
Date:   Sat May 2 20:37:32 2020 +0500

    Add GtkWindowHandle

 docs/reference/gtk/gtk4-docs.xml     |   1 +
 docs/reference/gtk/gtk4-sections.txt |  18 +
 docs/reference/gtk/gtk4.types.in     |   1 +
 gtk/gtk.h                            |   1 +
 gtk/gtkwindowhandle.c                | 691 +++++++++++++++++++++++++++++++++++
 gtk/gtkwindowhandle.h                |  44 +++
 gtk/meson.build                      |   2 +
 7 files changed, 758 insertions(+)
---
diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml
index 63cc7d7579..6fcf4d2540 100644
--- a/docs/reference/gtk/gtk4-docs.xml
+++ b/docs/reference/gtk/gtk4-docs.xml
@@ -298,6 +298,7 @@
       <xi:include href="xml/gtktooltip.xml" />
       <xi:include href="xml/gtkwidgetpaintable.xml" />
       <xi:include href="xml/gtkwindowcontrols.xml" />
+      <xi:include href="xml/gtkwindowhandle.xml" />
     </chapter>
 
     <chapter id="AbstractObjects">
diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt
index 6a3f16b13a..3eaacc83b0 100644
--- a/docs/reference/gtk/gtk4-sections.txt
+++ b/docs/reference/gtk/gtk4-sections.txt
@@ -4309,6 +4309,24 @@ GTK_WINDOW_CONTROLS_GET_CLASS
 gtk_window_controls_get_type
 </SECTION>
 
+<SECTION>
+<FILE>gtkwindowchandle</FILE>
+<TITLE>GtkWindowHandle</TITLE>
+GtkWindowHandle
+gtk_window_handle_new
+gtk_window_handle_get_child
+gtk_window_handle_set_child
+<SUBSECTION Standard>
+GTK_WINDOW_HANDLE
+GTK_IS_WINDOW_HANDLE
+GTK_TYPE_WINDOW_HANDLE
+GTK_WINDOW_HANDLE_CLASS
+GTK_IS_WINDOW_HANDLE_CLASS
+GTK_WINDOW_HANDLE_GET_CLASS
+<SUBSECTION Private>
+gtk_window_handle_get_type
+</SECTION>
+
 <SECTION>
 <FILE>gtkmain</FILE>
 <TITLE>General</TITLE>
diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in
index 6fa275416c..8001a45b68 100644
--- a/docs/reference/gtk/gtk4.types.in
+++ b/docs/reference/gtk/gtk4.types.in
@@ -215,3 +215,4 @@ gtk_widget_get_type
 gtk_window_get_type
 gtk_window_controls_get_type
 gtk_window_group_get_type
+gtk_window_handle_get_type
diff --git a/gtk/gtk.h b/gtk/gtk.h
index e9bf5e1cff..ca76e135fc 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -253,6 +253,7 @@
 #include <gtk/gtkwindow.h>
 #include <gtk/gtkwindowcontrols.h>
 #include <gtk/gtkwindowgroup.h>
+#include <gtk/gtkwindowhandle.h>
 
 #include <gtk/gtk-autocleanups.h>
 
diff --git a/gtk/gtkwindowhandle.c b/gtk/gtkwindowhandle.c
new file mode 100644
index 0000000000..d7b97a39b5
--- /dev/null
+++ b/gtk/gtkwindowhandle.c
@@ -0,0 +1,691 @@
+/*
+ * Copyright (c) 2020 Alexander Mikhaylenko <alexm gnome org>
+ *
+ * This program 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 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "config.h"
+
+#include "gtkwindowhandle.h"
+
+#include "gtkbinlayout.h"
+#include "gtkbox.h"
+#include "gtkbuildable.h"
+#include "gtkgestureclick.h"
+#include "gtkgesturedrag.h"
+#include "gtkgestureprivate.h"
+#include "gtkintl.h"
+#include "gtkmodelbuttonprivate.h"
+#include "gtknative.h"
+#include "gtkpopovermenuprivate.h"
+#include "gtkprivate.h"
+#include "gtkseparator.h"
+#include "gtkwidgetprivate.h"
+#include "gtkwindowprivate.h"
+
+/**
+ * SECTION:gtkwindowhandle
+ * @Short_description: A titlebar area widget
+ * @Title: GtkWindowHandle
+ * @See_also: #GtkWindow, #GtkHeaderBar
+ *
+ * GtkWindowHandle is a titlebar area widget. When added into a window, it can
+ * be dragged to move the window, and handles right click double click and
+ * middle click as expected of a titlebar.
+ *
+ * # CSS nodes
+ *
+ * #GtkWindowHandle has a single CSS node with the name windowhandle.
+ */
+
+struct _GtkWindowHandle {
+  GtkWidget parent_instance;
+
+  GtkGesture *click_gesture;
+  GtkGesture *drag_gesture;
+  GtkGesture *bubble_drag_gesture;
+
+  GtkWidget *child;
+  GtkWidget *fallback_menu;
+};
+
+enum {
+  PROP_0,
+  PROP_CHILD,
+  LAST_PROP
+};
+
+static GParamSpec *props[LAST_PROP] = { NULL, };
+
+static void gtk_window_handle_buildable_iface_init (GtkBuildableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GtkWindowHandle, gtk_window_handle, GTK_TYPE_WIDGET,
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_window_handle_buildable_iface_init))
+
+static void
+lower_window (GtkWindowHandle *self)
+{
+  GdkSurface *surface =
+    gtk_native_get_surface (gtk_widget_get_native (GTK_WIDGET (self)));
+
+  gdk_toplevel_lower (GDK_TOPLEVEL (surface));
+}
+
+static GtkWindow *
+get_window (GtkWindowHandle *self)
+{
+  GtkRoot *root = gtk_widget_get_root (GTK_WIDGET (self));
+
+  if (GTK_IS_WINDOW (root))
+    return GTK_WINDOW (root);
+
+  return NULL;
+}
+
+static void
+restore_window_clicked (GtkModelButton  *button,
+                        GtkWindowHandle *self)
+{
+  GtkWindow *window = get_window (self);
+
+  if (!window)
+    return;
+
+  if (gtk_window_is_maximized (window))
+    gtk_window_unmaximize (window);
+}
+
+static void
+move_window_clicked (GtkModelButton  *button,
+                     GtkWindowHandle *self)
+{
+  GtkNative *native = gtk_widget_get_native (GTK_WIDGET (self));
+  GdkSurface *surface = gtk_native_get_surface (native);
+
+  gdk_surface_begin_move_drag (surface,
+                               NULL,
+                               0, /* 0 means "use keyboard" */
+                               0, 0,
+                               GDK_CURRENT_TIME);
+}
+
+static void
+resize_window_clicked (GtkModelButton  *button,
+                       GtkWindowHandle *self)
+{
+  GtkNative *native = gtk_widget_get_native (GTK_WIDGET (self));
+  GdkSurface *surface = gtk_native_get_surface (native);
+
+  gdk_surface_begin_resize_drag (surface,
+                                 0,
+                                 NULL,
+                                 0, /* 0 means "use keyboard" */
+                                 0, 0,
+                                 GDK_CURRENT_TIME);
+}
+
+static void
+minimize_window_clicked (GtkModelButton  *button,
+                         GtkWindowHandle *self)
+{
+  GtkWindow *window = get_window (self);
+
+  if (!window)
+    return;
+
+  /* Turns out, we can't minimize a maximized window */
+  if (gtk_window_is_maximized (window))
+    gtk_window_unmaximize (window);
+
+  gtk_window_minimize (window);
+}
+
+static void
+maximize_window_clicked (GtkModelButton  *button,
+                         GtkWindowHandle *self)
+{
+  GtkWindow *window = get_window (self);
+
+  if (window)
+    gtk_window_maximize (window);
+}
+
+static void
+close_window_clicked (GtkModelButton  *button,
+                      GtkWindowHandle *self)
+{
+  GtkWindow *window = get_window (self);
+
+  if (window)
+    gtk_window_close (window);
+}
+
+static void
+popup_menu_closed (GtkPopover      *popover,
+                   GtkWindowHandle *self)
+{
+  g_clear_pointer (&self->fallback_menu, gtk_widget_unparent);
+}
+
+static void
+do_popup_fallback (GtkWindowHandle *self,
+                   GdkEvent        *event)
+{
+  GdkRectangle rect = { 0, 0, 1, 1 };
+  GdkDevice *device;
+  GtkWidget *box, *menuitem;
+  GtkWindow *window;
+  gboolean maximized, resizable, deletable;
+
+  g_clear_pointer (&self->fallback_menu, gtk_widget_destroy);
+
+  window = get_window (self);
+
+  if (window)
+    {
+      maximized = gtk_window_is_maximized (window);
+      resizable = gtk_window_get_resizable (window);
+      deletable = gtk_window_get_deletable (window);
+    }
+  else
+    {
+      maximized = FALSE;
+      resizable = FALSE;
+      deletable = FALSE;
+    }
+
+  self->fallback_menu = gtk_popover_menu_new ();
+  gtk_widget_set_parent (self->fallback_menu, GTK_WIDGET (self));
+
+  gtk_popover_set_has_arrow (GTK_POPOVER (self->fallback_menu), FALSE);
+  gtk_widget_set_halign (self->fallback_menu, GTK_ALIGN_START);
+
+
+  device = gdk_event_get_device (event);
+
+  if (device && gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD)
+    device = gdk_device_get_associated_device (device);
+
+  if (device)
+    {
+      GdkSurface *surface;
+      double px, py;
+
+      surface = gtk_native_get_surface (gtk_widget_get_native (GTK_WIDGET (self)));
+      gdk_surface_get_device_position (surface, device, &px, &py, NULL);
+      rect.x = round (px);
+      rect.y = round (py);
+
+      gtk_widget_translate_coordinates (GTK_WIDGET (gtk_widget_get_native (GTK_WIDGET (self))),
+                                       GTK_WIDGET (self),
+                                       rect.x, rect.y,
+                                       &rect.x, &rect.y);
+    }
+
+  gtk_popover_set_pointing_to (GTK_POPOVER (self->fallback_menu), &rect);
+
+  box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+  gtk_popover_menu_add_submenu (GTK_POPOVER_MENU (self->fallback_menu), box, "main");
+
+  menuitem = gtk_model_button_new ();
+  g_object_set (menuitem, "text", _("Restore"), NULL);
+  gtk_widget_set_sensitive (menuitem, maximized && resizable);
+  g_signal_connect (G_OBJECT (menuitem), "clicked",
+                    G_CALLBACK (restore_window_clicked), self);
+  gtk_container_add (GTK_CONTAINER (box), menuitem);
+
+  menuitem = gtk_model_button_new ();
+  g_object_set (menuitem, "text", _("Move"), NULL);
+  gtk_widget_set_sensitive (menuitem, !maximized);
+  g_signal_connect (G_OBJECT (menuitem), "clicked",
+                    G_CALLBACK (move_window_clicked), self);
+  gtk_container_add (GTK_CONTAINER (box), menuitem);
+
+  menuitem = gtk_model_button_new ();
+  g_object_set (menuitem, "text", _("Resize"), NULL);
+  gtk_widget_set_sensitive (menuitem, resizable && !maximized);
+  g_signal_connect (G_OBJECT (menuitem), "clicked",
+                    G_CALLBACK (resize_window_clicked), self);
+  gtk_container_add (GTK_CONTAINER (box), menuitem);
+
+  menuitem = gtk_model_button_new ();
+  g_object_set (menuitem, "text", _("Minimize"), NULL);
+  g_signal_connect (G_OBJECT (menuitem), "clicked",
+                    G_CALLBACK (minimize_window_clicked), self);
+  gtk_container_add (GTK_CONTAINER (box), menuitem);
+
+  menuitem = gtk_model_button_new ();
+  g_object_set (menuitem, "text", _("Maximize"), NULL);
+  gtk_widget_set_sensitive (menuitem, resizable && !maximized);
+  g_signal_connect (G_OBJECT (menuitem), "clicked",
+                    G_CALLBACK (maximize_window_clicked), self);
+  gtk_container_add (GTK_CONTAINER (box), menuitem);
+
+  menuitem = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+  gtk_container_add (GTK_CONTAINER (box), menuitem);
+
+  menuitem = gtk_model_button_new ();
+  g_object_set (menuitem, "text", _("Close"), NULL);
+  gtk_widget_set_sensitive (menuitem, deletable);
+  g_signal_connect (G_OBJECT (menuitem), "clicked",
+                    G_CALLBACK (close_window_clicked), self);
+  gtk_container_add (GTK_CONTAINER (box), menuitem);
+
+  g_signal_connect (self->fallback_menu, "closed",
+                    G_CALLBACK (popup_menu_closed), self);
+  gtk_popover_popup (GTK_POPOVER (self->fallback_menu));
+}
+
+static void
+do_popup (GtkWindowHandle *self,
+          GdkEvent        *event)
+{
+  GdkSurface *surface =
+    gtk_native_get_surface (gtk_widget_get_native (GTK_WIDGET (self)));
+
+  if (!gdk_toplevel_show_window_menu (GDK_TOPLEVEL (surface), event))
+    do_popup_fallback (self, event);
+}
+
+static gboolean
+perform_titlebar_action (GtkWindowHandle *self,
+                         GdkEvent        *event,
+                         guint            button,
+                         gint             n_press)
+{
+  GtkSettings *settings;
+  gchar *action = NULL;
+  gboolean retval = TRUE;
+  GtkActionMuxer *context;
+
+  settings = gtk_widget_get_settings (GTK_WIDGET (self));
+  switch (button)
+    {
+    case GDK_BUTTON_PRIMARY:
+      if (n_press == 2)
+        g_object_get (settings, "gtk-titlebar-double-click", &action, NULL);
+      break;
+    case GDK_BUTTON_MIDDLE:
+      g_object_get (settings, "gtk-titlebar-middle-click", &action, NULL);
+      break;
+    case GDK_BUTTON_SECONDARY:
+      g_object_get (settings, "gtk-titlebar-right-click", &action, NULL);
+      break;
+    default:
+      break;
+    }
+
+  context = _gtk_widget_get_action_muxer (GTK_WIDGET (self), TRUE);
+
+  if (action == NULL)
+    retval = FALSE;
+  else if (g_str_equal (action, "none"))
+    retval = FALSE;
+    /* treat all maximization variants the same */
+  else if (g_str_has_prefix (action, "toggle-maximize"))
+    g_action_group_activate_action (G_ACTION_GROUP (context),
+                                    "window.toggle-maximized",
+                                    NULL);
+  else if (g_str_equal (action, "lower"))
+    lower_window (self);
+  else if (g_str_equal (action, "minimize"))
+    g_action_group_activate_action (G_ACTION_GROUP (context),
+                                    "window.minimize",
+                                    NULL);
+  else if (g_str_equal (action, "menu"))
+    do_popup (self, event);
+  else
+    {
+      g_warning ("Unsupported titlebar action %s", action);
+      retval = FALSE;
+    }
+
+  g_free (action);
+
+  return retval;
+}
+
+static void
+click_gesture_pressed_cb (GtkGestureClick *gesture,
+                          int              n_press,
+                          double           x,
+                          double           y,
+                          GtkWindowHandle *self)
+{
+  GtkWidget *widget;
+  GdkEventSequence *sequence;
+  GdkEvent *event;
+  guint button;
+  GtkRoot *root;
+
+  widget = GTK_WIDGET (self);
+  sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+  button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
+  event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
+  root = gtk_widget_get_root (widget);
+
+  if (!event)
+    return;
+
+  if (n_press > 1)
+    gtk_gesture_set_state (self->drag_gesture, GTK_EVENT_SEQUENCE_DENIED);
+
+  if (gdk_display_device_is_grabbed (gtk_widget_get_display (widget),
+                                     gtk_gesture_get_device (GTK_GESTURE (gesture))))
+    {
+      gtk_gesture_set_state (self->drag_gesture, GTK_EVENT_SEQUENCE_DENIED);
+      return;
+    }
+
+  switch (button)
+    {
+    case GDK_BUTTON_PRIMARY:
+      if (GTK_IS_WINDOW (root))
+        gtk_window_update_toplevel (GTK_WINDOW (root));
+
+      if (n_press == 2)
+        perform_titlebar_action (self, event, button, n_press);
+
+      if (gtk_widget_has_grab (GTK_WIDGET (root)))
+        gtk_gesture_set_sequence_state (GTK_GESTURE (gesture),
+                                        sequence, GTK_EVENT_SEQUENCE_CLAIMED);
+      break;
+
+    case GDK_BUTTON_SECONDARY:
+      if (perform_titlebar_action (self, event, button, n_press))
+        gtk_gesture_set_sequence_state (GTK_GESTURE (gesture),
+                                        sequence, GTK_EVENT_SEQUENCE_CLAIMED);
+
+      gtk_event_controller_reset (GTK_EVENT_CONTROLLER (gesture));
+      gtk_event_controller_reset (GTK_EVENT_CONTROLLER (self->drag_gesture));
+      break;
+
+    case GDK_BUTTON_MIDDLE:
+      if (perform_titlebar_action (self, event, button, n_press))
+        gtk_gesture_set_sequence_state (GTK_GESTURE (gesture),
+                                        sequence, GTK_EVENT_SEQUENCE_CLAIMED);
+      break;
+
+    default:
+      return;
+    }
+}
+
+static void
+drag_gesture_update_cb (GtkGestureDrag  *gesture,
+                        double           offset_x,
+                        double           offset_y,
+                        GtkWindowHandle *self)
+{
+  int double_click_distance;
+  GtkSettings *settings;
+
+  settings = gtk_widget_get_settings (GTK_WIDGET (self));
+  g_object_get (settings,
+                "gtk-double-click-distance", &double_click_distance,
+                NULL);
+
+  if (ABS (offset_x) > double_click_distance ||
+      ABS (offset_y) > double_click_distance)
+    {
+      GdkEventSequence *sequence;
+      double start_x, start_y;
+      gint window_x, window_y;
+      GtkNative *native;
+      GdkSurface *surface;
+
+      sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+
+      if (gtk_event_controller_get_propagation_phase (GTK_EVENT_CONTROLLER (gesture)) == GTK_PHASE_CAPTURE)
+        {
+          GtkWidget *event_widget = gtk_gesture_get_last_target (GTK_GESTURE (gesture), sequence);
+
+          /* Check whether the target widget should be left alone at handling
+           * the sequence, this is better done late to give room for gestures
+           * there to go denied.
+           *
+           * Besides claiming gestures, we must bail out too if there's gestures
+           * in the "none" state at this point, as those are still handling events
+           * and can potentially go claimed, and we don't want to stop the target
+           * widget from doing anything.
+           */
+          if (event_widget != GTK_WIDGET (self) &&
+              !gtk_widget_has_grab (event_widget) &&
+              gtk_widget_consumes_motion (event_widget, GTK_WIDGET (self), sequence))
+            {
+              gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
+              return;
+            }
+        }
+
+      gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+
+      gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y);
+
+      native = gtk_widget_get_native (GTK_WIDGET (self));
+      gtk_widget_translate_coordinates (GTK_WIDGET (self),
+                                        GTK_WIDGET (native),
+                                        start_x, start_y,
+                                        &window_x, &window_y);
+
+      surface = gtk_native_get_surface (native);
+      gdk_surface_begin_move_drag (surface,
+                                   gtk_gesture_get_device (GTK_GESTURE (gesture)),
+                                   gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)),
+                                   window_x, window_y,
+                                   gtk_event_controller_get_current_event_time (GTK_EVENT_CONTROLLER 
(gesture)));
+
+      gtk_event_controller_reset (GTK_EVENT_CONTROLLER (gesture));
+      gtk_event_controller_reset (GTK_EVENT_CONTROLLER (self->click_gesture));
+    }
+}
+
+static GtkGesture *
+create_drag_gesture (GtkWindowHandle *self)
+{
+  GtkGesture *gesture;
+
+  gesture = gtk_gesture_drag_new ();
+  g_signal_connect (gesture, "drag-update",
+                    G_CALLBACK (drag_gesture_update_cb), self);
+  gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
+
+  return gesture;
+}
+
+static void
+gtk_window_handle_unrealize (GtkWidget *widget)
+{
+  GtkWindowHandle *self = GTK_WINDOW_HANDLE (widget);
+
+  g_clear_pointer (&self->fallback_menu, gtk_widget_destroy);
+
+  GTK_WIDGET_CLASS (gtk_window_handle_parent_class)->unrealize (widget);
+}
+
+static void
+gtk_window_handle_dispose (GObject *object)
+{
+  GtkWindowHandle *self = GTK_WINDOW_HANDLE (object);
+
+  g_clear_pointer (&self->child, gtk_widget_unparent);
+
+  G_OBJECT_CLASS (gtk_window_handle_parent_class)->dispose (object);
+}
+
+static void
+gtk_window_handle_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  GtkWindowHandle *self = GTK_WINDOW_HANDLE (object);
+
+  switch (prop_id)
+    {
+    case PROP_CHILD:
+      g_value_set_object (value, gtk_window_handle_get_child (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_window_handle_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  GtkWindowHandle *self = GTK_WINDOW_HANDLE (object);
+
+  switch (prop_id)
+    {
+    case PROP_CHILD:
+      gtk_window_handle_set_child (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_window_handle_class_init (GtkWindowHandleClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->dispose = gtk_window_handle_dispose;
+  object_class->get_property = gtk_window_handle_get_property;
+  object_class->set_property = gtk_window_handle_set_property;
+
+  widget_class->unrealize = gtk_window_handle_unrealize;
+  widget_class->grab_focus = gtk_widget_grab_focus_none;
+  widget_class->focus = gtk_widget_focus_child;
+
+  props[PROP_CHILD] =
+      g_param_spec_object ("child",
+                           P_("Child"),
+                           P_("The child widget"),
+                           GTK_TYPE_WIDGET,
+                           GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  g_object_class_install_properties (object_class, LAST_PROP, props);
+
+  gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
+  gtk_widget_class_set_css_name (widget_class, I_("windowhandle"));
+}
+
+static void
+gtk_window_handle_init (GtkWindowHandle *self)
+{
+  self->click_gesture = gtk_gesture_click_new ();
+  gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (self->click_gesture), 0);
+  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (self->click_gesture),
+                                              GTK_PHASE_BUBBLE);
+  g_signal_connect (self->click_gesture, "pressed",
+                    G_CALLBACK (click_gesture_pressed_cb), self);
+  gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (self->click_gesture));
+
+  self->drag_gesture = create_drag_gesture (self);
+  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (self->drag_gesture),
+                                              GTK_PHASE_CAPTURE);
+
+  self->bubble_drag_gesture = create_drag_gesture (self);
+  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (self->bubble_drag_gesture),
+                                              GTK_PHASE_BUBBLE);
+}
+
+static GtkBuildableIface *parent_buildable_iface;
+
+static void
+gtk_window_handle_buildable_add_child (GtkBuildable *buildable,
+                                       GtkBuilder   *builder,
+                                       GObject      *child,
+                                       const gchar  *type)
+{
+  if (GTK_IS_WIDGET (child))
+    gtk_window_handle_set_child (GTK_WINDOW_HANDLE (buildable), GTK_WIDGET (child));
+  else
+    parent_buildable_iface->add_child (buildable, builder, child, type);
+}
+
+static void
+gtk_window_handle_buildable_iface_init (GtkBuildableIface *iface)
+{
+  parent_buildable_iface = g_type_interface_peek_parent (iface);
+
+  iface->add_child = gtk_window_handle_buildable_add_child;
+}
+
+/**
+ * gtk_window_handle_new:
+ *
+ * Creates a new #GtkWindowHandle.
+ *
+ * Returns: a new #GtkWindowHandle.
+ **/
+GtkWidget *
+gtk_window_handle_new (void)
+{
+  return g_object_new (GTK_TYPE_WINDOW_HANDLE, NULL);
+}
+
+/**
+ * gtk_window_handle_get_child:
+ * @self: a #GtkWindowHandle
+ *
+ * Gets the child widget of @self.
+ *
+ * Returns: (nullable) (transfer none): the child widget of @self
+ */
+GtkWidget *
+gtk_window_handle_get_child (GtkWindowHandle *self)
+{
+  g_return_val_if_fail (GTK_IS_WINDOW_HANDLE (self), NULL);
+
+  return self->child;
+}
+
+/**
+ * gtk_window_handle_set_child:
+ * @self: a #GtkWindowHandle
+ * @child: (allow-none): the child widget
+ *
+ * Sets the child widget of @self.
+ */
+void
+gtk_window_handle_set_child (GtkWindowHandle *self,
+                             GtkWidget       *child)
+{
+  g_return_if_fail (GTK_IS_WINDOW_HANDLE (self));
+  g_return_if_fail (child == NULL || GTK_IS_WIDGET (child));
+
+  if (self->child == child)
+    return;
+
+  g_clear_pointer (&self->child, gtk_widget_unparent);
+
+  self->child = child;
+
+  if (child)
+    gtk_widget_set_parent (child, GTK_WIDGET (self));
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CHILD]);
+}
diff --git a/gtk/gtkwindowhandle.h b/gtk/gtkwindowhandle.h
new file mode 100644
index 0000000000..1a64d08062
--- /dev/null
+++ b/gtk/gtkwindowhandle.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2020 Alexander Mikhaylenko <alexm gnome org>
+ *
+ * This program 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 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#pragma once
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gtk/gtkwidget.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_WINDOW_HANDLE (gtk_window_handle_get_type ())
+
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (GtkWindowHandle, gtk_window_handle, GTK, WINDOW_HANDLE, GtkWidget)
+
+GDK_AVAILABLE_IN_ALL
+GtkWidget * gtk_window_handle_new       (void);
+
+GDK_AVAILABLE_IN_ALL
+GtkWidget * gtk_window_handle_get_child (GtkWindowHandle *self);
+
+GDK_AVAILABLE_IN_ALL
+void        gtk_window_handle_set_child (GtkWindowHandle *self,
+                                         GtkWidget       *child);
+
+G_END_DECLS
diff --git a/gtk/meson.build b/gtk/meson.build
index 3307c1aac1..8287477888 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -391,6 +391,7 @@ gtk_public_sources = files([
   'gtkwindow.c',
   'gtkwindowcontrols.c',
   'gtkwindowgroup.c',
+  'gtkwindowhandle.c',
 ])
 
 gtk_private_type_headers = files([
@@ -621,6 +622,7 @@ gtk_public_headers = files([
   'gtkwindow.h',
   'gtkwindowcontrols.h',
   'gtkwindowgroup.h',
+  'gtkwindowhandle.h',
   'gtk-a11y.h',
   'gtk-autocleanups.h',
   'gtk.h',


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