Learning_GTK4_tree/gtk/gtkpaned.c

2565 lines
82 KiB
C
Raw Permalink Normal View History

2023-12-12 11:36:42 +01:00
/* GTK - The GIMP Toolkit
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* 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/>.
*/
/*
* Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
* file for a list of people on the GTK+ Team. See the ChangeLog
* files for a list of changes. These files are distributed with
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
*/
#include "config.h"
#include "gtkpaned.h"
#include "gtkaccessiblerange.h"
#include "gtkcssboxesprivate.h"
#include "gtkeventcontrollermotion.h"
#include "gtkgesturepan.h"
#include "gtkgesturesingle.h"
#include "gtkpanedhandleprivate.h"
#include "gtkmarshalers.h"
#include "gtkorientable.h"
#include "gtkprivate.h"
#include "gtktypebuiltins.h"
#include "gtkwidgetprivate.h"
#include "gtkbuildable.h"
#include <math.h>
/**
* GtkPaned:
*
* A widget with two panes, arranged either horizontally or vertically.
*
* ![An example GtkPaned](panes.png)
*
* The division between the two panes is adjustable by the user
* by dragging a handle.
*
* Child widgets are added to the panes of the widget with
* [method@Gtk.Paned.set_start_child] and [method@Gtk.Paned.set_end_child].
* The division between the two children is set by default from the size
* requests of the children, but it can be adjusted by the user.
*
* A paned widget draws a separator between the two child widgets and a
* small handle that the user can drag to adjust the division. It does not
* draw any relief around the children or around the separator. (The space
* in which the separator is called the gutter.) Often, it is useful to put
* each child inside a [class@Gtk.Frame] so that the gutter appears as a
* ridge. No separator is drawn if one of the children is missing.
*
* Each child has two options that can be set, "resize" and "shrink". If
* "resize" is true then, when the `GtkPaned` is resized, that child will
* expand or shrink along with the paned widget. If "shrink" is true, then
* that child can be made smaller than its requisition by the user.
* Setting "shrink" to false allows the application to set a minimum size.
* If "resize" is false for both children, then this is treated as if
* "resize" is true for both children.
*
* The application can set the position of the slider as if it were set
* by the user, by calling [method@Gtk.Paned.set_position].
*
* # CSS nodes
*
* ```
* paned
* <child>
* separator[.wide]
* <child>
* ```
*
* `GtkPaned` has a main CSS node with name paned, and a subnode for
* the separator with name separator. The subnode gets a .wide style
* class when the paned is supposed to be wide.
*
* In horizontal orientation, the nodes are arranged based on the text
* direction, so in left-to-right mode, :first-child will select the
* leftmost child, while it will select the rightmost child in
* RTL layouts.
*
* ## Creating a paned widget with minimum sizes.
*
* ```c
* GtkWidget *hpaned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
* GtkWidget *frame1 = gtk_frame_new (NULL);
* GtkWidget *frame2 = gtk_frame_new (NULL);
*
* gtk_widget_set_size_request (hpaned, 200, -1);
*
* gtk_paned_set_start_child (GTK_PANED (hpaned), frame1);
* gtk_paned_set_resize_start_child (GTK_PANED (hpaned), TRUE);
* gtk_paned_set_shrink_start_child (GTK_PANED (hpaned), FALSE);
* gtk_widget_set_size_request (frame1, 50, -1);
*
* gtk_paned_set_end_child (GTK_PANED (hpaned), frame2);
* gtk_paned_set_resize_end_child (GTK_PANED (hpaned), FALSE);
* gtk_paned_set_shrink_end_child (GTK_PANED (hpaned), FALSE);
* gtk_widget_set_size_request (frame2, 50, -1);
* ```
*/
#define HANDLE_EXTRA_SIZE 6
typedef struct _GtkPanedClass GtkPanedClass;
struct _GtkPaned
{
GtkWidget parent_instance;
GtkPaned *first_paned;
GtkWidget *start_child;
GtkWidget *end_child;
GtkWidget *last_start_child_focus;
GtkWidget *last_end_child_focus;
GtkWidget *saved_focus;
GtkOrientation orientation;
GtkWidget *handle_widget;
GtkGesture *pan_gesture; /* Used for touch */
GtkGesture *drag_gesture; /* Used for mice */
int start_child_size;
int drag_pos;
int last_allocation;
int max_position;
int min_position;
int original_position;
guint in_recursion : 1;
guint resize_start_child : 1;
guint shrink_start_child : 1;
guint resize_end_child : 1;
guint shrink_end_child : 1;
guint position_set : 1;
guint panning : 1;
};
struct _GtkPanedClass
{
GtkWidgetClass parent_class;
gboolean (* cycle_child_focus) (GtkPaned *paned,
gboolean reverse);
gboolean (* toggle_handle_focus) (GtkPaned *paned);
gboolean (* move_handle) (GtkPaned *paned,
GtkScrollType scroll);
gboolean (* cycle_handle_focus) (GtkPaned *paned,
gboolean reverse);
gboolean (* accept_position) (GtkPaned *paned);
gboolean (* cancel_position) (GtkPaned *paned);
};
enum {
PROP_0,
PROP_POSITION,
PROP_POSITION_SET,
PROP_MIN_POSITION,
PROP_MAX_POSITION,
PROP_WIDE_HANDLE,
PROP_RESIZE_START_CHILD,
PROP_RESIZE_END_CHILD,
PROP_SHRINK_START_CHILD,
PROP_SHRINK_END_CHILD,
PROP_START_CHILD,
PROP_END_CHILD,
LAST_PROP,
/* GtkOrientable */
PROP_ORIENTATION,
};
enum {
CYCLE_CHILD_FOCUS,
TOGGLE_HANDLE_FOCUS,
MOVE_HANDLE,
CYCLE_HANDLE_FOCUS,
ACCEPT_POSITION,
CANCEL_POSITION,
LAST_SIGNAL
};
static void gtk_paned_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec);
static void gtk_paned_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec);
static void gtk_paned_dispose (GObject *object);
static void gtk_paned_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline);
static void gtk_paned_size_allocate (GtkWidget *widget,
int width,
int height,
int baseline);
static void gtk_paned_unrealize (GtkWidget *widget);
static void gtk_paned_css_changed (GtkWidget *widget,
GtkCssStyleChange *change);
static void gtk_paned_calc_position (GtkPaned *paned,
int allocation,
int start_child_req,
int end_child_req);
static void gtk_paned_set_focus_child (GtkWidget *widget,
GtkWidget *child);
static void gtk_paned_set_saved_focus (GtkPaned *paned,
GtkWidget *widget);
static void gtk_paned_set_first_paned (GtkPaned *paned,
GtkPaned *first_paned);
static void gtk_paned_set_last_start_child_focus (GtkPaned *paned,
GtkWidget *widget);
static void gtk_paned_set_last_end_child_focus (GtkPaned *paned,
GtkWidget *widget);
static gboolean gtk_paned_cycle_child_focus (GtkPaned *paned,
gboolean reverse);
static gboolean gtk_paned_cycle_handle_focus (GtkPaned *paned,
gboolean reverse);
static gboolean gtk_paned_move_handle (GtkPaned *paned,
GtkScrollType scroll);
static gboolean gtk_paned_accept_position (GtkPaned *paned);
static gboolean gtk_paned_cancel_position (GtkPaned *paned);
static gboolean gtk_paned_toggle_handle_focus (GtkPaned *paned);
static void update_drag (GtkPaned *paned,
int xpos,
int ypos);
static void gtk_paned_buildable_iface_init (GtkBuildableIface *iface);
static void gtk_paned_accessible_range_init (GtkAccessibleRangeInterface *iface);
G_DEFINE_TYPE_WITH_CODE (GtkPaned, gtk_paned, GTK_TYPE_WIDGET,
G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)
G_IMPLEMENT_INTERFACE (GTK_TYPE_ACCESSIBLE_RANGE,
gtk_paned_accessible_range_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
gtk_paned_buildable_iface_init))
static guint signals[LAST_SIGNAL] = { 0 };
static GParamSpec *paned_props[LAST_PROP] = { NULL, };
static void
add_tab_bindings (GtkWidgetClass *widget_class,
GdkModifierType modifiers)
{
gtk_widget_class_add_binding_signal (widget_class,
GDK_KEY_Tab, modifiers,
"toggle-handle-focus",
NULL);
gtk_widget_class_add_binding_signal (widget_class,
GDK_KEY_KP_Tab, modifiers,
"toggle-handle-focus",
NULL);
}
static void
add_move_binding (GtkWidgetClass *widget_class,
guint keyval,
GdkModifierType mask,
GtkScrollType scroll)
{
gtk_widget_class_add_binding_signal (widget_class,
keyval, mask,
"move-handle",
"(i)", scroll);
}
static void
get_handle_area (GtkPaned *paned,
graphene_rect_t *area)
{
int extra = 0;
if (!gtk_widget_compute_bounds (paned->handle_widget, GTK_WIDGET (paned), area))
return;
if (!gtk_paned_get_wide_handle (paned))
extra = HANDLE_EXTRA_SIZE;
graphene_rect_inset (area, - extra, - extra);
}
static void
gtk_paned_compute_expand (GtkWidget *widget,
gboolean *hexpand,
gboolean *vexpand)
{
GtkPaned *paned = GTK_PANED (widget);
gboolean h = FALSE;
gboolean v = FALSE;
if (paned->start_child)
{
h = h || gtk_widget_compute_expand (paned->start_child, GTK_ORIENTATION_HORIZONTAL);
v = v || gtk_widget_compute_expand (paned->start_child, GTK_ORIENTATION_VERTICAL);
}
if (paned->end_child)
{
h = h || gtk_widget_compute_expand (paned->end_child, GTK_ORIENTATION_HORIZONTAL);
v = v || gtk_widget_compute_expand (paned->end_child, GTK_ORIENTATION_VERTICAL);
}
*hexpand = h;
*vexpand = v;
}
static GtkSizeRequestMode
gtk_paned_get_request_mode (GtkWidget *widget)
{
GtkPaned *paned = GTK_PANED (widget);
int wfh = 0, hfw = 0;
if (paned->start_child)
{
switch (gtk_widget_get_request_mode (paned->start_child))
{
case GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH:
hfw++;
break;
case GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT:
wfh++;
break;
case GTK_SIZE_REQUEST_CONSTANT_SIZE:
default:
break;
}
}
if (paned->end_child)
{
switch (gtk_widget_get_request_mode (paned->end_child))
{
case GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH:
hfw++;
break;
case GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT:
wfh++;
break;
case GTK_SIZE_REQUEST_CONSTANT_SIZE:
default:
break;
}
}
if (hfw == 0 && wfh == 0)
return GTK_SIZE_REQUEST_CONSTANT_SIZE;
else
return wfh > hfw ?
GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT :
GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
}
static void
gtk_paned_set_orientation (GtkPaned *self,
GtkOrientation orientation)
{
if (self->orientation != orientation)
{
static const char *cursor_name[2] = {
"col-resize",
"row-resize",
};
self->orientation = orientation;
gtk_widget_update_orientation (GTK_WIDGET (self), self->orientation);
gtk_widget_set_cursor_from_name (self->handle_widget,
cursor_name[orientation]);
gtk_gesture_pan_set_orientation (GTK_GESTURE_PAN (self->pan_gesture),
orientation);
gtk_widget_queue_resize (GTK_WIDGET (self));
g_object_notify (G_OBJECT (self), "orientation");
}
}
static void
gtk_paned_class_init (GtkPanedClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
object_class->set_property = gtk_paned_set_property;
object_class->get_property = gtk_paned_get_property;
object_class->dispose = gtk_paned_dispose;
widget_class->measure = gtk_paned_measure;
widget_class->size_allocate = gtk_paned_size_allocate;
widget_class->unrealize = gtk_paned_unrealize;
widget_class->focus = gtk_widget_focus_child;
widget_class->set_focus_child = gtk_paned_set_focus_child;
widget_class->css_changed = gtk_paned_css_changed;
widget_class->get_request_mode = gtk_paned_get_request_mode;
widget_class->compute_expand = gtk_paned_compute_expand;
class->cycle_child_focus = gtk_paned_cycle_child_focus;
class->toggle_handle_focus = gtk_paned_toggle_handle_focus;
class->move_handle = gtk_paned_move_handle;
class->cycle_handle_focus = gtk_paned_cycle_handle_focus;
class->accept_position = gtk_paned_accept_position;
class->cancel_position = gtk_paned_cancel_position;
/**
* GtkPaned:position: (attributes org.gtk.Property.get=gtk_paned_get_position org.gtk.Property.set=gtk_paned_set_position)
*
* Position of the separator in pixels, from the left/top.
*/
paned_props[PROP_POSITION] =
g_param_spec_int ("position", NULL, NULL,
0, G_MAXINT, 0,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkPaned:position-set:
*
* Whether the [property@Gtk.Paned:position] property has been set.
*/
paned_props[PROP_POSITION_SET] =
g_param_spec_boolean ("position-set", NULL, NULL,
FALSE,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkPaned:min-position:
*
* The smallest possible value for the [property@Gtk.Paned:position]
* property.
*
* This property is derived from the size and shrinkability
* of the widget's children.
*/
paned_props[PROP_MIN_POSITION] =
g_param_spec_int ("min-position", NULL, NULL,
0, G_MAXINT, 0,
GTK_PARAM_READABLE|G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkPaned:max-position:
*
* The largest possible value for the [property@Gtk.Paned:position]
* property.
*
* This property is derived from the size and shrinkability
* of the widget's children.
*/
paned_props[PROP_MAX_POSITION] =
g_param_spec_int ("max-position", NULL, NULL,
0, G_MAXINT, G_MAXINT,
GTK_PARAM_READABLE|G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkPaned:wide-handle: (attributes org.gtk.Property.get=gtk_paned_get_wide_handle org.gtk.Property.set=gtk_paned_set_wide_handle)
*
* Whether the `GtkPaned` should provide a stronger visual separation.
*
* For example, this could be set when a paned contains two
* [class@Gtk.Notebook]s, whose tab rows would otherwise merge visually.
*/
paned_props[PROP_WIDE_HANDLE] =
g_param_spec_boolean ("wide-handle", NULL, NULL,
FALSE,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkPaned:resize-start-child: (attributes org.gtk.Property.get=gtk_paned_get_resize_start_child org.gtk.Property.set=gtk_paned_set_resize_start_child)
*
* Determines whether the first child expands and shrinks
* along with the paned widget.
*/
paned_props[PROP_RESIZE_START_CHILD] =
g_param_spec_boolean ("resize-start-child", NULL, NULL,
TRUE,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkPaned:resize-end-child: (attributes org.gtk.Property.get=gtk_paned_get_resize_end_child org.gtk.Property.set=gtk_paned_set_resize_end_child)
*
* Determines whether the second child expands and shrinks
* along with the paned widget.
*/
paned_props[PROP_RESIZE_END_CHILD] =
g_param_spec_boolean ("resize-end-child", NULL, NULL,
TRUE,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkPaned:shrink-start-child: (attributes org.gtk.Property.get=gtk_paned_get_shrink_start_child org.gtk.Property.set=gtk_paned_set_shrink_start_child)
*
* Determines whether the first child can be made smaller
* than its requisition.
*/
paned_props[PROP_SHRINK_START_CHILD] =
g_param_spec_boolean ("shrink-start-child", NULL, NULL,
TRUE,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkPaned:shrink-end-child: (attributes org.gtk.Property.get=gtk_paned_get_shrink_end_child org.gtk.Property.set=gtk_paned_set_shrink_end_child)
*
* Determines whether the second child can be made smaller
* than its requisition.
*/
paned_props[PROP_SHRINK_END_CHILD] =
g_param_spec_boolean ("shrink-end-child", NULL, NULL,
TRUE,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkPaned:start-child: (attributes org.gtk.Property.get=gtk_paned_get_start_child org.gtk.Property.set=gtk_paned_set_start_child)
*
* The first child.
*/
paned_props[PROP_START_CHILD] =
g_param_spec_object ("start-child", NULL, NULL,
GTK_TYPE_WIDGET,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkPaned:end-child: (attributes org.gtk.Property.get=gtk_paned_get_end_child org.gtk.Property.set=gtk_paned_set_end_child)
*
* The second child.
*/
paned_props[PROP_END_CHILD] =
g_param_spec_object ("end-child", NULL, NULL,
GTK_TYPE_WIDGET,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (object_class, LAST_PROP, paned_props);
g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation");
/**
* GtkPaned::cycle-child-focus:
* @widget: the object that received the signal
* @reversed: whether cycling backward or forward
*
* Emitted to cycle the focus between the children of the paned.
*
* This is a [keybinding signal](class.SignalAction.html).
*
* The default binding is <kbd>F6</kbd>.
*/
signals [CYCLE_CHILD_FOCUS] =
g_signal_new (I_("cycle-child-focus"),
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkPanedClass, cycle_child_focus),
NULL, NULL,
_gtk_marshal_BOOLEAN__BOOLEAN,
G_TYPE_BOOLEAN, 1,
G_TYPE_BOOLEAN);
g_signal_set_va_marshaller (signals[CYCLE_CHILD_FOCUS],
G_TYPE_FROM_CLASS (object_class),
_gtk_marshal_BOOLEAN__BOOLEANv);
/**
* GtkPaned::toggle-handle-focus:
* @widget: the object that received the signal
*
* Emitted to accept the current position of the handle and then
* move focus to the next widget in the focus chain.
*
* This is a [keybinding signal](class.SignalAction.html).
*
* The default binding is <kbd>Tab</kbd>.
*/
signals [TOGGLE_HANDLE_FOCUS] =
g_signal_new (I_("toggle-handle-focus"),
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkPanedClass, toggle_handle_focus),
NULL, NULL,
_gtk_marshal_BOOLEAN__VOID,
G_TYPE_BOOLEAN, 0);
g_signal_set_va_marshaller (signals[TOGGLE_HANDLE_FOCUS],
G_TYPE_FROM_CLASS (object_class),
_gtk_marshal_BOOLEAN__VOIDv);
/**
* GtkPaned::move-handle:
* @widget: the object that received the signal
* @scroll_type: a `GtkScrollType`
*
* Emitted to move the handle with key bindings.
*
* This is a [keybinding signal](class.SignalAction.html).
*/
signals[MOVE_HANDLE] =
g_signal_new (I_("move-handle"),
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkPanedClass, move_handle),
NULL, NULL,
_gtk_marshal_BOOLEAN__ENUM,
G_TYPE_BOOLEAN, 1,
GTK_TYPE_SCROLL_TYPE);
g_signal_set_va_marshaller (signals[MOVE_HANDLE],
G_TYPE_FROM_CLASS (object_class),
_gtk_marshal_BOOLEAN__ENUMv);
/**
* GtkPaned::cycle-handle-focus:
* @widget: the object that received the signal
* @reversed: whether cycling backward or forward
*
* Emitted to cycle whether the paned should grab focus to allow
* the user to change position of the handle by using key bindings.
*
* This is a [keybinding signal](class.SignalAction.html).
*
* The default binding for this signal is <kbd>F8</kbd>.
*/
signals [CYCLE_HANDLE_FOCUS] =
g_signal_new (I_("cycle-handle-focus"),
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkPanedClass, cycle_handle_focus),
NULL, NULL,
_gtk_marshal_BOOLEAN__BOOLEAN,
G_TYPE_BOOLEAN, 1,
G_TYPE_BOOLEAN);
g_signal_set_va_marshaller (signals[CYCLE_HANDLE_FOCUS],
G_TYPE_FROM_CLASS (object_class),
_gtk_marshal_BOOLEAN__BOOLEANv);
/**
* GtkPaned::accept-position:
* @widget: the object that received the signal
*
* Emitted to accept the current position of the handle when
* moving it using key bindings.
*
* This is a [keybinding signal](class.SignalAction.html).
*
* The default binding for this signal is <kbd>Return</kbd> or
* <kbd>Space</kbd>.
*/
signals [ACCEPT_POSITION] =
g_signal_new (I_("accept-position"),
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkPanedClass, accept_position),
NULL, NULL,
_gtk_marshal_BOOLEAN__VOID,
G_TYPE_BOOLEAN, 0);
g_signal_set_va_marshaller (signals[ACCEPT_POSITION],
G_TYPE_FROM_CLASS (object_class),
_gtk_marshal_BOOLEAN__VOIDv);
/**
* GtkPaned::cancel-position:
* @widget: the object that received the signal
*
* Emitted to cancel moving the position of the handle using key
* bindings.
*
* The position of the handle will be reset to the value prior to
* moving it.
*
* This is a [keybinding signal](class.SignalAction.html).
*
* The default binding for this signal is <kbd>Escape</kbd>.
*/
signals [CANCEL_POSITION] =
g_signal_new (I_("cancel-position"),
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkPanedClass, cancel_position),
NULL, NULL,
_gtk_marshal_BOOLEAN__VOID,
G_TYPE_BOOLEAN, 0);
g_signal_set_va_marshaller (signals[CANCEL_POSITION],
G_TYPE_FROM_CLASS (object_class),
_gtk_marshal_BOOLEAN__VOIDv);
/* F6 and friends */
gtk_widget_class_add_binding_signal (widget_class,
GDK_KEY_F6, 0,
"cycle-child-focus",
"(b)", FALSE);
gtk_widget_class_add_binding_signal (widget_class,
GDK_KEY_F6, GDK_SHIFT_MASK,
"cycle-child-focus",
"(b)", TRUE);
/* F8 and friends */
gtk_widget_class_add_binding_signal (widget_class,
GDK_KEY_F8, 0,
"cycle-handle-focus",
"(b)", FALSE);
gtk_widget_class_add_binding_signal (widget_class,
GDK_KEY_F8, GDK_SHIFT_MASK,
"cycle-handle-focus",
"(b)", TRUE);
add_tab_bindings (widget_class, 0);
add_tab_bindings (widget_class, GDK_CONTROL_MASK);
add_tab_bindings (widget_class, GDK_SHIFT_MASK);
add_tab_bindings (widget_class, GDK_CONTROL_MASK | GDK_SHIFT_MASK);
/* accept and cancel positions */
gtk_widget_class_add_binding_signal (widget_class,
GDK_KEY_Escape, 0,
"cancel-position",
NULL);
gtk_widget_class_add_binding_signal (widget_class,
GDK_KEY_Return, 0,
"accept-position",
NULL);
gtk_widget_class_add_binding_signal (widget_class,
GDK_KEY_ISO_Enter, 0,
"accept-position",
NULL);
gtk_widget_class_add_binding_signal (widget_class,
GDK_KEY_KP_Enter, 0,
"accept-position",
NULL);
gtk_widget_class_add_binding_signal (widget_class,
GDK_KEY_space, 0,
"accept-position",
NULL);
gtk_widget_class_add_binding_signal (widget_class,
GDK_KEY_KP_Space, 0,
"accept-position",
NULL);
/* move handle */
add_move_binding (widget_class, GDK_KEY_Left, 0, GTK_SCROLL_STEP_LEFT);
add_move_binding (widget_class, GDK_KEY_KP_Left, 0, GTK_SCROLL_STEP_LEFT);
add_move_binding (widget_class, GDK_KEY_Left, GDK_CONTROL_MASK, GTK_SCROLL_PAGE_LEFT);
add_move_binding (widget_class, GDK_KEY_KP_Left, GDK_CONTROL_MASK, GTK_SCROLL_PAGE_LEFT);
add_move_binding (widget_class, GDK_KEY_Right, 0, GTK_SCROLL_STEP_RIGHT);
add_move_binding (widget_class, GDK_KEY_Right, GDK_CONTROL_MASK, GTK_SCROLL_PAGE_RIGHT);
add_move_binding (widget_class, GDK_KEY_KP_Right, 0, GTK_SCROLL_STEP_RIGHT);
add_move_binding (widget_class, GDK_KEY_KP_Right, GDK_CONTROL_MASK, GTK_SCROLL_PAGE_RIGHT);
add_move_binding (widget_class, GDK_KEY_Up, 0, GTK_SCROLL_STEP_UP);
add_move_binding (widget_class, GDK_KEY_Up, GDK_CONTROL_MASK, GTK_SCROLL_PAGE_UP);
add_move_binding (widget_class, GDK_KEY_KP_Up, 0, GTK_SCROLL_STEP_UP);
add_move_binding (widget_class, GDK_KEY_KP_Up, GDK_CONTROL_MASK, GTK_SCROLL_PAGE_UP);
add_move_binding (widget_class, GDK_KEY_Page_Up, 0, GTK_SCROLL_PAGE_UP);
add_move_binding (widget_class, GDK_KEY_KP_Page_Up, 0, GTK_SCROLL_PAGE_UP);
add_move_binding (widget_class, GDK_KEY_Down, 0, GTK_SCROLL_STEP_DOWN);
add_move_binding (widget_class, GDK_KEY_Down, GDK_CONTROL_MASK, GTK_SCROLL_PAGE_DOWN);
add_move_binding (widget_class, GDK_KEY_KP_Down, 0, GTK_SCROLL_STEP_DOWN);
add_move_binding (widget_class, GDK_KEY_KP_Down, GDK_CONTROL_MASK, GTK_SCROLL_PAGE_DOWN);
add_move_binding (widget_class, GDK_KEY_Page_Down, 0, GTK_SCROLL_PAGE_RIGHT);
add_move_binding (widget_class, GDK_KEY_KP_Page_Down, 0, GTK_SCROLL_PAGE_RIGHT);
add_move_binding (widget_class, GDK_KEY_Home, 0, GTK_SCROLL_START);
add_move_binding (widget_class, GDK_KEY_KP_Home, 0, GTK_SCROLL_START);
add_move_binding (widget_class, GDK_KEY_End, 0, GTK_SCROLL_END);
add_move_binding (widget_class, GDK_KEY_KP_End, 0, GTK_SCROLL_END);
gtk_widget_class_set_css_name (widget_class, I_("paned"));
}
static GtkBuildableIface *parent_buildable_iface;
static void
gtk_paned_buildable_add_child (GtkBuildable *buildable,
GtkBuilder *builder,
GObject *child,
const char *type)
{
GtkPaned *self = GTK_PANED (buildable);
if (g_strcmp0 (type, "start") == 0)
{
gtk_paned_set_start_child (self, GTK_WIDGET (child));
gtk_paned_set_resize_start_child (self, FALSE);
gtk_paned_set_shrink_start_child (self, TRUE);
}
else if (g_strcmp0 (type, "end") == 0)
{
gtk_paned_set_end_child (self, GTK_WIDGET (child));
gtk_paned_set_resize_end_child (self, TRUE);
gtk_paned_set_shrink_end_child (self, TRUE);
}
else if (type == NULL && GTK_IS_WIDGET (child))
{
if (self->start_child == NULL)
{
gtk_paned_set_start_child (self, GTK_WIDGET (child));
gtk_paned_set_resize_start_child (self, FALSE);
gtk_paned_set_shrink_start_child (self, TRUE);
}
else if (self->end_child == NULL)
{
gtk_paned_set_end_child (self, GTK_WIDGET (child));
gtk_paned_set_resize_end_child (self, TRUE);
gtk_paned_set_shrink_end_child (self, TRUE);
}
else
g_warning ("GtkPaned only accepts two widgets as children");
}
else
parent_buildable_iface->add_child (buildable, builder, child, type);
}
static void
gtk_paned_buildable_iface_init (GtkBuildableIface *iface)
{
parent_buildable_iface = g_type_interface_peek_parent (iface);
iface->add_child = gtk_paned_buildable_add_child;
}
static gboolean
accessible_range_set_current_value (GtkAccessibleRange *accessible_range,
double value)
{
gtk_paned_set_position (GTK_PANED (accessible_range), (int) value + 0.5);
return TRUE;
}
static void
gtk_paned_accessible_range_init (GtkAccessibleRangeInterface *iface)
{
iface->set_current_value = accessible_range_set_current_value;
}
static gboolean
initiates_touch_drag (GtkPaned *paned,
double start_x,
double start_y)
{
int handle_size, handle_pos, drag_pos;
graphene_rect_t handle_area;
#define TOUCH_EXTRA_AREA_WIDTH 50
get_handle_area (paned, &handle_area);
if (paned->orientation == GTK_ORIENTATION_HORIZONTAL)
{
handle_pos = handle_area.origin.x;
drag_pos = start_x;
handle_size = handle_area.size.width;
}
else
{
handle_pos = handle_area.origin.y;
drag_pos = start_y;
handle_size = handle_area.size.height;
}
if (drag_pos < handle_pos - TOUCH_EXTRA_AREA_WIDTH ||
drag_pos > handle_pos + handle_size + TOUCH_EXTRA_AREA_WIDTH)
return FALSE;
#undef TOUCH_EXTRA_AREA_WIDTH
return TRUE;
}
static void
gesture_drag_begin_cb (GtkGestureDrag *gesture,
double start_x,
double start_y,
GtkPaned *paned)
{
GdkEventSequence *sequence;
graphene_rect_t handle_area;
GdkEvent *event;
GdkDevice *device;
gboolean is_touch;
sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
device = gdk_event_get_device (event);
paned->panning = FALSE;
is_touch = (gdk_event_get_event_type (event) == GDK_TOUCH_BEGIN ||
gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN);
get_handle_area (paned, &handle_area);
if ((is_touch && GTK_GESTURE (gesture) == paned->drag_gesture) ||
(!is_touch && GTK_GESTURE (gesture) == paned->pan_gesture))
{
gtk_gesture_set_state (GTK_GESTURE (gesture),
GTK_EVENT_SEQUENCE_DENIED);
return;
}
if (graphene_rect_contains_point (&handle_area, &(graphene_point_t){start_x, start_y}) ||
(is_touch && initiates_touch_drag (paned, start_x, start_y)))
{
if (paned->orientation == GTK_ORIENTATION_HORIZONTAL)
paned->drag_pos = start_x - handle_area.origin.x;
else
paned->drag_pos = start_y - handle_area.origin.y;
paned->panning = TRUE;
gtk_gesture_set_state (GTK_GESTURE (gesture),
GTK_EVENT_SEQUENCE_CLAIMED);
}
else
{
gtk_gesture_set_state (GTK_GESTURE (gesture),
GTK_EVENT_SEQUENCE_DENIED);
}
}
static void
gesture_drag_update_cb (GtkGestureDrag *gesture,
double offset_x,
double offset_y,
GtkPaned *paned)
{
double start_x, start_y;
gtk_gesture_drag_get_start_point (GTK_GESTURE_DRAG (gesture),
&start_x, &start_y);
update_drag (paned, start_x + offset_x, start_y + offset_y);
}
static void
gesture_drag_end_cb (GtkGestureDrag *gesture,
double offset_x,
double offset_y,
GtkPaned *paned)
{
if (!paned->panning)
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
paned->panning = FALSE;
}
static void
gtk_paned_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkPaned *paned = GTK_PANED (object);
switch (prop_id)
{
case PROP_ORIENTATION:
gtk_paned_set_orientation (paned, g_value_get_enum (value));
break;
case PROP_POSITION:
gtk_paned_set_position (paned, g_value_get_int (value));
break;
case PROP_POSITION_SET:
if (paned->position_set != g_value_get_boolean (value))
{
paned->position_set = g_value_get_boolean (value);
gtk_widget_queue_resize (GTK_WIDGET (paned));
g_object_notify_by_pspec (object, pspec);
}
break;
case PROP_WIDE_HANDLE:
gtk_paned_set_wide_handle (paned, g_value_get_boolean (value));
break;
case PROP_RESIZE_START_CHILD:
gtk_paned_set_resize_start_child (paned, g_value_get_boolean (value));
break;
case PROP_RESIZE_END_CHILD:
gtk_paned_set_resize_end_child (paned, g_value_get_boolean (value));
break;
case PROP_SHRINK_START_CHILD:
gtk_paned_set_shrink_start_child (paned, g_value_get_boolean (value));
break;
case PROP_SHRINK_END_CHILD:
gtk_paned_set_shrink_end_child (paned, g_value_get_boolean (value));
break;
case PROP_START_CHILD:
gtk_paned_set_start_child (paned, g_value_get_object (value));
break;
case PROP_END_CHILD:
gtk_paned_set_end_child (paned, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_paned_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkPaned *paned = GTK_PANED (object);
switch (prop_id)
{
case PROP_ORIENTATION:
g_value_set_enum (value, paned->orientation);
break;
case PROP_POSITION:
g_value_set_int (value, paned->start_child_size);
break;
case PROP_POSITION_SET:
g_value_set_boolean (value, paned->position_set);
break;
case PROP_MIN_POSITION:
g_value_set_int (value, paned->min_position);
break;
case PROP_MAX_POSITION:
g_value_set_int (value, paned->max_position);
break;
case PROP_WIDE_HANDLE:
g_value_set_boolean (value, gtk_paned_get_wide_handle (paned));
break;
case PROP_RESIZE_START_CHILD:
g_value_set_boolean (value, paned->resize_start_child);
break;
case PROP_RESIZE_END_CHILD:
g_value_set_boolean (value, paned->resize_end_child);
break;
case PROP_SHRINK_START_CHILD:
g_value_set_boolean (value, paned->shrink_start_child);
break;
case PROP_SHRINK_END_CHILD:
g_value_set_boolean (value, paned->shrink_end_child);
break;
case PROP_START_CHILD:
g_value_set_object (value, gtk_paned_get_start_child (paned));
break;
case PROP_END_CHILD:
g_value_set_object (value, gtk_paned_get_end_child (paned));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_paned_dispose (GObject *object)
{
GtkPaned *paned = GTK_PANED (object);
gtk_paned_set_saved_focus (paned, NULL);
gtk_paned_set_first_paned (paned, NULL);
g_clear_pointer (&paned->start_child, gtk_widget_unparent);
g_clear_pointer (&paned->end_child, gtk_widget_unparent);
g_clear_pointer (&paned->handle_widget, gtk_widget_unparent);
G_OBJECT_CLASS (gtk_paned_parent_class)->dispose (object);
}
static void
gtk_paned_compute_position (GtkPaned *paned,
int allocation,
int start_child_req,
int end_child_req,
int *min_pos,
int *max_pos,
int *out_pos)
{
int min, max, pos;
min = paned->shrink_start_child ? 0 : start_child_req;
max = allocation;
if (!paned->shrink_end_child)
max = MAX (1, max - end_child_req);
max = MAX (min, max);
if (!paned->position_set)
{
if (paned->resize_start_child && !paned->resize_end_child)
pos = MAX (0, allocation - end_child_req);
else if (!paned->resize_start_child && paned->resize_end_child)
pos = start_child_req;
else if (start_child_req + end_child_req != 0)
pos = allocation * ((double)start_child_req / (start_child_req + end_child_req)) + 0.5;
else
pos = allocation * 0.5 + 0.5;
}
else
{
/* If the position was set before the initial allocation.
* (paned->last_allocation <= 0) just clamp it and leave it.
*/
if (paned->last_allocation > 0)
{
if (paned->resize_start_child && !paned->resize_end_child)
pos = paned->start_child_size + allocation - paned->last_allocation;
else if (!(!paned->resize_start_child && paned->resize_end_child))
pos = allocation * ((double) paned->start_child_size / (paned->last_allocation)) + 0.5;
else
pos = paned->start_child_size;
}
else
pos = paned->start_child_size;
}
pos = CLAMP (pos, min, max);
if (min_pos)
*min_pos = min;
if (max_pos)
*max_pos = max;
if (out_pos)
*out_pos = pos;
}
static void
gtk_paned_get_preferred_size_for_orientation (GtkWidget *widget,
int size,
int *minimum,
int *natural)
{
GtkPaned *paned = GTK_PANED (widget);
int child_min, child_nat;
*minimum = *natural = 0;
if (paned->start_child && gtk_widget_get_visible (paned->start_child))
{
gtk_widget_measure (paned->start_child, paned->orientation, size, &child_min, &child_nat, NULL, NULL);
if (paned->shrink_start_child)
*minimum = 0;
else
*minimum = child_min;
*natural = child_nat;
}
if (paned->end_child && gtk_widget_get_visible (paned->end_child))
{
gtk_widget_measure (paned->end_child, paned->orientation, size, &child_min, &child_nat, NULL, NULL);
if (!paned->shrink_end_child)
*minimum += child_min;
*natural += child_nat;
}
if (paned->start_child && gtk_widget_get_visible (paned->start_child) &&
paned->end_child && gtk_widget_get_visible (paned->end_child))
{
int handle_size;
gtk_widget_measure (paned->handle_widget,
paned->orientation,
-1,
NULL, &handle_size,
NULL, NULL);
*minimum += handle_size;
*natural += handle_size;
}
}
static void
gtk_paned_get_preferred_size_for_opposite_orientation (GtkWidget *widget,
int size,
int *minimum,
int *natural)
{
GtkPaned *paned = GTK_PANED (widget);
int for_start_child, for_end_child, for_handle;
int child_min, child_nat;
if (size > -1 &&
paned->start_child && gtk_widget_get_visible (paned->start_child) &&
paned->end_child && gtk_widget_get_visible (paned->end_child))
{
int start_child_req, end_child_req;
gtk_widget_measure (paned->handle_widget,
paned->orientation,
-1,
NULL, &for_handle,
NULL, NULL);
gtk_widget_measure (paned->start_child, paned->orientation, -1, &start_child_req, NULL, NULL, NULL);
gtk_widget_measure (paned->end_child, paned->orientation, -1, &end_child_req, NULL, NULL, NULL);
gtk_paned_compute_position (paned,
size - for_handle, start_child_req, end_child_req,
NULL, NULL, &for_start_child);
for_end_child = size - for_start_child - for_handle;
if (paned->shrink_start_child)
for_start_child = MAX (start_child_req, for_start_child);
if (paned->shrink_end_child)
for_end_child = MAX (end_child_req, for_end_child);
}
else
{
for_start_child = size;
for_end_child = size;
for_handle = -1;
}
*minimum = *natural = 0;
if (paned->start_child && gtk_widget_get_visible (paned->start_child))
{
gtk_widget_measure (paned->start_child,
OPPOSITE_ORIENTATION (paned->orientation),
for_start_child,
&child_min, &child_nat,
NULL, NULL);
*minimum = child_min;
*natural = child_nat;
}
if (paned->end_child && gtk_widget_get_visible (paned->end_child))
{
gtk_widget_measure (paned->end_child,
OPPOSITE_ORIENTATION (paned->orientation),
for_end_child,
&child_min, &child_nat,
NULL, NULL);
*minimum = MAX (*minimum, child_min);
*natural = MAX (*natural, child_nat);
}
if (paned->start_child && gtk_widget_get_visible (paned->start_child) &&
paned->end_child && gtk_widget_get_visible (paned->end_child))
{
gtk_widget_measure (paned->handle_widget,
OPPOSITE_ORIENTATION (paned->orientation),
for_handle,
&child_min, &child_nat,
NULL, NULL);
*minimum = MAX (*minimum, child_min);
*natural = MAX (*natural, child_nat);
}
}
static void
gtk_paned_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkPaned *paned = GTK_PANED (widget);
if (orientation == paned->orientation)
gtk_paned_get_preferred_size_for_orientation (widget, for_size, minimum, natural);
else
gtk_paned_get_preferred_size_for_opposite_orientation (widget, for_size, minimum, natural);
}
static void
flip_child (int width,
GtkAllocation *child_pos)
{
child_pos->x = width - child_pos->x - child_pos->width;
}
static void
gtk_paned_size_allocate (GtkWidget *widget,
int width,
int height,
int baseline)
{
GtkPaned *paned = GTK_PANED (widget);
if (paned->start_child && gtk_widget_get_visible (paned->start_child) &&
paned->end_child && gtk_widget_get_visible (paned->end_child))
{
GtkAllocation start_child_allocation;
GtkAllocation end_child_allocation;
GtkAllocation handle_allocation;
int handle_size;
gtk_widget_measure (paned->handle_widget,
paned->orientation,
-1,
NULL, &handle_size,
NULL, NULL);
if (paned->orientation == GTK_ORIENTATION_HORIZONTAL)
{
int start_child_width, end_child_width;
gtk_widget_measure (paned->start_child, GTK_ORIENTATION_HORIZONTAL,
height,
&start_child_width, NULL, NULL, NULL);
gtk_widget_measure (paned->end_child, GTK_ORIENTATION_HORIZONTAL,
height,
&end_child_width, NULL, NULL, NULL);
gtk_paned_calc_position (paned,
MAX (1, width - handle_size),
start_child_width,
end_child_width);
handle_allocation = (GdkRectangle){
paned->start_child_size,
0,
handle_size,
height
};
start_child_allocation.height = end_child_allocation.height = height;
start_child_allocation.width = MAX (1, paned->start_child_size);
start_child_allocation.x = 0;
start_child_allocation.y = end_child_allocation.y = 0;
end_child_allocation.x = start_child_allocation.x + paned->start_child_size + handle_size;
end_child_allocation.width = MAX (1, width - paned->start_child_size - handle_size);
if (gtk_widget_get_direction (GTK_WIDGET (widget)) == GTK_TEXT_DIR_RTL)
{
flip_child (width, &(end_child_allocation));
flip_child (width, &(start_child_allocation));
flip_child (width, &(handle_allocation));
}
if (start_child_width > start_child_allocation.width)
{
if (gtk_widget_get_direction (GTK_WIDGET (widget)) == GTK_TEXT_DIR_LTR)
start_child_allocation.x -= start_child_width - start_child_allocation.width;
start_child_allocation.width = start_child_width;
}
if (end_child_width > end_child_allocation.width)
{
if (gtk_widget_get_direction (GTK_WIDGET (widget)) == GTK_TEXT_DIR_RTL)
end_child_allocation.x -= end_child_width - end_child_allocation.width;
end_child_allocation.width = end_child_width;
}
}
else
{
int start_child_height, end_child_height;
gtk_widget_measure (paned->start_child, GTK_ORIENTATION_VERTICAL,
width,
&start_child_height, NULL, NULL, NULL);
gtk_widget_measure (paned->end_child, GTK_ORIENTATION_VERTICAL,
width,
&end_child_height, NULL, NULL, NULL);
gtk_paned_calc_position (paned,
MAX (1, height - handle_size),
start_child_height,
end_child_height);
handle_allocation = (GdkRectangle){
0,
paned->start_child_size,
width,
handle_size,
};
start_child_allocation.width = end_child_allocation.width = width;
start_child_allocation.height = MAX (1, paned->start_child_size);
start_child_allocation.x = end_child_allocation.x = 0;
start_child_allocation.y = 0;
end_child_allocation.y = start_child_allocation.y + paned->start_child_size + handle_size;
end_child_allocation.height = MAX (1, height - end_child_allocation.y);
if (start_child_height > start_child_allocation.height)
{
start_child_allocation.y -= start_child_height - start_child_allocation.height;
start_child_allocation.height = start_child_height;
}
if (end_child_height > end_child_allocation.height)
end_child_allocation.height = end_child_height;
}
gtk_widget_set_child_visible (paned->handle_widget, TRUE);
gtk_widget_size_allocate (paned->handle_widget, &handle_allocation, -1);
gtk_widget_size_allocate (paned->start_child, &start_child_allocation, -1);
gtk_widget_size_allocate (paned->end_child, &end_child_allocation, -1);
}
else
{
if (paned->start_child && gtk_widget_get_visible (paned->start_child))
{
gtk_widget_set_child_visible (paned->start_child, TRUE);
gtk_widget_size_allocate (paned->start_child,
&(GtkAllocation) {0, 0, width, height}, -1);
}
else if (paned->end_child && gtk_widget_get_visible (paned->end_child))
{
gtk_widget_set_child_visible (paned->end_child, TRUE);
gtk_widget_size_allocate (paned->end_child,
&(GtkAllocation) {0, 0, width, height}, -1);
}
gtk_widget_set_child_visible (paned->handle_widget, FALSE);
}
gtk_accessible_update_property (GTK_ACCESSIBLE (paned),
GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, 0.0,
GTK_ACCESSIBLE_PROPERTY_VALUE_MAX,
(double) (paned->orientation == GTK_ORIENTATION_HORIZONTAL ? width : height),
GTK_ACCESSIBLE_PROPERTY_VALUE_NOW,
(double) paned->start_child_size,
-1);
}
static void
gtk_paned_unrealize (GtkWidget *widget)
{
GtkPaned *paned = GTK_PANED (widget);
gtk_paned_set_last_start_child_focus (paned, NULL);
gtk_paned_set_last_end_child_focus (paned, NULL);
gtk_paned_set_saved_focus (paned, NULL);
gtk_paned_set_first_paned (paned, NULL);
GTK_WIDGET_CLASS (gtk_paned_parent_class)->unrealize (widget);
}
static void
connect_drag_gesture_signals (GtkPaned *paned,
GtkGesture *gesture)
{
g_signal_connect (gesture, "drag-begin",
G_CALLBACK (gesture_drag_begin_cb), paned);
g_signal_connect (gesture, "drag-update",
G_CALLBACK (gesture_drag_update_cb), paned);
g_signal_connect (gesture, "drag-end",
G_CALLBACK (gesture_drag_end_cb), paned);
}
static void
gtk_paned_init (GtkPaned *paned)
{
GtkGesture *gesture;
gtk_widget_set_focusable (GTK_WIDGET (paned), TRUE);
gtk_widget_set_overflow (GTK_WIDGET (paned), GTK_OVERFLOW_HIDDEN);
paned->orientation = GTK_ORIENTATION_HORIZONTAL;
paned->start_child = NULL;
paned->end_child = NULL;
paned->position_set = FALSE;
paned->last_allocation = -1;
paned->last_start_child_focus = NULL;
paned->last_end_child_focus = NULL;
paned->in_recursion = FALSE;
paned->original_position = -1;
paned->max_position = G_MAXINT;
paned->resize_start_child = TRUE;
paned->resize_end_child = TRUE;
paned->shrink_start_child = TRUE;
paned->shrink_end_child = TRUE;
gtk_widget_update_orientation (GTK_WIDGET (paned), paned->orientation);
/* Touch gesture */
gesture = gtk_gesture_pan_new (GTK_ORIENTATION_HORIZONTAL);
connect_drag_gesture_signals (paned, gesture);
gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), TRUE);
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
GTK_PHASE_CAPTURE);
gtk_widget_add_controller (GTK_WIDGET (paned), GTK_EVENT_CONTROLLER (gesture));
paned->pan_gesture = gesture;
/* Pointer gesture */
gesture = gtk_gesture_drag_new ();
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
GTK_PHASE_CAPTURE);
connect_drag_gesture_signals (paned, gesture);
gtk_widget_add_controller (GTK_WIDGET (paned), GTK_EVENT_CONTROLLER (gesture));
paned->drag_gesture = gesture;
paned->handle_widget = gtk_paned_handle_new ();
gtk_widget_set_parent (paned->handle_widget, GTK_WIDGET (paned));
gtk_widget_set_cursor_from_name (paned->handle_widget, "col-resize");
}
static gboolean
is_rtl (GtkPaned *paned)
{
return paned->orientation == GTK_ORIENTATION_HORIZONTAL &&
gtk_widget_get_direction (GTK_WIDGET (paned)) == GTK_TEXT_DIR_RTL;
}
static void
update_drag (GtkPaned *paned,
int xpos,
int ypos)
{
int pos;
int handle_size;
int size;
if (paned->orientation == GTK_ORIENTATION_HORIZONTAL)
pos = xpos;
else
pos = ypos;
pos -= paned->drag_pos;
if (is_rtl (paned))
{
gtk_widget_measure (paned->handle_widget,
GTK_ORIENTATION_HORIZONTAL,
-1,
NULL, &handle_size,
NULL, NULL);
size = gtk_widget_get_width (GTK_WIDGET (paned)) - pos - handle_size;
}
else
{
size = pos;
}
size = CLAMP (size, paned->min_position, paned->max_position);
if (size != paned->start_child_size)
gtk_paned_set_position (paned, size);
}
static void
gtk_paned_css_changed (GtkWidget *widget,
GtkCssStyleChange *change)
{
GTK_WIDGET_CLASS (gtk_paned_parent_class)->css_changed (widget, change);
if (change == NULL ||
gtk_css_style_change_affects (change, GTK_CSS_AFFECTS_ICON_SIZE))
{
gtk_widget_queue_resize (widget);
}
else if (gtk_css_style_change_affects (change, GTK_CSS_AFFECTS_ICON_TEXTURE |
GTK_CSS_AFFECTS_ICON_REDRAW))
{
gtk_widget_queue_draw (widget);
}
}
/**
* gtk_paned_new:
* @orientation: the paneds orientation.
*
* Creates a new `GtkPaned` widget.
*
* Returns: the newly created paned widget
*/
GtkWidget *
gtk_paned_new (GtkOrientation orientation)
{
return g_object_new (GTK_TYPE_PANED,
"orientation", orientation,
NULL);
}
/**
* gtk_paned_set_start_child: (attributes org.gtk.Method.set_property=start-child)
* @paned: a `GtkPaned`
* @child: (nullable): the widget to add
*
* Sets the start child of @paned to @child.
*
* If @child is `NULL`, the existing child will be removed.
*/
void
gtk_paned_set_start_child (GtkPaned *paned,
GtkWidget *child)
{
g_return_if_fail (GTK_IS_PANED (paned));
g_return_if_fail (child == NULL || paned->start_child == child || gtk_widget_get_parent (child) == NULL);
if (paned->start_child == child)
return;
g_clear_pointer (&paned->start_child, gtk_widget_unparent);
if (child)
{
paned->start_child = child;
gtk_widget_insert_before (child, GTK_WIDGET (paned), paned->handle_widget);
}
g_object_notify (G_OBJECT (paned), "start-child");
}
/**
* gtk_paned_get_start_child: (attributes org.gtk.Method.get_property=start-child)
* @paned: a `GtkPaned`
*
* Retrieves the start child of the given `GtkPaned`.
*
* Returns: (transfer none) (nullable): the start child widget
*/
GtkWidget *
gtk_paned_get_start_child (GtkPaned *paned)
{
g_return_val_if_fail (GTK_IS_PANED (paned), NULL);
return paned->start_child;
}
/**
* gtk_paned_set_resize_start_child: (attributes org.gtk.Method.set_property=resize-start-child)
* @paned: a `GtkPaned`
* @resize: true to let the start child be resized
*
* Sets whether the [property@Gtk.Paned:start-child] can be resized.
*/
void
gtk_paned_set_resize_start_child (GtkPaned *paned,
gboolean resize)
{
g_return_if_fail (GTK_IS_PANED (paned));
if (paned->resize_start_child == resize)
return;
paned->resize_start_child = resize;
g_object_notify (G_OBJECT (paned), "resize-start-child");
}
/**
* gtk_paned_get_resize_start_child: (attributes org.gtk.Method.get_property=resize-start-child)
* @paned: a `GtkPaned`
*
* Returns whether the [property@Gtk.Paned:start-child] can be resized.
*
* Returns: true if the start child is resizable
*/
gboolean
gtk_paned_get_resize_start_child (GtkPaned *paned)
{
g_return_val_if_fail (GTK_IS_PANED (paned), FALSE);
return paned->resize_start_child;
}
/**
* gtk_paned_set_shrink_start_child: (attributes org.gtk.Method.set_property=shrink-start-child)
* @paned: a `GtkPaned`
* @resize: true to let the start child be shrunk
*
* Sets whether the [property@Gtk.Paned:start-child] can shrink.
*/
void
gtk_paned_set_shrink_start_child (GtkPaned *paned,
gboolean shrink)
{
g_return_if_fail (GTK_IS_PANED (paned));
if (paned->shrink_start_child == shrink)
return;
paned->shrink_start_child = shrink;
g_object_notify (G_OBJECT (paned), "shrink-start-child");
}
/**
* gtk_paned_get_shrink_start_child: (attributes org.gtk.Method.get_property=shrink-start-child)
* @paned: a `GtkPaned`
*
* Returns whether the [property@Gtk.Paned:start-child] can shrink.
*
* Returns: true if the start child is shrinkable
*/
gboolean
gtk_paned_get_shrink_start_child (GtkPaned *paned)
{
g_return_val_if_fail (GTK_IS_PANED (paned), FALSE);
return paned->shrink_start_child;
}
/**
* gtk_paned_set_end_child: (attributes org.gtk.Method.set_property=end-child)
* @paned: a `GtkPaned`
* @child: (nullable): the widget to add
*
* Sets the end child of @paned to @child.
*
* If @child is `NULL`, the existing child will be removed.
*/
void
gtk_paned_set_end_child (GtkPaned *paned,
GtkWidget *child)
{
g_return_if_fail (GTK_IS_PANED (paned));
g_return_if_fail (child == NULL || paned->end_child == child || gtk_widget_get_parent (child) == NULL);
if (paned->end_child == child)
return;
g_clear_pointer (&paned->end_child, gtk_widget_unparent);
if (child)
{
paned->end_child = child;
gtk_widget_insert_after (child, GTK_WIDGET (paned), paned->handle_widget);
}
g_object_notify (G_OBJECT (paned), "end-child");
}
/**
* gtk_paned_get_end_child: (attributes org.gtk.Method.get_property=end-child)
* @paned: a `GtkPaned`
*
* Retrieves the end child of the given `GtkPaned`.
*
* Returns: (transfer none) (nullable): the end child widget
*/
GtkWidget *
gtk_paned_get_end_child (GtkPaned *paned)
{
g_return_val_if_fail (GTK_IS_PANED (paned), NULL);
return paned->end_child;
}
/**
* gtk_paned_set_resize_end_child: (attributes org.gtk.Method.set_property=resize-end-child)
* @paned: a `GtkPaned`
* @resize: true to let the end child be resized
*
* Sets whether the [property@Gtk.Paned:end-child] can be resized.
*/
void
gtk_paned_set_resize_end_child (GtkPaned *paned,
gboolean resize)
{
g_return_if_fail (GTK_IS_PANED (paned));
if (paned->resize_end_child == resize)
return;
paned->resize_end_child = resize;
g_object_notify (G_OBJECT (paned), "resize-end-child");
}
/**
* gtk_paned_get_resize_end_child: (attributes org.gtk.Method.get_property=resize-end-child)
* @paned: a `GtkPaned`
*
* Returns whether the [property@Gtk.Paned:end-child] can be resized.
*
* Returns: true if the end child is resizable
*/
gboolean
gtk_paned_get_resize_end_child (GtkPaned *paned)
{
g_return_val_if_fail (GTK_IS_PANED (paned), FALSE);
return paned->resize_end_child;
}
/**
* gtk_paned_set_shrink_end_child: (attributes org.gtk.Method.set_property=shrink-end-child)
* @paned: a `GtkPaned`
* @resize: true to let the end child be shrunk
*
* Sets whether the [property@Gtk.Paned:end-child] can shrink.
*/
void
gtk_paned_set_shrink_end_child (GtkPaned *paned,
gboolean shrink)
{
g_return_if_fail (GTK_IS_PANED (paned));
if (paned->shrink_end_child == shrink)
return;
paned->shrink_end_child = shrink;
g_object_notify (G_OBJECT (paned), "shrink-end-child");
}
/**
* gtk_paned_get_shrink_end_child: (attributes org.gtk.Method.get_property=shrink-end-child)
* @paned: a `GtkPaned`
*
* Returns whether the [property@Gtk.Paned:end-child] can shrink.
*
* Returns: true if the end child is shrinkable
*/
gboolean
gtk_paned_get_shrink_end_child (GtkPaned *paned)
{
g_return_val_if_fail (GTK_IS_PANED (paned), FALSE);
return paned->shrink_end_child;
}
/**
* gtk_paned_get_position: (attributes org.gtk.Method.get_property=position)
* @paned: a `GtkPaned` widget
*
* Obtains the position of the divider between the two panes.
*
* Returns: the position of the divider, in pixels
**/
int
gtk_paned_get_position (GtkPaned *paned)
{
g_return_val_if_fail (GTK_IS_PANED (paned), 0);
return paned->start_child_size;
}
/**
* gtk_paned_set_position: (attributes org.gtk.Method.set_property=position)
* @paned: a `GtkPaned` widget
* @position: pixel position of divider, a negative value means that the position
* is unset
*
* Sets the position of the divider between the two panes.
*/
void
gtk_paned_set_position (GtkPaned *paned,
int position)
{
g_return_if_fail (GTK_IS_PANED (paned));
g_object_freeze_notify (G_OBJECT (paned));
if (position >= 0)
{
/* We don't clamp here - the assumption is that
* if the total allocation changes at the same time
* as the position, the position set is with reference
* to the new total size. If only the position changes,
* then clamping will occur in gtk_paned_calc_position()
*/
if (!paned->position_set)
g_object_notify_by_pspec (G_OBJECT (paned), paned_props[PROP_POSITION_SET]);
if (paned->start_child_size != position)
{
g_object_notify_by_pspec (G_OBJECT (paned), paned_props[PROP_POSITION]);
gtk_widget_queue_allocate (GTK_WIDGET (paned));
}
paned->start_child_size = position;
paned->position_set = TRUE;
}
else
{
if (paned->position_set)
g_object_notify_by_pspec (G_OBJECT (paned), paned_props[PROP_POSITION_SET]);
paned->position_set = FALSE;
}
g_object_thaw_notify (G_OBJECT (paned));
#ifdef G_OS_WIN32
/* Hacky work-around for bug #144269 */
if (paned->end_child != NULL)
{
gtk_widget_queue_draw (paned->end_child);
}
#endif
}
static void
gtk_paned_calc_position (GtkPaned *paned,
int allocation,
int start_child_req,
int end_child_req)
{
int old_position;
int old_min_position;
int old_max_position;
old_position = paned->start_child_size;
old_min_position = paned->min_position;
old_max_position = paned->max_position;
gtk_paned_compute_position (paned,
allocation, start_child_req, end_child_req,
&paned->min_position, &paned->max_position,
&paned->start_child_size);
gtk_widget_set_child_visible (paned->start_child, paned->start_child_size != 0);
gtk_widget_set_child_visible (paned->end_child, paned->start_child_size != allocation);
g_object_freeze_notify (G_OBJECT (paned));
if (paned->start_child_size != old_position)
g_object_notify_by_pspec (G_OBJECT (paned), paned_props[PROP_POSITION]);
if (paned->min_position != old_min_position)
g_object_notify_by_pspec (G_OBJECT (paned), paned_props[PROP_MIN_POSITION]);
if (paned->max_position != old_max_position)
g_object_notify_by_pspec (G_OBJECT (paned), paned_props[PROP_MAX_POSITION]);
g_object_thaw_notify (G_OBJECT (paned));
paned->last_allocation = allocation;
}
static void
gtk_paned_set_saved_focus (GtkPaned *paned, GtkWidget *widget)
{
if (paned->saved_focus)
g_object_remove_weak_pointer (G_OBJECT (paned->saved_focus),
(gpointer *)&(paned->saved_focus));
paned->saved_focus = widget;
if (paned->saved_focus)
g_object_add_weak_pointer (G_OBJECT (paned->saved_focus),
(gpointer *)&(paned->saved_focus));
}
static void
gtk_paned_set_first_paned (GtkPaned *paned, GtkPaned *first_paned)
{
if (paned->first_paned)
g_object_remove_weak_pointer (G_OBJECT (paned->first_paned),
(gpointer *)&(paned->first_paned));
paned->first_paned = first_paned;
if (paned->first_paned)
g_object_add_weak_pointer (G_OBJECT (paned->first_paned),
(gpointer *)&(paned->first_paned));
}
static void
gtk_paned_set_last_start_child_focus (GtkPaned *paned, GtkWidget *widget)
{
if (paned->last_start_child_focus)
g_object_remove_weak_pointer (G_OBJECT (paned->last_start_child_focus),
(gpointer *)&(paned->last_start_child_focus));
paned->last_start_child_focus = widget;
if (paned->last_start_child_focus)
g_object_add_weak_pointer (G_OBJECT (paned->last_start_child_focus),
(gpointer *)&(paned->last_start_child_focus));
}
static void
gtk_paned_set_last_end_child_focus (GtkPaned *paned, GtkWidget *widget)
{
if (paned->last_end_child_focus)
g_object_remove_weak_pointer (G_OBJECT (paned->last_end_child_focus),
(gpointer *)&(paned->last_end_child_focus));
paned->last_end_child_focus = widget;
if (paned->last_end_child_focus)
g_object_add_weak_pointer (G_OBJECT (paned->last_end_child_focus),
(gpointer *)&(paned->last_end_child_focus));
}
static GtkWidget *
paned_get_focus_widget (GtkPaned *paned)
{
GtkWidget *toplevel;
toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (paned)));
if (GTK_IS_WINDOW (toplevel))
return gtk_window_get_focus (GTK_WINDOW (toplevel));
return NULL;
}
static void
gtk_paned_set_focus_child (GtkWidget *widget,
GtkWidget *child)
{
GtkPaned *paned = GTK_PANED (widget);
GtkWidget *focus_child;
if (child == NULL)
{
GtkWidget *last_focus;
GtkWidget *w;
last_focus = paned_get_focus_widget (paned);
if (last_focus)
{
/* If there is one or more paned widgets between us and the
* focus widget, we want the topmost of those as last_focus
*/
for (w = last_focus; w && w != GTK_WIDGET (paned); w = gtk_widget_get_parent (w))
if (GTK_IS_PANED (w))
last_focus = w;
if (w == NULL)
{
g_warning ("Error finding last focus widget of GtkPaned %p, "
"gtk_paned_set_focus_child was called on widget %p "
"which is not child of %p.",
widget, child, widget);
return;
}
focus_child = gtk_widget_get_focus_child (widget);
if (focus_child == paned->start_child)
gtk_paned_set_last_start_child_focus (paned, last_focus);
else if (focus_child == paned->end_child)
gtk_paned_set_last_end_child_focus (paned, last_focus);
}
}
GTK_WIDGET_CLASS (gtk_paned_parent_class)->set_focus_child (widget, child);
}
static void
gtk_paned_get_cycle_chain (GtkPaned *paned,
GtkDirectionType direction,
GList **widgets)
{
GtkWidget *ancestor = NULL;
GtkWidget *focus_child;
GtkWidget *parent;
GtkWidget *widget = GTK_WIDGET (paned);
GList *temp_list = NULL;
GList *list;
if (paned->in_recursion)
return;
g_assert (widgets != NULL);
if (paned->last_start_child_focus &&
!gtk_widget_is_ancestor (paned->last_start_child_focus, widget))
{
gtk_paned_set_last_start_child_focus (paned, NULL);
}
if (paned->last_end_child_focus &&
!gtk_widget_is_ancestor (paned->last_end_child_focus, widget))
{
gtk_paned_set_last_end_child_focus (paned, NULL);
}
parent = gtk_widget_get_parent (widget);
if (parent)
ancestor = gtk_widget_get_ancestor (parent, GTK_TYPE_PANED);
/* The idea here is that temp_list is a list of widgets we want to cycle
* to. The list is prioritized so that the first element is our first
* choice, the next our second, and so on.
*
* We can't just use g_list_reverse(), because we want to try
* paned->last_child?_focus before paned->child?, both when we
* are going forward and backward.
*/
focus_child = gtk_widget_get_focus_child (GTK_WIDGET (paned));
if (direction == GTK_DIR_TAB_FORWARD)
{
if (focus_child == paned->start_child)
{
temp_list = g_list_append (temp_list, paned->last_end_child_focus);
temp_list = g_list_append (temp_list, paned->end_child);
temp_list = g_list_append (temp_list, ancestor);
}
else if (focus_child == paned->end_child)
{
temp_list = g_list_append (temp_list, ancestor);
temp_list = g_list_append (temp_list, paned->last_start_child_focus);
temp_list = g_list_append (temp_list, paned->start_child);
}
else
{
temp_list = g_list_append (temp_list, paned->last_start_child_focus);
temp_list = g_list_append (temp_list, paned->start_child);
temp_list = g_list_append (temp_list, paned->last_end_child_focus);
temp_list = g_list_append (temp_list, paned->end_child);
temp_list = g_list_append (temp_list, ancestor);
}
}
else
{
if (focus_child == paned->start_child)
{
temp_list = g_list_append (temp_list, ancestor);
temp_list = g_list_append (temp_list, paned->last_end_child_focus);
temp_list = g_list_append (temp_list, paned->end_child);
}
else if (focus_child == paned->end_child)
{
temp_list = g_list_append (temp_list, paned->last_start_child_focus);
temp_list = g_list_append (temp_list, paned->start_child);
temp_list = g_list_append (temp_list, ancestor);
}
else
{
temp_list = g_list_append (temp_list, paned->last_end_child_focus);
temp_list = g_list_append (temp_list, paned->end_child);
temp_list = g_list_append (temp_list, paned->last_start_child_focus);
temp_list = g_list_append (temp_list, paned->start_child);
temp_list = g_list_append (temp_list, ancestor);
}
}
/* Walk the list and expand all the paned widgets. */
for (list = temp_list; list != NULL; list = list->next)
{
widget = list->data;
if (widget)
{
if (GTK_IS_PANED (widget))
{
paned->in_recursion = TRUE;
gtk_paned_get_cycle_chain (GTK_PANED (widget), direction, widgets);
paned->in_recursion = FALSE;
}
else
{
*widgets = g_list_append (*widgets, widget);
}
}
}
g_list_free (temp_list);
}
static gboolean
gtk_paned_cycle_child_focus (GtkPaned *paned,
gboolean reversed)
{
GList *cycle_chain = NULL;
GList *list;
GtkDirectionType direction = reversed? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD;
/* ignore f6 if the handle is focused */
if (gtk_widget_is_focus (GTK_WIDGET (paned)))
return TRUE;
/* we can't just let the event propagate up the hierarchy,
* because the paned will want to cycle focus _unless_ an
* ancestor paned handles the event
*/
gtk_paned_get_cycle_chain (paned, direction, &cycle_chain);
for (list = cycle_chain; list != NULL; list = list->next)
if (gtk_widget_child_focus (GTK_WIDGET (list->data), direction))
break;
g_list_free (cycle_chain);
return TRUE;
}
static void
get_child_panes (GtkWidget *widget,
GList **panes)
{
if (!widget || !gtk_widget_get_realized (widget))
return;
if (GTK_IS_PANED (widget))
{
GtkPaned *paned = GTK_PANED (widget);
get_child_panes (paned->start_child, panes);
*panes = g_list_prepend (*panes, widget);
get_child_panes (paned->end_child, panes);
}
else
{
GtkWidget *child;
for (child = gtk_widget_get_first_child (widget);
child != NULL;
child = gtk_widget_get_next_sibling (child))
get_child_panes (child, panes);
}
}
static GList *
get_all_panes (GtkPaned *paned)
{
GtkPaned *topmost = NULL;
GList *result = NULL;
GtkWidget *w;
for (w = GTK_WIDGET (paned); w != NULL; w = gtk_widget_get_parent (w))
{
if (GTK_IS_PANED (w))
topmost = GTK_PANED (w);
}
g_assert (topmost);
get_child_panes (GTK_WIDGET (topmost), &result);
return g_list_reverse (result);
}
static void
gtk_paned_find_neighbours (GtkPaned *paned,
GtkPaned **next,
GtkPaned **prev)
{
GList *all_panes;
GList *this_link;
all_panes = get_all_panes (paned);
g_assert (all_panes);
this_link = g_list_find (all_panes, paned);
g_assert (this_link);
if (this_link->next)
*next = this_link->next->data;
else
*next = all_panes->data;
if (this_link->prev)
*prev = this_link->prev->data;
else
*prev = g_list_last (all_panes)->data;
g_list_free (all_panes);
}
static gboolean
gtk_paned_move_handle (GtkPaned *paned,
GtkScrollType scroll)
{
if (gtk_widget_is_focus (GTK_WIDGET (paned)))
{
int old_position;
int new_position;
int increment;
enum {
SINGLE_STEP_SIZE = 1,
PAGE_STEP_SIZE = 75
};
new_position = old_position = gtk_paned_get_position (paned);
increment = 0;
switch (scroll)
{
case GTK_SCROLL_STEP_LEFT:
case GTK_SCROLL_STEP_UP:
case GTK_SCROLL_STEP_BACKWARD:
increment = - SINGLE_STEP_SIZE;
break;
case GTK_SCROLL_STEP_RIGHT:
case GTK_SCROLL_STEP_DOWN:
case GTK_SCROLL_STEP_FORWARD:
increment = SINGLE_STEP_SIZE;
break;
case GTK_SCROLL_PAGE_LEFT:
case GTK_SCROLL_PAGE_UP:
case GTK_SCROLL_PAGE_BACKWARD:
increment = - PAGE_STEP_SIZE;
break;
case GTK_SCROLL_PAGE_RIGHT:
case GTK_SCROLL_PAGE_DOWN:
case GTK_SCROLL_PAGE_FORWARD:
increment = PAGE_STEP_SIZE;
break;
case GTK_SCROLL_START:
new_position = paned->min_position;
break;
case GTK_SCROLL_END:
new_position = paned->max_position;
break;
case GTK_SCROLL_NONE:
case GTK_SCROLL_JUMP:
default:
break;
}
if (increment)
{
if (is_rtl (paned))
increment = -increment;
new_position = old_position + increment;
}
new_position = CLAMP (new_position, paned->min_position, paned->max_position);
if (old_position != new_position)
gtk_paned_set_position (paned, new_position);
return TRUE;
}
return FALSE;
}
static void
gtk_paned_restore_focus (GtkPaned *paned)
{
if (gtk_widget_is_focus (GTK_WIDGET (paned)))
{
if (paned->saved_focus &&
gtk_widget_get_sensitive (paned->saved_focus))
{
gtk_widget_grab_focus (paned->saved_focus);
}
else
{
/* the saved focus is somehow not available for focusing,
* try
* 1) tabbing into the paned widget
* if that didn't work,
* 2) unset focus for the window if there is one
*/
if (!gtk_widget_child_focus (GTK_WIDGET (paned), GTK_DIR_TAB_FORWARD))
{
GtkRoot *root = gtk_widget_get_root (GTK_WIDGET (paned));
gtk_root_set_focus (root, NULL);
}
}
gtk_paned_set_saved_focus (paned, NULL);
gtk_paned_set_first_paned (paned, NULL);
}
}
static gboolean
gtk_paned_accept_position (GtkPaned *paned)
{
if (gtk_widget_is_focus (GTK_WIDGET (paned)))
{
paned->original_position = -1;
gtk_paned_restore_focus (paned);
return TRUE;
}
return FALSE;
}
static gboolean
gtk_paned_cancel_position (GtkPaned *paned)
{
if (gtk_widget_is_focus (GTK_WIDGET (paned)))
{
if (paned->original_position != -1)
{
gtk_paned_set_position (paned, paned->original_position);
paned->original_position = -1;
}
gtk_paned_restore_focus (paned);
return TRUE;
}
return FALSE;
}
static gboolean
gtk_paned_cycle_handle_focus (GtkPaned *paned,
gboolean reversed)
{
GtkPaned *next, *prev;
if (gtk_widget_is_focus (GTK_WIDGET (paned)))
{
GtkPaned *focus = NULL;
if (!paned->first_paned)
{
/* The first_pane has disappeared. As an ad-hoc solution,
* we make the currently focused paned the first_paned. To the
* user this will seem like the paned cycling has been reset.
*/
gtk_paned_set_first_paned (paned, paned);
}
gtk_paned_find_neighbours (paned, &next, &prev);
if (reversed && prev &&
prev != paned && paned != paned->first_paned)
{
focus = prev;
}
else if (!reversed && next &&
next != paned && next != paned->first_paned)
{
focus = next;
}
else
{
gtk_paned_accept_position (paned);
return TRUE;
}
g_assert (focus);
gtk_paned_set_saved_focus (focus, paned->saved_focus);
gtk_paned_set_first_paned (focus, paned->first_paned);
gtk_paned_set_saved_focus (paned, NULL);
gtk_paned_set_first_paned (paned, NULL);
gtk_widget_grab_focus (GTK_WIDGET (focus));
if (!gtk_widget_is_focus (GTK_WIDGET (paned)))
{
paned->original_position = -1;
paned->original_position = gtk_paned_get_position (focus);
}
}
else
{
GtkPaned *focus;
GtkPaned *first;
GtkWidget *focus_child;
gtk_paned_find_neighbours (paned, &next, &prev);
focus_child = gtk_widget_get_focus_child (GTK_WIDGET (paned));
if (focus_child == paned->start_child)
{
if (reversed)
{
focus = prev;
first = paned;
}
else
{
focus = paned;
first = paned;
}
}
else if (focus_child == paned->end_child)
{
if (reversed)
{
focus = paned;
first = next;
}
else
{
focus = next;
first = next;
}
}
else
{
/* Focus is not inside this paned, and we don't have focus.
* Presumably this happened because the application wants us
* to start keyboard navigating.
*/
focus = paned;
if (reversed)
first = paned;
else
first = next;
}
gtk_paned_set_saved_focus (focus, gtk_root_get_focus (gtk_widget_get_root (GTK_WIDGET (paned))));
gtk_paned_set_first_paned (focus, first);
paned->original_position = gtk_paned_get_position (focus);
gtk_widget_grab_focus (GTK_WIDGET (focus));
}
return TRUE;
}
static gboolean
gtk_paned_toggle_handle_focus (GtkPaned *paned)
{
/* This function/signal has the wrong name. It is called when you
* press Tab or Shift-Tab and what we do is act as if
* the user pressed Return and then Tab or Shift-Tab
*/
if (gtk_widget_is_focus (GTK_WIDGET (paned)))
gtk_paned_accept_position (paned);
return FALSE;
}
/**
* gtk_paned_set_wide_handle: (attributes org.gtk.Method.set_property=wide-handle)
* @paned: a `GtkPaned`
* @wide: the new value for the [property@Gtk.Paned:wide-handle] property
*
* Sets whether the separator should be wide.
*/
void
gtk_paned_set_wide_handle (GtkPaned *paned,
gboolean wide)
{
gboolean old_wide;
g_return_if_fail (GTK_IS_PANED (paned));
old_wide = gtk_paned_get_wide_handle (paned);
if (old_wide != wide)
{
if (wide)
gtk_widget_add_css_class (paned->handle_widget, "wide");
else
gtk_widget_remove_css_class (paned->handle_widget, "wide");
g_object_notify_by_pspec (G_OBJECT (paned), paned_props[PROP_WIDE_HANDLE]);
}
}
/**
* gtk_paned_get_wide_handle: (attributes org.gtk.Method.get_property=wide-handle)
* @paned: a `GtkPaned`
*
* Gets whether the separator should be wide.
*
* Returns: %TRUE if the paned should have a wide handle
*/
gboolean
gtk_paned_get_wide_handle (GtkPaned *paned)
{
g_return_val_if_fail (GTK_IS_PANED (paned), FALSE);
return gtk_widget_has_css_class (paned->handle_widget, "wide");
}