Merge branch 'wip/exalm/headerbar-dragging' into 'master'

Extract GtkWindow dragging and titlebar actions

Closes #2689

See merge request GNOME/gtk!1814
This commit is contained in:
Matthias Clasen 2020-05-10 18:15:57 +00:00
commit eae4a194b8
13 changed files with 833 additions and 575 deletions

View File

@ -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">

View File

@ -4309,6 +4309,24 @@ GTK_WINDOW_CONTROLS_GET_CLASS
gtk_window_controls_get_type
</SECTION>
<SECTION>
<FILE>gtkwindowhandle</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>

View File

@ -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

View File

@ -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>

View File

@ -21,9 +21,10 @@
#include "gtkheaderbarprivate.h"
#include "gtkbinlayout.h"
#include "gtkbox.h"
#include "gtkbuildable.h"
#include "gtkcenterlayout.h"
#include "gtkcenterbox.h"
#include "gtkcssnodeprivate.h"
#include "gtkintl.h"
#include "gtklabel.h"
@ -33,6 +34,7 @@
#include "gtktypebuiltins.h"
#include "gtkwidgetprivate.h"
#include "gtkwindowcontrols.h"
#include "gtkwindowhandle.h"
#include "a11y/gtkcontaineraccessible.h"
@ -86,18 +88,21 @@
*
* |[<!-- language="plain" -->
* headerbar
* box.start
* windowcontrols.start
* [other children]
* [Title Widget]
* box.end
* [other children]
* windowcontrols.end
* windowhandle
* box
* box.start
* windowcontrols.start
* [other children]
* [Title Widget]
* box.end
* [other children]
* windowcontrols.end
* ]|
*
* A #GtkHeaderBar's CSS node is called headerbar. It contains two box subnodes
* at the start and end of the headerbar, as well as a center node that
* represents the title.
* A #GtkHeaderBar's CSS node is called headerbar. It contains a windowhandle
* subnode, which contains a box subnode, which contains two box subnodes at
* the start and end of the headerbar, as well as a center node that represents
* the title.
*
* Each of the boxes contains a windowcontrols subnode, see #GtkWindowControls
* for details, as well as other children.
@ -120,6 +125,8 @@ struct _GtkHeaderBarClass
struct _GtkHeaderBarPrivate
{
GtkWidget *handle;
GtkWidget *center_box;
GtkWidget *start_box;
GtkWidget *end_box;
@ -184,11 +191,10 @@ static void
update_default_decoration (GtkHeaderBar *bar)
{
GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
GtkLayoutManager *layout = gtk_widget_get_layout_manager (GTK_WIDGET (bar));
gboolean have_children = FALSE;
/* Check whether we have any child widgets that we didn't add ourselves */
if (gtk_center_layout_get_center_widget (GTK_CENTER_LAYOUT (layout)) != NULL)
if (gtk_center_box_get_center_widget (GTK_CENTER_BOX (priv->center_box)) != NULL)
{
have_children = TRUE;
}
@ -264,7 +270,6 @@ static void
construct_title_label (GtkHeaderBar *bar)
{
GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
GtkLayoutManager *layout = gtk_widget_get_layout_manager (GTK_WIDGET (bar));
GtkWidget *label;
g_assert (priv->title_label == NULL);
@ -276,9 +281,7 @@ construct_title_label (GtkHeaderBar *bar)
gtk_label_set_single_line_mode (GTK_LABEL (label), TRUE);
gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
gtk_label_set_width_chars (GTK_LABEL (label), MIN_TITLE_CHARS);
gtk_widget_insert_after (label, GTK_WIDGET (bar), priv->start_box);
gtk_center_layout_set_center_widget (GTK_CENTER_LAYOUT (layout), label);
gtk_center_box_set_center_widget (GTK_CENTER_BOX (priv->center_box), label);
priv->title_label = label;
@ -319,13 +322,11 @@ gtk_header_bar_set_title_widget (GtkHeaderBar *bar,
if (title_widget != NULL)
{
GtkLayoutManager *layout = gtk_widget_get_layout_manager (GTK_WIDGET (bar));
priv->title_widget = title_widget;
gtk_widget_insert_after (priv->title_widget, GTK_WIDGET (bar), priv->start_box);
gtk_center_layout_set_center_widget (GTK_CENTER_LAYOUT (layout), title_widget);
gtk_center_box_set_center_widget (GTK_CENTER_BOX (priv->center_box), title_widget);
g_clear_pointer (&priv->title_label, gtk_widget_unparent);
priv->title_label = NULL;
}
else
{
@ -386,13 +387,14 @@ gtk_header_bar_dispose (GObject *object)
{
GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (GTK_HEADER_BAR (object));
g_clear_pointer (&priv->title_widget, gtk_widget_unparent);
g_clear_pointer (&priv->title_label, gtk_widget_unparent);
priv->title_widget = NULL;
priv->title_label = NULL;
priv->start_box = NULL;
priv->end_box = NULL;
g_clear_pointer (&priv->handle, gtk_widget_unparent);
G_OBJECT_CLASS (gtk_header_bar_parent_class)->dispose (object);
g_clear_pointer (&priv->start_box, gtk_widget_unparent);
g_clear_pointer (&priv->end_box, gtk_widget_unparent);
}
static void
@ -498,7 +500,6 @@ gtk_header_bar_remove (GtkContainer *container,
{
GtkHeaderBar *bar = GTK_HEADER_BAR (container);
GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
GtkLayoutManager *layout = gtk_widget_get_layout_manager (GTK_WIDGET (bar));
GtkWidget *parent;
gboolean removed = FALSE;
@ -515,7 +516,7 @@ gtk_header_bar_remove (GtkContainer *container,
removed = TRUE;
}
else if (parent == GTK_WIDGET (container) &&
gtk_center_layout_get_center_widget (GTK_CENTER_LAYOUT (layout)) == widget)
gtk_center_box_get_center_widget (GTK_CENTER_BOX (priv->center_box)) == widget)
{
gtk_widget_unparent (widget);
removed = TRUE;
@ -636,7 +637,7 @@ gtk_header_bar_class_init (GtkHeaderBarClass *class)
g_object_class_install_properties (object_class, LAST_PROP, header_bar_props);
gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_PANEL);
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_CENTER_LAYOUT);
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
gtk_widget_class_set_css_name (widget_class, I_("headerbar"));
}
@ -644,21 +645,24 @@ static void
gtk_header_bar_init (GtkHeaderBar *bar)
{
GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
GtkLayoutManager *layout;
priv->title_widget = NULL;
priv->decoration_layout = NULL;
priv->state = GDK_SURFACE_STATE_WITHDRAWN;
layout = gtk_widget_get_layout_manager (GTK_WIDGET (bar));
priv->handle = gtk_window_handle_new ();
gtk_widget_set_parent (priv->handle, GTK_WIDGET (bar));
priv->center_box = gtk_center_box_new ();
gtk_window_handle_set_child (GTK_WINDOW_HANDLE (priv->handle), priv->center_box);
priv->start_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_widget_add_css_class (priv->start_box, "start");
gtk_widget_set_parent (priv->start_box, GTK_WIDGET (bar));
gtk_center_layout_set_start_widget (GTK_CENTER_LAYOUT (layout), priv->start_box);
gtk_center_box_set_start_widget (GTK_CENTER_BOX (priv->center_box), priv->start_box);
priv->end_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_widget_add_css_class (priv->end_box, "end");
gtk_widget_set_parent (priv->end_box, GTK_WIDGET (bar));
gtk_center_layout_set_end_widget (GTK_CENTER_LAYOUT (layout), priv->end_box);
gtk_center_box_set_end_widget (GTK_CENTER_BOX (priv->center_box), priv->end_box);
construct_title_label (bar);
}

View File

@ -11440,12 +11440,13 @@ gtk_widget_remove_controller (GtkWidget *widget,
}
gboolean
_gtk_widget_consumes_motion (GtkWidget *widget,
GdkEventSequence *sequence)
gtk_widget_consumes_motion (GtkWidget *widget,
GtkWidget *parent,
GdkEventSequence *sequence)
{
GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
while (widget != NULL && !GTK_IS_WINDOW (widget))
while (widget != NULL && widget != parent)
{
guint i;

View File

@ -281,8 +281,9 @@ void _gtk_widget_update_parent_muxer (GtkWidget *widget
GtkActionMuxer * _gtk_widget_get_action_muxer (GtkWidget *widget,
gboolean create);
gboolean _gtk_widget_consumes_motion (GtkWidget *widget,
GdkEventSequence *sequence);
gboolean gtk_widget_consumes_motion (GtkWidget *widget,
GtkWidget *parent,
GdkEventSequence *sequence);
gboolean gtk_widget_has_tick_callback (GtkWidget *widget);

View File

@ -29,10 +29,8 @@
#include "gtkaccelgroupprivate.h"
#include "gtkactionable.h"
#include "gtkapplicationprivate.h"
#include "gtkbox.h"
#include "gtkbuildable.h"
#include "gtkbuilderprivate.h"
#include "gtkbutton.h"
#include "gtkcheckbutton.h"
#include "gtkcsscornervalueprivate.h"
#include "gtkcsscolorvalueprivate.h"
@ -42,9 +40,7 @@
#include "gtkeventcontrollerlegacy.h"
#include "gtkeventcontrollerkey.h"
#include "gtkeventcontrollermotion.h"
#include "gtkgesturedrag.h"
#include "gtkgestureclick.h"
#include "gtkgestureprivate.h"
#include "gtkheaderbar.h"
#include "gtkicontheme.h"
#include "gtkintl.h"
@ -52,9 +48,6 @@
#include "gtkmarshalers.h"
#include "gtkmessagedialog.h"
#include "gtkpointerfocusprivate.h"
#include "gtkpopovermenuprivate.h"
#include "gtkmodelbuttonprivate.h"
#include "gtkseparator.h"
#include "gtkprivate.h"
#include "gtkroot.h"
#include "gtknative.h"
@ -249,8 +242,6 @@ typedef struct
GdkSurfaceTypeHint type_hint;
GtkGesture *click_gesture;
GtkGesture *drag_gesture;
GtkGesture *bubble_drag_gesture;
GtkEventController *key_controller;
GtkEventController *application_shortcut_controller;
@ -328,7 +319,6 @@ typedef enum
GTK_WINDOW_REGION_EDGE_S,
GTK_WINDOW_REGION_EDGE_SE,
GTK_WINDOW_REGION_CONTENT,
GTK_WINDOW_REGION_TITLE,
} GtkWindowRegion;
typedef struct
@ -486,8 +476,6 @@ static void gtk_window_activate_close (GtkWidget *widget,
const char *action_name,
GVariant *parameter);
static void gtk_window_do_popup (GtkWindow *window,
GdkEvent *event);
static void gtk_window_css_changed (GtkWidget *widget,
GtkCssStyleChange *change);
static void gtk_window_state_flags_changed (GtkWidget *widget,
@ -1222,66 +1210,6 @@ gtk_window_close (GtkWindow *window)
g_object_unref (window);
}
static gboolean
gtk_window_titlebar_action (GtkWindow *window,
GdkEvent *event,
guint button,
gint n_press)
{
GtkWindowPrivate *priv = gtk_window_get_instance_private (window);
GtkSettings *settings;
gchar *action = NULL;
gboolean retval = TRUE;
settings = gtk_widget_get_settings (GTK_WIDGET (window));
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;
}
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"))
{
/*
* gtk header bar won't show the maximize button if the following
* properties are not met, apply the same to title bar actions for
* consistency.
*/
if (gtk_window_get_resizable (window))
_gtk_window_toggle_maximized (window);
}
else if (g_str_equal (action, "lower"))
gdk_toplevel_lower (GDK_TOPLEVEL (priv->surface));
else if (g_str_equal (action, "minimize"))
gdk_toplevel_minimize (GDK_TOPLEVEL (priv->surface));
else if (g_str_equal (action, "menu"))
gtk_window_do_popup (window, event);
else
{
g_warning ("Unsupported titlebar action %s", action);
retval = FALSE;
}
g_free (action);
return retval;
}
static void
click_gesture_pressed_cb (GtkGestureClick *gesture,
gint n_press,
@ -1290,14 +1218,12 @@ click_gesture_pressed_cb (GtkGestureClick *gesture,
GtkWindow *window)
{
GtkWindowPrivate *priv = gtk_window_get_instance_private (window);
GtkWidget *event_widget, *widget;
GdkEventSequence *sequence;
GtkWindowRegion region;
GdkEvent *event;
guint button;
gboolean window_drag = FALSE;
double tx, ty;
widget = GTK_WIDGET (window);
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);
@ -1305,192 +1231,32 @@ click_gesture_pressed_cb (GtkGestureClick *gesture,
if (!event)
return;
if (n_press > 1)
gtk_gesture_set_state (priv->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 (priv->drag_gesture, GTK_EVENT_SEQUENCE_DENIED);
return;
}
region = get_active_region_type (window, x, y);
if (button == GDK_BUTTON_SECONDARY && region == GTK_WINDOW_REGION_TITLE)
{
if (gtk_window_titlebar_action (window, 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 (priv->drag_gesture));
return;
}
else if (button == GDK_BUTTON_MIDDLE && region == GTK_WINDOW_REGION_TITLE)
{
if (gtk_window_titlebar_action (window, event, button, n_press))
gtk_gesture_set_sequence_state (GTK_GESTURE (gesture),
sequence, GTK_EVENT_SEQUENCE_CLAIMED);
return;
}
else if (button != GDK_BUTTON_PRIMARY)
if (button != GDK_BUTTON_PRIMARY)
return;
event_widget = gtk_get_event_widget ((GdkEvent *) event);
if (priv->maximized)
return;
if (region == GTK_WINDOW_REGION_TITLE)
gtk_window_update_toplevel (window);
switch (region)
{
case GTK_WINDOW_REGION_CONTENT:
if (event_widget != widget)
{
/* TODO: Have some way of enabling/disabling window-dragging on random widgets */
}
if (!window_drag)
{
gtk_gesture_set_sequence_state (GTK_GESTURE (gesture),
sequence, GTK_EVENT_SEQUENCE_DENIED);
return;
}
G_GNUC_FALLTHROUGH;
case GTK_WINDOW_REGION_TITLE:
if (n_press == 2)
gtk_window_titlebar_action (window, event, button, n_press);
if (gtk_widget_has_grab (widget))
gtk_gesture_set_sequence_state (GTK_GESTURE (gesture),
sequence, GTK_EVENT_SEQUENCE_CLAIMED);
break;
case GTK_WINDOW_REGION_EDGE_NW:
case GTK_WINDOW_REGION_EDGE_N:
case GTK_WINDOW_REGION_EDGE_NE:
case GTK_WINDOW_REGION_EDGE_W:
case GTK_WINDOW_REGION_EDGE_E:
case GTK_WINDOW_REGION_EDGE_SW:
case GTK_WINDOW_REGION_EDGE_S:
case GTK_WINDOW_REGION_EDGE_SE:
default:
if (!priv->maximized)
{
double tx, ty;
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
gdk_event_get_position (event, &tx, &ty);
gdk_surface_begin_resize_drag (priv->surface,
(GdkSurfaceEdge) region,
gdk_event_get_device ((GdkEvent *) event),
GDK_BUTTON_PRIMARY,
tx, ty,
gdk_event_get_time (event));
gtk_event_controller_reset (GTK_EVENT_CONTROLLER (gesture));
gtk_event_controller_reset (GTK_EVENT_CONTROLLER (priv->drag_gesture));
}
break;
}
}
static void
drag_gesture_begin_cb (GtkGestureDrag *gesture,
gdouble x,
gdouble y,
GtkWindow *window)
{
GtkWindowRegion region;
gboolean widget_drag = FALSE;
if (gdk_display_device_is_grabbed (gtk_widget_get_display (GTK_WIDGET (window)),
gtk_gesture_get_device (GTK_GESTURE (gesture))))
return;
region = get_active_region_type (window, x, y);
switch (region)
{
case GTK_WINDOW_REGION_TITLE:
/* Claim it */
break;
case GTK_WINDOW_REGION_CONTENT:
/* TODO: Have some way of enabling/disabling window-dragging on random widgets */
if (region == GTK_WINDOW_REGION_CONTENT)
return;
if (!widget_drag)
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
break;
gdk_event_get_position (event, &tx, &ty);
gdk_surface_begin_resize_drag (priv->surface,
(GdkSurfaceEdge) region,
gdk_event_get_device ((GdkEvent *) event),
GDK_BUTTON_PRIMARY,
tx, ty,
gdk_event_get_time (event));
case GTK_WINDOW_REGION_EDGE_NW:
case GTK_WINDOW_REGION_EDGE_N:
case GTK_WINDOW_REGION_EDGE_NE:
case GTK_WINDOW_REGION_EDGE_W:
case GTK_WINDOW_REGION_EDGE_E:
case GTK_WINDOW_REGION_EDGE_SW:
case GTK_WINDOW_REGION_EDGE_S:
case GTK_WINDOW_REGION_EDGE_SE:
default:
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
}
}
static void
drag_gesture_update_cb (GtkGestureDrag *gesture,
gdouble offset_x,
gdouble offset_y,
GtkWindow *window)
{
GtkWindowPrivate *priv = gtk_window_get_instance_private (window);
gint double_click_distance;
GtkSettings *settings;
settings = gtk_widget_get_settings (GTK_WIDGET (window));
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;
gdouble start_x, start_y;
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 (window) &&
!gtk_widget_has_grab (event_widget) &&
_gtk_widget_consumes_motion (event_widget, 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);
gdk_surface_begin_move_drag (priv->surface,
gtk_gesture_get_device (GTK_GESTURE (gesture)),
gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)),
(int)start_x, (int)start_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 (priv->click_gesture));
}
gtk_event_controller_reset (GTK_EVENT_CONTROLLER (gesture));
}
static void
@ -1853,21 +1619,6 @@ gtk_window_init (GtkWindow *window)
gtk_widget_add_controller (widget, controller);
}
static GtkGesture *
create_drag_gesture (GtkWindow *window)
{
GtkGesture *gesture;
gesture = gtk_gesture_drag_new ();
g_signal_connect (gesture, "drag-begin",
G_CALLBACK (drag_gesture_begin_cb), window);
g_signal_connect (gesture, "drag-update",
G_CALLBACK (drag_gesture_update_cb), window);
gtk_widget_add_controller (GTK_WIDGET (window), GTK_EVENT_CONTROLLER (gesture));
return gesture;
}
static void
gtk_window_constructed (GObject *object)
{
@ -1884,14 +1635,6 @@ gtk_window_constructed (GObject *object)
G_CALLBACK (click_gesture_pressed_cb), object);
gtk_widget_add_controller (GTK_WIDGET (object), GTK_EVENT_CONTROLLER (priv->click_gesture));
priv->drag_gesture = create_drag_gesture (window);
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->drag_gesture),
GTK_PHASE_CAPTURE);
priv->bubble_drag_gesture = create_drag_gesture (window);
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->bubble_drag_gesture),
GTK_PHASE_BUBBLE);
g_list_store_append (toplevel_list, window);
g_object_unref (window);
}
@ -4887,12 +4630,6 @@ gtk_window_unrealize (GtkWidget *widget)
gsk_renderer_unrealize (priv->renderer);
if (priv->popup_menu)
{
gtk_widget_destroy (priv->popup_menu);
priv->popup_menu = NULL;
}
/* Icons */
gtk_window_unrealize_icon (window);
@ -5230,7 +4967,6 @@ static GtkWindowRegion
get_active_region_type (GtkWindow *window, gint x, gint y)
{
GtkWindowPrivate *priv = gtk_window_get_instance_private (window);
GtkAllocation allocation;
gint i;
if (priv->client_decorated)
@ -5242,16 +4978,6 @@ get_active_region_type (GtkWindow *window, gint x, gint y)
}
}
if (priv->title_box != NULL &&
gtk_widget_get_visible (priv->title_box) &&
gtk_widget_get_child_visible (priv->title_box))
{
gtk_widget_get_allocation (priv->title_box, &allocation);
if (allocation.x <= x && allocation.x + allocation.width > x &&
allocation.y <= y && allocation.y + allocation.height > y)
return GTK_WINDOW_REGION_TITLE;
}
return GTK_WINDOW_REGION_CONTENT;
}
@ -5749,236 +5475,6 @@ _gtk_window_unset_focus_and_default (GtkWindow *window,
g_object_unref (window);
}
static void
popup_menu_closed (GtkPopover *popover,
GtkWindow *widget)
{
GtkWindowPrivate *priv = gtk_window_get_instance_private (GTK_WINDOW (widget));
g_clear_pointer (&priv->popup_menu, gtk_widget_unparent);
}
static GdkSurfaceState
gtk_window_get_state (GtkWindow *window)
{
GtkWindowPrivate *priv = gtk_window_get_instance_private (window);
if (priv->surface)
return gdk_toplevel_get_state (GDK_TOPLEVEL (priv->surface));
return 0;
}
static void
restore_window_clicked (GtkModelButton *button,
gpointer user_data)
{
GtkWindow *window = GTK_WINDOW (user_data);
GtkWindowPrivate *priv = gtk_window_get_instance_private (window);
GdkSurfaceState state;
if (priv->maximized)
{
gtk_window_unmaximize (window);
return;
}
state = gtk_window_get_state (window);
if (state & GDK_SURFACE_STATE_MINIMIZED)
gtk_window_unminimize (window);
}
static void
move_window_clicked (GtkModelButton *button,
gpointer user_data)
{
GtkWindow *window = GTK_WINDOW (user_data);
GtkWindowPrivate *priv = gtk_window_get_instance_private (window);
gdk_surface_begin_move_drag (priv->surface,
NULL,
0, /* 0 means "use keyboard" */
0, 0,
GDK_CURRENT_TIME);
}
static void
resize_window_clicked (GtkModelButton *button,
gpointer user_data)
{
GtkWindow *window = GTK_WINDOW (user_data);
GtkWindowPrivate *priv = gtk_window_get_instance_private (window);
gdk_surface_begin_resize_drag (priv->surface,
0,
NULL,
0, /* 0 means "use keyboard" */
0, 0,
GDK_CURRENT_TIME);
}
static void
minimize_window_clicked (GtkModelButton *button,
gpointer user_data)
{
GtkWindow *window = GTK_WINDOW (user_data);
GtkWindowPrivate *priv = gtk_window_get_instance_private (window);
/* Turns out, we can't minimize a maximized window */
if (priv->maximized)
gtk_window_unmaximize (window);
gtk_window_minimize (window);
}
static void
maximize_window_clicked (GtkModelButton *button,
gpointer user_data)
{
gtk_window_maximize (GTK_WINDOW (user_data));
}
static void
ontop_window_clicked (GtkModelButton *button,
gpointer user_data)
{
GtkWindow *window = (GtkWindow *)user_data;
GtkWindowPrivate *priv = gtk_window_get_instance_private (window);
gtk_popover_popdown (GTK_POPOVER (priv->popup_menu));
}
static void
close_window_clicked (GtkModelButton *button,
gpointer user_data)
{
GtkWindow *window = (GtkWindow *)user_data;
gtk_window_close (window);
}
static void
gtk_window_do_popup_fallback (GtkWindow *window,
GdkEvent *event)
{
GtkWindowPrivate *priv = gtk_window_get_instance_private (window);
GtkWidget *menuitem;
GdkSurfaceState state;
gboolean maximized, minimized;
GtkWidget *box;
if (priv->popup_menu)
gtk_widget_destroy (priv->popup_menu);
state = gtk_window_get_state (window);
minimized = (state & GDK_SURFACE_STATE_MINIMIZED) == GDK_SURFACE_STATE_MINIMIZED;
maximized = priv->maximized && !minimized;
priv->popup_menu = gtk_popover_menu_new ();
gtk_widget_set_parent (priv->popup_menu, priv->title_box);
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_popover_menu_add_submenu (GTK_POPOVER_MENU (priv->popup_menu), box, "main");
menuitem = gtk_model_button_new ();
g_object_set (menuitem, "text", _("Restore"), NULL);
/* "Restore" means "Unmaximize" or "Unminimize"
* (yes, some WMs allow window menu to be shown for minimized windows).
* Not restorable:
* - visible windows that are not maximized or minimized
* - non-resizable windows that are not minimized
* - non-normal windows
*/
if ((gtk_widget_is_visible (GTK_WIDGET (window)) &&
!(maximized || minimized)) ||
(!minimized && !priv->resizable))
gtk_widget_set_sensitive (menuitem, FALSE);
g_signal_connect (G_OBJECT (menuitem), "clicked",
G_CALLBACK (restore_window_clicked), window);
gtk_container_add (GTK_CONTAINER (box), menuitem);
menuitem = gtk_model_button_new ();
g_object_set (menuitem, "text", _("Move"), NULL);
if (maximized || minimized)
gtk_widget_set_sensitive (menuitem, FALSE);
g_signal_connect (G_OBJECT (menuitem), "clicked",
G_CALLBACK (move_window_clicked), window);
gtk_container_add (GTK_CONTAINER (box), menuitem);
menuitem = gtk_model_button_new ();
g_object_set (menuitem, "text", _("Resize"), NULL);
if (!priv->resizable || maximized || minimized)
gtk_widget_set_sensitive (menuitem, FALSE);
g_signal_connect (G_OBJECT (menuitem), "clicked",
G_CALLBACK (resize_window_clicked), window);
gtk_container_add (GTK_CONTAINER (box), menuitem);
menuitem = gtk_model_button_new ();
g_object_set (menuitem, "text", _("Minimize"), NULL);
if (minimized)
gtk_widget_set_sensitive (menuitem, FALSE);
g_signal_connect (G_OBJECT (menuitem), "clicked",
G_CALLBACK (minimize_window_clicked), window);
gtk_container_add (GTK_CONTAINER (box), menuitem);
menuitem = gtk_model_button_new ();
g_object_set (menuitem, "text", _("Maximize"), NULL);
if (maximized || !priv->resizable)
gtk_widget_set_sensitive (menuitem, FALSE);
g_signal_connect (G_OBJECT (menuitem), "clicked",
G_CALLBACK (maximize_window_clicked), window);
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", _("Always on Top"),
"role", GTK_BUTTON_ROLE_CHECK,
NULL);
if (maximized)
gtk_widget_set_sensitive (menuitem, FALSE);
g_signal_connect (G_OBJECT (menuitem), "clicked",
G_CALLBACK (ontop_window_clicked), window);
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);
if (!priv->deletable)
gtk_widget_set_sensitive (menuitem, FALSE);
g_signal_connect (G_OBJECT (menuitem), "clicked",
G_CALLBACK (close_window_clicked), window);
gtk_container_add (GTK_CONTAINER (box), menuitem);
g_signal_connect (priv->popup_menu, "closed",
G_CALLBACK (popup_menu_closed), window);
gtk_popover_popup (GTK_POPOVER (priv->popup_menu));
}
static void
gtk_window_do_popup (GtkWindow *window,
GdkEvent *event)
{
GtkWindowPrivate *priv = gtk_window_get_instance_private (window);
if (!gdk_toplevel_show_window_menu (GDK_TOPLEVEL (priv->surface), event))
gtk_window_do_popup_fallback (window, event);
}
/*********************************
* Functions related to resizing *
*********************************/

687
gtk/gtkwindowhandle.c Normal file
View File

@ -0,0 +1,687 @@
/*
* 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"
/**
* 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 (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]);
}

44
gtk/gtkwindowhandle.h Normal file
View File

@ -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

View File

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

View File

@ -1391,8 +1391,8 @@ headerbar {
transition: $backdrop_transition;
}
> box.start,
> box.end {
box.start,
box.end {
border-spacing: 6px;
}

View File

@ -1,19 +1,21 @@
window.background.csd:dir(ltr)
decoration:dir(ltr)
headerbar.titlebar:dir(ltr)
box.horizontal.start:dir(ltr)
stackswitcher.linked:dir(ltr)
button.text-button.toggle:dir(ltr):checked
color: rgb(255,192,203); /* bloomfilter-not.css:1:61-73 */
windowhandle:dir(ltr)
box:dir(ltr)
box.horizontal.start:dir(ltr)
stackswitcher.linked:dir(ltr)
button.text-button.toggle:dir(ltr):checked
color: rgb(255,192,203); /* bloomfilter-not.css:1:61-73 */
label:dir(ltr)
button.text-button.toggle:dir(ltr)
color: rgb(255,192,203); /* bloomfilter-not.css:1:61-73 */
label:dir(ltr)
button.text-button.toggle:dir(ltr)
color: rgb(255,192,203); /* bloomfilter-not.css:1:61-73 */
label:dir(ltr)
button.text-button.toggle:dir(ltr)
label:dir(ltr)
box.end.horizontal:dir(ltr)
label:dir(ltr)
button.text-button.toggle:dir(ltr)
label:dir(ltr)
box.end.horizontal:dir(ltr)
stack:dir(ltr)
box.horizontal:dir(ltr)
box.horizontal:dir(ltr)