1/* GTK - The GIMP Toolkit
2 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18/*
19 * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
20 * file for a list of people on the GTK+ Team. See the ChangeLog
21 * files for a list of changes. These files are distributed with
22 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
23 */
24
25#include "config.h"
26
27#include "gtkpaned.h"
28
29#include "gtkcssboxesprivate.h"
30#include "gtkeventcontrollermotion.h"
31#include "gtkgesturepan.h"
32#include "gtkgesturesingle.h"
33#include "gtkpanedhandleprivate.h"
34#include "gtkintl.h"
35#include "gtkmarshalers.h"
36#include "gtkorientable.h"
37#include "gtkprivate.h"
38#include "gtktypebuiltins.h"
39#include "gtkwidgetprivate.h"
40#include "gtkbuildable.h"
41
42#include <math.h>
43
44/**
45 * GtkPaned:
46 *
47 * A widget with two panes, arranged either horizontally or vertically.
48 *
49 * ![An example GtkPaned](panes.png)
50 *
51 * The division between the two panes is adjustable by the user
52 * by dragging a handle.
53 *
54 * Child widgets are added to the panes of the widget with
55 * [method@Gtk.Paned.set_start_child] and [method@Gtk.Paned.set_end_child].
56 * The division between the two children is set by default from the size
57 * requests of the children, but it can be adjusted by the user.
58 *
59 * A paned widget draws a separator between the two child widgets and a
60 * small handle that the user can drag to adjust the division. It does not
61 * draw any relief around the children or around the separator. (The space
62 * in which the separator is called the gutter.) Often, it is useful to put
63 * each child inside a [class@Gtk.Frame] so that the gutter appears as a
64 * ridge. No separator is drawn if one of the children is missing.
65 *
66 * Each child has two options that can be set, "resize" and "shrink". If
67 * "resize" is true then, when the `GtkPaned` is resized, that child will
68 * expand or shrink along with the paned widget. If "shrink" is true, then
69 * that child can be made smaller than its requisition by the user.
70 * Setting "shrink" to false allows the application to set a minimum size.
71 * If "resize" is false for both children, then this is treated as if
72 * "resize" is true for both children.
73 *
74 * The application can set the position of the slider as if it were set
75 * by the user, by calling [method@Gtk.Paned.set_position].
76 *
77 * # CSS nodes
78 *
79 * ```
80 * paned
81 * ├── <child>
82 * ├── separator[.wide]
83 * ╰── <child>
84 * ```
85 *
86 * `GtkPaned` has a main CSS node with name paned, and a subnode for
87 * the separator with name separator. The subnode gets a .wide style
88 * class when the paned is supposed to be wide.
89 *
90 * In horizontal orientation, the nodes are arranged based on the text
91 * direction, so in left-to-right mode, :first-child will select the
92 * leftmost child, while it will select the rightmost child in
93 * RTL layouts.
94 *
95 * ## Creating a paned widget with minimum sizes.
96 *
97 * ```c
98 * GtkWidget *hpaned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
99 * GtkWidget *frame1 = gtk_frame_new (NULL);
100 * GtkWidget *frame2 = gtk_frame_new (NULL);
101 *
102 * gtk_widget_set_size_request (hpaned, 200, -1);
103 *
104 * gtk_paned_set_start_child (GTK_PANED (hpaned), frame1);
105 * gtk_paned_set_start_child_resize (GTK_PANED (hpaned), TRUE);
106 * gtk_paned_set_start_child_shrink (GTK_PANED (hpaned), FALSE);
107 * gtk_widget_set_size_request (frame1, 50, -1);
108 *
109 * gtk_paned_set_end_child (GTK_PANED (hpaned), frame2);
110 * gtk_paned_set_end_child_resize (GTK_PANED (hpaned), FALSE);
111 * gtk_paned_set_end_child_shrink (GTK_PANED (hpaned), FALSE);
112 * gtk_widget_set_size_request (frame2, 50, -1);
113 * ```
114 */
115
116#define HANDLE_EXTRA_SIZE 6
117
118typedef struct _GtkPanedClass GtkPanedClass;
119
120struct _GtkPaned
121{
122 GtkWidget parent_instance;
123
124 GtkPaned *first_paned;
125 GtkWidget *start_child;
126 GtkWidget *end_child;
127 GtkWidget *last_start_child_focus;
128 GtkWidget *last_end_child_focus;
129 GtkWidget *saved_focus;
130 GtkOrientation orientation;
131
132 GtkWidget *handle_widget;
133
134 GtkGesture *pan_gesture; /* Used for touch */
135 GtkGesture *drag_gesture; /* Used for mice */
136
137 int start_child_size;
138 int drag_pos;
139 int last_allocation;
140 int max_position;
141 int min_position;
142 int original_position;
143
144 guint in_recursion : 1;
145 guint resize_start_child : 1;
146 guint shrink_start_child : 1;
147 guint resize_end_child : 1;
148 guint shrink_end_child : 1;
149 guint position_set : 1;
150 guint panning : 1;
151};
152
153struct _GtkPanedClass
154{
155 GtkWidgetClass parent_class;
156
157 gboolean (* cycle_child_focus) (GtkPaned *paned,
158 gboolean reverse);
159 gboolean (* toggle_handle_focus) (GtkPaned *paned);
160 gboolean (* move_handle) (GtkPaned *paned,
161 GtkScrollType scroll);
162 gboolean (* cycle_handle_focus) (GtkPaned *paned,
163 gboolean reverse);
164 gboolean (* accept_position) (GtkPaned *paned);
165 gboolean (* cancel_position) (GtkPaned *paned);
166};
167
168enum {
169 PROP_0,
170 PROP_POSITION,
171 PROP_POSITION_SET,
172 PROP_MIN_POSITION,
173 PROP_MAX_POSITION,
174 PROP_WIDE_HANDLE,
175 PROP_RESIZE_START_CHILD,
176 PROP_RESIZE_END_CHILD,
177 PROP_SHRINK_START_CHILD,
178 PROP_SHRINK_END_CHILD,
179 PROP_START_CHILD,
180 PROP_END_CHILD,
181 LAST_PROP,
182
183 /* GtkOrientable */
184 PROP_ORIENTATION,
185};
186
187enum {
188 CYCLE_CHILD_FOCUS,
189 TOGGLE_HANDLE_FOCUS,
190 MOVE_HANDLE,
191 CYCLE_HANDLE_FOCUS,
192 ACCEPT_POSITION,
193 CANCEL_POSITION,
194 LAST_SIGNAL
195};
196
197static void gtk_paned_set_property (GObject *object,
198 guint prop_id,
199 const GValue *value,
200 GParamSpec *pspec);
201static void gtk_paned_get_property (GObject *object,
202 guint prop_id,
203 GValue *value,
204 GParamSpec *pspec);
205static void gtk_paned_dispose (GObject *object);
206static void gtk_paned_measure (GtkWidget *widget,
207 GtkOrientation orientation,
208 int for_size,
209 int *minimum,
210 int *natural,
211 int *minimum_baseline,
212 int *natural_baseline);
213static void gtk_paned_size_allocate (GtkWidget *widget,
214 int width,
215 int height,
216 int baseline);
217static void gtk_paned_unrealize (GtkWidget *widget);
218static void gtk_paned_css_changed (GtkWidget *widget,
219 GtkCssStyleChange *change);
220static void gtk_paned_calc_position (GtkPaned *paned,
221 int allocation,
222 int start_child_req,
223 int end_child_req);
224static void gtk_paned_set_focus_child (GtkWidget *widget,
225 GtkWidget *child);
226static void gtk_paned_set_saved_focus (GtkPaned *paned,
227 GtkWidget *widget);
228static void gtk_paned_set_first_paned (GtkPaned *paned,
229 GtkPaned *first_paned);
230static void gtk_paned_set_last_start_child_focus (GtkPaned *paned,
231 GtkWidget *widget);
232static void gtk_paned_set_last_end_child_focus (GtkPaned *paned,
233 GtkWidget *widget);
234static gboolean gtk_paned_cycle_child_focus (GtkPaned *paned,
235 gboolean reverse);
236static gboolean gtk_paned_cycle_handle_focus (GtkPaned *paned,
237 gboolean reverse);
238static gboolean gtk_paned_move_handle (GtkPaned *paned,
239 GtkScrollType scroll);
240static gboolean gtk_paned_accept_position (GtkPaned *paned);
241static gboolean gtk_paned_cancel_position (GtkPaned *paned);
242static gboolean gtk_paned_toggle_handle_focus (GtkPaned *paned);
243
244static void update_drag (GtkPaned *paned,
245 int xpos,
246 int ypos);
247
248static void gtk_paned_buildable_iface_init (GtkBuildableIface *iface);
249
250G_DEFINE_TYPE_WITH_CODE (GtkPaned, gtk_paned, GTK_TYPE_WIDGET,
251 G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)
252 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
253 gtk_paned_buildable_iface_init))
254
255static guint signals[LAST_SIGNAL] = { 0 };
256static GParamSpec *paned_props[LAST_PROP] = { NULL, };
257
258static void
259add_tab_bindings (GtkWidgetClass *widget_class,
260 GdkModifierType modifiers)
261{
262 gtk_widget_class_add_binding_signal (widget_class,
263 GDK_KEY_Tab, mods: modifiers,
264 signal: "toggle-handle-focus",
265 NULL);
266 gtk_widget_class_add_binding_signal (widget_class,
267 GDK_KEY_KP_Tab, mods: modifiers,
268 signal: "toggle-handle-focus",
269 NULL);
270}
271
272static void
273add_move_binding (GtkWidgetClass *widget_class,
274 guint keyval,
275 GdkModifierType mask,
276 GtkScrollType scroll)
277{
278 gtk_widget_class_add_binding_signal (widget_class,
279 keyval, mods: mask,
280 signal: "move-handle",
281 format_string: "(i)", scroll);
282}
283
284static void
285get_handle_area (GtkPaned *paned,
286 graphene_rect_t *area)
287{
288 int extra = 0;
289
290 if (!gtk_widget_compute_bounds (widget: paned->handle_widget, GTK_WIDGET (paned), out_bounds: area))
291 return;
292
293 if (!gtk_paned_get_wide_handle (paned))
294 extra = HANDLE_EXTRA_SIZE;
295
296 graphene_rect_inset (r: area, d_x: - extra, d_y: - extra);
297}
298
299static void
300gtk_paned_compute_expand (GtkWidget *widget,
301 gboolean *hexpand,
302 gboolean *vexpand)
303{
304 GtkPaned *paned = GTK_PANED (widget);
305 gboolean h = FALSE;
306 gboolean v = FALSE;
307
308 if (paned->start_child)
309 {
310 h = h || gtk_widget_compute_expand (widget: paned->start_child, orientation: GTK_ORIENTATION_HORIZONTAL);
311 v = v || gtk_widget_compute_expand (widget: paned->start_child, orientation: GTK_ORIENTATION_VERTICAL);
312 }
313
314 if (paned->end_child)
315 {
316 h = h || gtk_widget_compute_expand (widget: paned->end_child, orientation: GTK_ORIENTATION_HORIZONTAL);
317 v = v || gtk_widget_compute_expand (widget: paned->end_child, orientation: GTK_ORIENTATION_VERTICAL);
318 }
319
320 *hexpand = h;
321 *vexpand = v;
322}
323
324static GtkSizeRequestMode
325gtk_paned_get_request_mode (GtkWidget *widget)
326{
327 GtkPaned *paned = GTK_PANED (widget);
328 int wfh = 0, hfw = 0;
329
330 if (paned->start_child)
331 {
332 switch (gtk_widget_get_request_mode (widget: paned->start_child))
333 {
334 case GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH:
335 hfw++;
336 break;
337 case GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT:
338 wfh++;
339 break;
340 case GTK_SIZE_REQUEST_CONSTANT_SIZE:
341 default:
342 break;
343 }
344 }
345 if (paned->end_child)
346 {
347 switch (gtk_widget_get_request_mode (widget: paned->end_child))
348 {
349 case GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH:
350 hfw++;
351 break;
352 case GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT:
353 wfh++;
354 break;
355 case GTK_SIZE_REQUEST_CONSTANT_SIZE:
356 default:
357 break;
358 }
359 }
360
361 if (hfw == 0 && wfh == 0)
362 return GTK_SIZE_REQUEST_CONSTANT_SIZE;
363 else
364 return wfh > hfw ?
365 GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT :
366 GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
367}
368
369static void
370gtk_paned_set_orientation (GtkPaned *self,
371 GtkOrientation orientation)
372{
373 if (self->orientation != orientation)
374 {
375 static const char *cursor_name[2] = {
376 "col-resize",
377 "row-resize",
378 };
379
380 self->orientation = orientation;
381
382 gtk_widget_update_orientation (GTK_WIDGET (self), orientation: self->orientation);
383 gtk_widget_set_cursor_from_name (widget: self->handle_widget,
384 name: cursor_name[orientation]);
385 gtk_gesture_pan_set_orientation (GTK_GESTURE_PAN (self->pan_gesture),
386 orientation);
387
388 gtk_widget_queue_resize (GTK_WIDGET (self));
389 g_object_notify (G_OBJECT (self), property_name: "orientation");
390 }
391}
392
393static void
394gtk_paned_class_init (GtkPanedClass *class)
395{
396 GObjectClass *object_class = G_OBJECT_CLASS (class);
397 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
398
399 object_class->set_property = gtk_paned_set_property;
400 object_class->get_property = gtk_paned_get_property;
401 object_class->dispose = gtk_paned_dispose;
402
403 widget_class->measure = gtk_paned_measure;
404 widget_class->size_allocate = gtk_paned_size_allocate;
405 widget_class->unrealize = gtk_paned_unrealize;
406 widget_class->focus = gtk_widget_focus_child;
407 widget_class->set_focus_child = gtk_paned_set_focus_child;
408 widget_class->css_changed = gtk_paned_css_changed;
409 widget_class->get_request_mode = gtk_paned_get_request_mode;
410 widget_class->compute_expand = gtk_paned_compute_expand;
411
412 class->cycle_child_focus = gtk_paned_cycle_child_focus;
413 class->toggle_handle_focus = gtk_paned_toggle_handle_focus;
414 class->move_handle = gtk_paned_move_handle;
415 class->cycle_handle_focus = gtk_paned_cycle_handle_focus;
416 class->accept_position = gtk_paned_accept_position;
417 class->cancel_position = gtk_paned_cancel_position;
418
419 /**
420 * GtkPaned:position: (attributes org.gtk.Property.get=gtk_paned_get_position org.gtk.Property.set=gtk_paned_set_position)
421 *
422 * Position of the separator in pixels, from the left/top.
423 */
424 paned_props[PROP_POSITION] =
425 g_param_spec_int (name: "position",
426 P_("Position"),
427 P_("Position of paned separator in pixels (0 means all the way to the left/top)"),
428 minimum: 0, G_MAXINT, default_value: 0,
429 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
430
431 /**
432 * GtkPaned:position-set:
433 *
434 * Whether the [property@Gtk.Paned:position] property has been set.
435 */
436 paned_props[PROP_POSITION_SET] =
437 g_param_spec_boolean (name: "position-set",
438 P_("Position Set"),
439 P_("TRUE if the Position property should be used"),
440 FALSE,
441 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
442
443 /**
444 * GtkPaned:min-position:
445 *
446 * The smallest possible value for the [property@Gtk.Paned:position]
447 * property.
448 *
449 * This property is derived from the size and shrinkability
450 * of the widget's children.
451 */
452 paned_props[PROP_MIN_POSITION] =
453 g_param_spec_int (name: "min-position",
454 P_("Minimal Position"),
455 P_("Smallest possible value for the “position” property"),
456 minimum: 0, G_MAXINT, default_value: 0,
457 GTK_PARAM_READABLE|G_PARAM_EXPLICIT_NOTIFY);
458
459 /**
460 * GtkPaned:max-position:
461 *
462 * The largest possible value for the [property@Gtk.Paned:position]
463 * property.
464 *
465 * This property is derived from the size and shrinkability
466 * of the widget's children.
467 */
468 paned_props[PROP_MAX_POSITION] =
469 g_param_spec_int (name: "max-position",
470 P_("Maximal Position"),
471 P_("Largest possible value for the “position” property"),
472 minimum: 0, G_MAXINT, G_MAXINT,
473 GTK_PARAM_READABLE|G_PARAM_EXPLICIT_NOTIFY);
474
475 /**
476 * GtkPaned:wide-handle: (attributes org.gtk.Property.get=gtk_paned_get_wide_handle org.gtk.Property.set=gtk_paned_set_wide_handle)
477 *
478 * Whether the `GtkPaned` should provide a stronger visual separation.
479 *
480 * For example, this could be set when a paned contains two
481 * [class@Gtk.Notebook]s, whose tab rows would otherwise merge visually.
482 */
483 paned_props[PROP_WIDE_HANDLE] =
484 g_param_spec_boolean (name: "wide-handle",
485 P_("Wide Handle"),
486 P_("Whether the paned should have a prominent handle"),
487 FALSE,
488 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
489
490 /**
491 * 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)
492 *
493 * Determines whether the first child expands and shrinks
494 * along with the paned widget.
495 */
496 paned_props[PROP_RESIZE_START_CHILD] =
497 g_param_spec_boolean (name: "resize-start-child",
498 P_("Resize first child"),
499 P_("If TRUE, the first child expands and shrinks along with the paned widget"),
500 TRUE,
501 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
502
503 /**
504 * 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)
505 *
506 * Determines whether the second child expands and shrinks
507 * along with the paned widget.
508 */
509 paned_props[PROP_RESIZE_END_CHILD] =
510 g_param_spec_boolean (name: "resize-end-child",
511 P_("Resize second child"),
512 P_("If TRUE, the second child expands and shrinks along with the paned widget"),
513 TRUE,
514 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
515
516 /**
517 * 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)
518 *
519 * Determines whether the first child can be made smaller
520 * than its requisition.
521 */
522 paned_props[PROP_SHRINK_START_CHILD] =
523 g_param_spec_boolean (name: "shrink-start-child",
524 P_("Shrink first child"),
525 P_("If TRUE, the first child can be made smaller than its requisition"),
526 TRUE,
527 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
528
529 /**
530 * 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)
531 *
532 * Determines whether the second child can be made smaller
533 * than its requisition.
534 */
535 paned_props[PROP_SHRINK_END_CHILD] =
536 g_param_spec_boolean (name: "shrink-end-child",
537 P_("Shrink second child"),
538 P_("If TRUE, the second child can be made smaller than its requisition"),
539 TRUE,
540 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
541
542 /**
543 * GtkPaned:start-child: (attributes org.gtk.Property.get=gtk_paned_get_start_child org.gtk.Property.set=gtk_paned_set_start_child)
544 *
545 * The first child.
546 */
547 paned_props[PROP_START_CHILD] =
548 g_param_spec_object (name: "start-child",
549 P_("First child"),
550 P_("The first child"),
551 GTK_TYPE_WIDGET,
552 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
553
554 /**
555 * GtkPaned:end-child: (attributes org.gtk.Property.get=gtk_paned_get_end_child org.gtk.Property.set=gtk_paned_set_end_child)
556 *
557 * The second child.
558 */
559 paned_props[PROP_END_CHILD] =
560 g_param_spec_object (name: "end-child",
561 P_("Second child"),
562 P_("The second child"),
563 GTK_TYPE_WIDGET,
564 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
565
566 g_object_class_install_properties (oclass: object_class, n_pspecs: LAST_PROP, pspecs: paned_props);
567 g_object_class_override_property (oclass: object_class, property_id: PROP_ORIENTATION, name: "orientation");
568
569 /**
570 * GtkPaned::cycle-child-focus:
571 * @widget: the object that received the signal
572 * @reversed: whether cycling backward or forward
573 *
574 * Emitted to cycle the focus between the children of the paned.
575 *
576 * This is a [keybinding signal](class.SignalAction.html).
577 *
578 * The default binding is <kbd>F6</kbd>.
579 */
580 signals [CYCLE_CHILD_FOCUS] =
581 g_signal_new (I_("cycle-child-focus"),
582 G_TYPE_FROM_CLASS (object_class),
583 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
584 G_STRUCT_OFFSET (GtkPanedClass, cycle_child_focus),
585 NULL, NULL,
586 c_marshaller: _gtk_marshal_BOOLEAN__BOOLEAN,
587 G_TYPE_BOOLEAN, n_params: 1,
588 G_TYPE_BOOLEAN);
589
590 /**
591 * GtkPaned::toggle-handle-focus:
592 * @widget: the object that received the signal
593 *
594 * Emitted to accept the current position of the handle and then
595 * move focus to the next widget in the focus chain.
596 *
597 * This is a [keybinding signal](class.SignalAction.html).
598 *
599 * The default binding is <kbd>Tab</kbd>.
600 */
601 signals [TOGGLE_HANDLE_FOCUS] =
602 g_signal_new (I_("toggle-handle-focus"),
603 G_TYPE_FROM_CLASS (object_class),
604 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
605 G_STRUCT_OFFSET (GtkPanedClass, toggle_handle_focus),
606 NULL, NULL,
607 c_marshaller: _gtk_marshal_BOOLEAN__VOID,
608 G_TYPE_BOOLEAN, n_params: 0);
609
610 /**
611 * GtkPaned::move-handle:
612 * @widget: the object that received the signal
613 * @scroll_type: a `GtkScrollType`
614 *
615 * Emitted to move the handle with key bindings.
616 *
617 * This is a [keybinding signal](class.SignalAction.html).
618 */
619 signals[MOVE_HANDLE] =
620 g_signal_new (I_("move-handle"),
621 G_TYPE_FROM_CLASS (object_class),
622 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
623 G_STRUCT_OFFSET (GtkPanedClass, move_handle),
624 NULL, NULL,
625 c_marshaller: _gtk_marshal_BOOLEAN__ENUM,
626 G_TYPE_BOOLEAN, n_params: 1,
627 GTK_TYPE_SCROLL_TYPE);
628
629 /**
630 * GtkPaned::cycle-handle-focus:
631 * @widget: the object that received the signal
632 * @reversed: whether cycling backward or forward
633 *
634 * Emitted to cycle whether the paned should grab focus to allow
635 * the user to change position of the handle by using key bindings.
636 *
637 * This is a [keybinding signal](class.SignalAction.html).
638 *
639 * The default binding for this signal is <kbd>F8</kbd>.
640 */
641 signals [CYCLE_HANDLE_FOCUS] =
642 g_signal_new (I_("cycle-handle-focus"),
643 G_TYPE_FROM_CLASS (object_class),
644 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
645 G_STRUCT_OFFSET (GtkPanedClass, cycle_handle_focus),
646 NULL, NULL,
647 c_marshaller: _gtk_marshal_BOOLEAN__BOOLEAN,
648 G_TYPE_BOOLEAN, n_params: 1,
649 G_TYPE_BOOLEAN);
650
651 /**
652 * GtkPaned::accept-position:
653 * @widget: the object that received the signal
654 *
655 * Emitted to accept the current position of the handle when
656 * moving it using key bindings.
657 *
658 * This is a [keybinding signal](class.SignalAction.html).
659 *
660 * The default binding for this signal is <kbd>Return</kbd> or
661 * <kbd>Space</kbd>.
662 */
663 signals [ACCEPT_POSITION] =
664 g_signal_new (I_("accept-position"),
665 G_TYPE_FROM_CLASS (object_class),
666 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
667 G_STRUCT_OFFSET (GtkPanedClass, accept_position),
668 NULL, NULL,
669 c_marshaller: _gtk_marshal_BOOLEAN__VOID,
670 G_TYPE_BOOLEAN, n_params: 0);
671
672 /**
673 * GtkPaned::cancel-position:
674 * @widget: the object that received the signal
675 *
676 * Emitted to cancel moving the position of the handle using key
677 * bindings.
678 *
679 * The position of the handle will be reset to the value prior to
680 * moving it.
681 *
682 * This is a [keybinding signal](class.SignalAction.html).
683 *
684 * The default binding for this signal is <kbd>Escape</kbd>.
685 */
686 signals [CANCEL_POSITION] =
687 g_signal_new (I_("cancel-position"),
688 G_TYPE_FROM_CLASS (object_class),
689 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
690 G_STRUCT_OFFSET (GtkPanedClass, cancel_position),
691 NULL, NULL,
692 c_marshaller: _gtk_marshal_BOOLEAN__VOID,
693 G_TYPE_BOOLEAN, n_params: 0);
694
695 /* F6 and friends */
696 gtk_widget_class_add_binding_signal (widget_class,
697 GDK_KEY_F6, mods: 0,
698 signal: "cycle-child-focus",
699 format_string: "(b)", FALSE);
700 gtk_widget_class_add_binding_signal (widget_class,
701 GDK_KEY_F6, mods: GDK_SHIFT_MASK,
702 signal: "cycle-child-focus",
703 format_string: "(b)", TRUE);
704
705 /* F8 and friends */
706 gtk_widget_class_add_binding_signal (widget_class,
707 GDK_KEY_F8, mods: 0,
708 signal: "cycle-handle-focus",
709 format_string: "(b)", FALSE);
710 gtk_widget_class_add_binding_signal (widget_class,
711 GDK_KEY_F8, mods: GDK_SHIFT_MASK,
712 signal: "cycle-handle-focus",
713 format_string: "(b)", TRUE);
714
715 add_tab_bindings (widget_class, modifiers: 0);
716 add_tab_bindings (widget_class, modifiers: GDK_CONTROL_MASK);
717 add_tab_bindings (widget_class, modifiers: GDK_SHIFT_MASK);
718 add_tab_bindings (widget_class, modifiers: GDK_CONTROL_MASK | GDK_SHIFT_MASK);
719
720 /* accept and cancel positions */
721 gtk_widget_class_add_binding_signal (widget_class,
722 GDK_KEY_Escape, mods: 0,
723 signal: "cancel-position",
724 NULL);
725
726 gtk_widget_class_add_binding_signal (widget_class,
727 GDK_KEY_Return, mods: 0,
728 signal: "accept-position",
729 NULL);
730 gtk_widget_class_add_binding_signal (widget_class,
731 GDK_KEY_ISO_Enter, mods: 0,
732 signal: "accept-position",
733 NULL);
734 gtk_widget_class_add_binding_signal (widget_class,
735 GDK_KEY_KP_Enter, mods: 0,
736 signal: "accept-position",
737 NULL);
738 gtk_widget_class_add_binding_signal (widget_class,
739 GDK_KEY_space, mods: 0,
740 signal: "accept-position",
741 NULL);
742 gtk_widget_class_add_binding_signal (widget_class,
743 GDK_KEY_KP_Space, mods: 0,
744 signal: "accept-position",
745 NULL);
746
747 /* move handle */
748 add_move_binding (widget_class, GDK_KEY_Left, mask: 0, scroll: GTK_SCROLL_STEP_LEFT);
749 add_move_binding (widget_class, GDK_KEY_KP_Left, mask: 0, scroll: GTK_SCROLL_STEP_LEFT);
750 add_move_binding (widget_class, GDK_KEY_Left, mask: GDK_CONTROL_MASK, scroll: GTK_SCROLL_PAGE_LEFT);
751 add_move_binding (widget_class, GDK_KEY_KP_Left, mask: GDK_CONTROL_MASK, scroll: GTK_SCROLL_PAGE_LEFT);
752
753 add_move_binding (widget_class, GDK_KEY_Right, mask: 0, scroll: GTK_SCROLL_STEP_RIGHT);
754 add_move_binding (widget_class, GDK_KEY_Right, mask: GDK_CONTROL_MASK, scroll: GTK_SCROLL_PAGE_RIGHT);
755 add_move_binding (widget_class, GDK_KEY_KP_Right, mask: 0, scroll: GTK_SCROLL_STEP_RIGHT);
756 add_move_binding (widget_class, GDK_KEY_KP_Right, mask: GDK_CONTROL_MASK, scroll: GTK_SCROLL_PAGE_RIGHT);
757
758 add_move_binding (widget_class, GDK_KEY_Up, mask: 0, scroll: GTK_SCROLL_STEP_UP);
759 add_move_binding (widget_class, GDK_KEY_Up, mask: GDK_CONTROL_MASK, scroll: GTK_SCROLL_PAGE_UP);
760 add_move_binding (widget_class, GDK_KEY_KP_Up, mask: 0, scroll: GTK_SCROLL_STEP_UP);
761 add_move_binding (widget_class, GDK_KEY_KP_Up, mask: GDK_CONTROL_MASK, scroll: GTK_SCROLL_PAGE_UP);
762 add_move_binding (widget_class, GDK_KEY_Page_Up, mask: 0, scroll: GTK_SCROLL_PAGE_UP);
763 add_move_binding (widget_class, GDK_KEY_KP_Page_Up, mask: 0, scroll: GTK_SCROLL_PAGE_UP);
764
765 add_move_binding (widget_class, GDK_KEY_Down, mask: 0, scroll: GTK_SCROLL_STEP_DOWN);
766 add_move_binding (widget_class, GDK_KEY_Down, mask: GDK_CONTROL_MASK, scroll: GTK_SCROLL_PAGE_DOWN);
767 add_move_binding (widget_class, GDK_KEY_KP_Down, mask: 0, scroll: GTK_SCROLL_STEP_DOWN);
768 add_move_binding (widget_class, GDK_KEY_KP_Down, mask: GDK_CONTROL_MASK, scroll: GTK_SCROLL_PAGE_DOWN);
769 add_move_binding (widget_class, GDK_KEY_Page_Down, mask: 0, scroll: GTK_SCROLL_PAGE_RIGHT);
770 add_move_binding (widget_class, GDK_KEY_KP_Page_Down, mask: 0, scroll: GTK_SCROLL_PAGE_RIGHT);
771
772 add_move_binding (widget_class, GDK_KEY_Home, mask: 0, scroll: GTK_SCROLL_START);
773 add_move_binding (widget_class, GDK_KEY_KP_Home, mask: 0, scroll: GTK_SCROLL_START);
774 add_move_binding (widget_class, GDK_KEY_End, mask: 0, scroll: GTK_SCROLL_END);
775 add_move_binding (widget_class, GDK_KEY_KP_End, mask: 0, scroll: GTK_SCROLL_END);
776
777 gtk_widget_class_set_css_name (widget_class, I_("paned"));
778}
779
780static GtkBuildableIface *parent_buildable_iface;
781
782static void
783gtk_paned_buildable_add_child (GtkBuildable *buildable,
784 GtkBuilder *builder,
785 GObject *child,
786 const char *type)
787{
788 GtkPaned *self = GTK_PANED (buildable);
789
790 if (g_strcmp0 (str1: type, str2: "start") == 0)
791 {
792 gtk_paned_set_start_child (paned: self, GTK_WIDGET (child));
793 gtk_paned_set_resize_start_child (paned: self, FALSE);
794 gtk_paned_set_shrink_start_child (paned: self, TRUE);
795 }
796 else if (g_strcmp0 (str1: type, str2: "end") == 0)
797 {
798 gtk_paned_set_end_child (paned: self, GTK_WIDGET (child));
799 gtk_paned_set_resize_end_child (paned: self, TRUE);
800 gtk_paned_set_shrink_end_child (paned: self, TRUE);
801 }
802 else if (type == NULL && GTK_IS_WIDGET (child))
803 {
804 if (self->start_child == NULL)
805 {
806 gtk_paned_set_start_child (paned: self, GTK_WIDGET (child));
807 gtk_paned_set_resize_start_child (paned: self, FALSE);
808 gtk_paned_set_shrink_start_child (paned: self, TRUE);
809 }
810 else if (self->end_child == NULL)
811 {
812 gtk_paned_set_end_child (paned: self, GTK_WIDGET (child));
813 gtk_paned_set_resize_end_child (paned: self, TRUE);
814 gtk_paned_set_shrink_end_child (paned: self, TRUE);
815 }
816 else
817 g_warning ("GtkPaned only accepts two widgets as children");
818 }
819 else
820 parent_buildable_iface->add_child (buildable, builder, child, type);
821}
822
823static void
824gtk_paned_buildable_iface_init (GtkBuildableIface *iface)
825{
826 parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface);
827 iface->add_child = gtk_paned_buildable_add_child;
828}
829
830static gboolean
831initiates_touch_drag (GtkPaned *paned,
832 double start_x,
833 double start_y)
834{
835 int handle_size, handle_pos, drag_pos;
836 graphene_rect_t handle_area;
837
838#define TOUCH_EXTRA_AREA_WIDTH 50
839 get_handle_area (paned, area: &handle_area);
840
841 if (paned->orientation == GTK_ORIENTATION_HORIZONTAL)
842 {
843 handle_pos = handle_area.origin.x;
844 drag_pos = start_x;
845 handle_size = handle_area.size.width;
846 }
847 else
848 {
849 handle_pos = handle_area.origin.y;
850 drag_pos = start_y;
851 handle_size = handle_area.size.height;
852 }
853
854 if (drag_pos < handle_pos - TOUCH_EXTRA_AREA_WIDTH ||
855 drag_pos > handle_pos + handle_size + TOUCH_EXTRA_AREA_WIDTH)
856 return FALSE;
857
858#undef TOUCH_EXTRA_AREA_WIDTH
859
860 return TRUE;
861}
862
863static void
864gesture_drag_begin_cb (GtkGestureDrag *gesture,
865 double start_x,
866 double start_y,
867 GtkPaned *paned)
868{
869 GdkEventSequence *sequence;
870 graphene_rect_t handle_area;
871 GdkEvent *event;
872 GdkDevice *device;
873 gboolean is_touch;
874
875 sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
876 event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
877 device = gdk_event_get_device (event);
878 paned->panning = FALSE;
879
880 is_touch = (gdk_event_get_event_type (event) == GDK_TOUCH_BEGIN ||
881 gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN);
882
883 get_handle_area (paned, area: &handle_area);
884
885 if ((is_touch && GTK_GESTURE (gesture) == paned->drag_gesture) ||
886 (!is_touch && GTK_GESTURE (gesture) == paned->pan_gesture))
887 {
888 gtk_gesture_set_state (GTK_GESTURE (gesture),
889 state: GTK_EVENT_SEQUENCE_DENIED);
890 return;
891 }
892
893 if (graphene_rect_contains_point (r: &handle_area, p: &(graphene_point_t){start_x, start_y}) ||
894 (is_touch && initiates_touch_drag (paned, start_x, start_y)))
895 {
896 if (paned->orientation == GTK_ORIENTATION_HORIZONTAL)
897 paned->drag_pos = start_x - handle_area.origin.x;
898 else
899 paned->drag_pos = start_y - handle_area.origin.y;
900
901 paned->panning = TRUE;
902
903 gtk_gesture_set_state (GTK_GESTURE (gesture),
904 state: GTK_EVENT_SEQUENCE_CLAIMED);
905 }
906 else
907 {
908 gtk_gesture_set_state (GTK_GESTURE (gesture),
909 state: GTK_EVENT_SEQUENCE_DENIED);
910 }
911}
912
913static void
914gesture_drag_update_cb (GtkGestureDrag *gesture,
915 double offset_x,
916 double offset_y,
917 GtkPaned *paned)
918{
919 double start_x, start_y;
920
921 gtk_gesture_drag_get_start_point (GTK_GESTURE_DRAG (gesture),
922 x: &start_x, y: &start_y);
923 update_drag (paned, xpos: start_x + offset_x, ypos: start_y + offset_y);
924}
925
926static void
927gesture_drag_end_cb (GtkGestureDrag *gesture,
928 double offset_x,
929 double offset_y,
930 GtkPaned *paned)
931{
932 if (!paned->panning)
933 gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_DENIED);
934
935 paned->panning = FALSE;
936}
937
938static void
939gtk_paned_set_property (GObject *object,
940 guint prop_id,
941 const GValue *value,
942 GParamSpec *pspec)
943{
944 GtkPaned *paned = GTK_PANED (object);
945
946 switch (prop_id)
947 {
948 case PROP_ORIENTATION:
949 gtk_paned_set_orientation (self: paned, orientation: g_value_get_enum (value));
950 break;
951 case PROP_POSITION:
952 gtk_paned_set_position (paned, position: g_value_get_int (value));
953 break;
954 case PROP_POSITION_SET:
955 if (paned->position_set != g_value_get_boolean (value))
956 {
957 paned->position_set = g_value_get_boolean (value);
958 gtk_widget_queue_resize (GTK_WIDGET (paned));
959 g_object_notify_by_pspec (object, pspec);
960 }
961 break;
962 case PROP_WIDE_HANDLE:
963 gtk_paned_set_wide_handle (paned, wide: g_value_get_boolean (value));
964 break;
965 case PROP_RESIZE_START_CHILD:
966 gtk_paned_set_resize_start_child (paned, resize: g_value_get_boolean (value));
967 break;
968 case PROP_RESIZE_END_CHILD:
969 gtk_paned_set_resize_end_child (paned, resize: g_value_get_boolean (value));
970 break;
971 case PROP_SHRINK_START_CHILD:
972 gtk_paned_set_shrink_start_child (paned, resize: g_value_get_boolean (value));
973 break;
974 case PROP_SHRINK_END_CHILD:
975 gtk_paned_set_shrink_end_child (paned, resize: g_value_get_boolean (value));
976 break;
977 case PROP_START_CHILD:
978 gtk_paned_set_start_child (paned, child: g_value_get_object (value));
979 break;
980 case PROP_END_CHILD:
981 gtk_paned_set_end_child (paned, child: g_value_get_object (value));
982 break;
983 default:
984 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
985 break;
986 }
987}
988
989static void
990gtk_paned_get_property (GObject *object,
991 guint prop_id,
992 GValue *value,
993 GParamSpec *pspec)
994{
995 GtkPaned *paned = GTK_PANED (object);
996
997 switch (prop_id)
998 {
999 case PROP_ORIENTATION:
1000 g_value_set_enum (value, v_enum: paned->orientation);
1001 break;
1002 case PROP_POSITION:
1003 g_value_set_int (value, v_int: paned->start_child_size);
1004 break;
1005 case PROP_POSITION_SET:
1006 g_value_set_boolean (value, v_boolean: paned->position_set);
1007 break;
1008 case PROP_MIN_POSITION:
1009 g_value_set_int (value, v_int: paned->min_position);
1010 break;
1011 case PROP_MAX_POSITION:
1012 g_value_set_int (value, v_int: paned->max_position);
1013 break;
1014 case PROP_WIDE_HANDLE:
1015 g_value_set_boolean (value, v_boolean: gtk_paned_get_wide_handle (paned));
1016 break;
1017 case PROP_RESIZE_START_CHILD:
1018 g_value_set_boolean (value, v_boolean: paned->resize_start_child);
1019 break;
1020 case PROP_RESIZE_END_CHILD:
1021 g_value_set_boolean (value, v_boolean: paned->resize_end_child);
1022 break;
1023 case PROP_SHRINK_START_CHILD:
1024 g_value_set_boolean (value, v_boolean: paned->shrink_start_child);
1025 break;
1026 case PROP_SHRINK_END_CHILD:
1027 g_value_set_boolean (value, v_boolean: paned->shrink_end_child);
1028 break;
1029 case PROP_START_CHILD:
1030 g_value_set_object (value, v_object: gtk_paned_get_start_child (paned));
1031 break;
1032 case PROP_END_CHILD:
1033 g_value_set_object (value, v_object: gtk_paned_get_end_child (paned));
1034 break;
1035 default:
1036 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1037 break;
1038 }
1039}
1040
1041static void
1042gtk_paned_dispose (GObject *object)
1043{
1044 GtkPaned *paned = GTK_PANED (object);
1045
1046 gtk_paned_set_saved_focus (paned, NULL);
1047 gtk_paned_set_first_paned (paned, NULL);
1048
1049 g_clear_pointer (&paned->start_child, gtk_widget_unparent);
1050 g_clear_pointer (&paned->end_child, gtk_widget_unparent);
1051 g_clear_pointer (&paned->handle_widget, gtk_widget_unparent);
1052
1053 G_OBJECT_CLASS (gtk_paned_parent_class)->dispose (object);
1054}
1055
1056static void
1057gtk_paned_compute_position (GtkPaned *paned,
1058 int allocation,
1059 int start_child_req,
1060 int end_child_req,
1061 int *min_pos,
1062 int *max_pos,
1063 int *out_pos)
1064{
1065 int min, max, pos;
1066
1067 min = paned->shrink_start_child ? 0 : start_child_req;
1068
1069 max = allocation;
1070 if (!paned->shrink_end_child)
1071 max = MAX (1, max - end_child_req);
1072 max = MAX (min, max);
1073
1074 if (!paned->position_set)
1075 {
1076 if (paned->resize_start_child && !paned->resize_end_child)
1077 pos = MAX (0, allocation - end_child_req);
1078 else if (!paned->resize_start_child && paned->resize_end_child)
1079 pos = start_child_req;
1080 else if (start_child_req + end_child_req != 0)
1081 pos = allocation * ((double)start_child_req / (start_child_req + end_child_req)) + 0.5;
1082 else
1083 pos = allocation * 0.5 + 0.5;
1084 }
1085 else
1086 {
1087 /* If the position was set before the initial allocation.
1088 * (paned->last_allocation <= 0) just clamp it and leave it.
1089 */
1090 if (paned->last_allocation > 0)
1091 {
1092 if (paned->resize_start_child && !paned->resize_end_child)
1093 pos = paned->start_child_size + allocation - paned->last_allocation;
1094 else if (!(!paned->resize_start_child && paned->resize_end_child))
1095 pos = allocation * ((double) paned->start_child_size / (paned->last_allocation)) + 0.5;
1096 else
1097 pos = paned->start_child_size;
1098 }
1099 else
1100 pos = paned->start_child_size;
1101 }
1102
1103 pos = CLAMP (pos, min, max);
1104
1105 if (min_pos)
1106 *min_pos = min;
1107 if (max_pos)
1108 *max_pos = max;
1109 if (out_pos)
1110 *out_pos = pos;
1111}
1112
1113static void
1114gtk_paned_get_preferred_size_for_orientation (GtkWidget *widget,
1115 int size,
1116 int *minimum,
1117 int *natural)
1118{
1119 GtkPaned *paned = GTK_PANED (widget);
1120 int child_min, child_nat;
1121
1122 *minimum = *natural = 0;
1123
1124 if (paned->start_child && gtk_widget_get_visible (widget: paned->start_child))
1125 {
1126 gtk_widget_measure (widget: paned->start_child, orientation: paned->orientation, for_size: size, minimum: &child_min, natural: &child_nat, NULL, NULL);
1127 if (paned->shrink_start_child)
1128 *minimum = 0;
1129 else
1130 *minimum = child_min;
1131 *natural = child_nat;
1132 }
1133
1134 if (paned->end_child && gtk_widget_get_visible (widget: paned->end_child))
1135 {
1136 gtk_widget_measure (widget: paned->end_child, orientation: paned->orientation, for_size: size, minimum: &child_min, natural: &child_nat, NULL, NULL);
1137
1138 if (!paned->shrink_end_child)
1139 *minimum += child_min;
1140 *natural += child_nat;
1141 }
1142
1143 if (paned->start_child && gtk_widget_get_visible (widget: paned->start_child) &&
1144 paned->end_child && gtk_widget_get_visible (widget: paned->end_child))
1145 {
1146 int handle_size;
1147
1148 gtk_widget_measure (widget: paned->handle_widget,
1149 orientation: paned->orientation,
1150 for_size: -1,
1151 NULL, natural: &handle_size,
1152 NULL, NULL);
1153
1154 *minimum += handle_size;
1155 *natural += handle_size;
1156 }
1157}
1158
1159static void
1160gtk_paned_get_preferred_size_for_opposite_orientation (GtkWidget *widget,
1161 int size,
1162 int *minimum,
1163 int *natural)
1164{
1165 GtkPaned *paned = GTK_PANED (widget);
1166 int for_start_child, for_end_child, for_handle;
1167 int child_min, child_nat;
1168
1169 if (size > -1 &&
1170 paned->start_child && gtk_widget_get_visible (widget: paned->start_child) &&
1171 paned->end_child && gtk_widget_get_visible (widget: paned->end_child))
1172 {
1173 int start_child_req, end_child_req;
1174
1175 gtk_widget_measure (widget: paned->handle_widget,
1176 orientation: paned->orientation,
1177 for_size: -1,
1178 NULL, natural: &for_handle,
1179 NULL, NULL);
1180
1181 gtk_widget_measure (widget: paned->start_child, orientation: paned->orientation, for_size: -1, minimum: &start_child_req, NULL, NULL, NULL);
1182 gtk_widget_measure (widget: paned->end_child, orientation: paned->orientation, for_size: -1, minimum: &end_child_req, NULL, NULL, NULL);
1183
1184 gtk_paned_compute_position (paned,
1185 allocation: size - for_handle, start_child_req, end_child_req,
1186 NULL, NULL, out_pos: &for_start_child);
1187
1188 for_end_child = size - for_start_child - for_handle;
1189
1190 if (paned->shrink_start_child)
1191 for_start_child = MAX (start_child_req, for_start_child);
1192 if (paned->shrink_end_child)
1193 for_end_child = MAX (end_child_req, for_end_child);
1194 }
1195 else
1196 {
1197 for_start_child = size;
1198 for_end_child = size;
1199 for_handle = -1;
1200 }
1201
1202 *minimum = *natural = 0;
1203
1204 if (paned->start_child && gtk_widget_get_visible (widget: paned->start_child))
1205 {
1206 gtk_widget_measure (widget: paned->start_child,
1207 OPPOSITE_ORIENTATION (paned->orientation),
1208 for_size: for_start_child,
1209 minimum: &child_min, natural: &child_nat,
1210 NULL, NULL);
1211
1212 *minimum = child_min;
1213 *natural = child_nat;
1214 }
1215
1216 if (paned->end_child && gtk_widget_get_visible (widget: paned->end_child))
1217 {
1218 gtk_widget_measure (widget: paned->end_child,
1219 OPPOSITE_ORIENTATION (paned->orientation),
1220 for_size: for_end_child,
1221 minimum: &child_min, natural: &child_nat,
1222 NULL, NULL);
1223
1224 *minimum = MAX (*minimum, child_min);
1225 *natural = MAX (*natural, child_nat);
1226 }
1227
1228 if (paned->start_child && gtk_widget_get_visible (widget: paned->start_child) &&
1229 paned->end_child && gtk_widget_get_visible (widget: paned->end_child))
1230 {
1231 gtk_widget_measure (widget: paned->handle_widget,
1232 OPPOSITE_ORIENTATION (paned->orientation),
1233 for_size: for_handle,
1234 minimum: &child_min, natural: &child_nat,
1235 NULL, NULL);
1236
1237 *minimum = MAX (*minimum, child_min);
1238 *natural = MAX (*natural, child_nat);
1239 }
1240}
1241
1242static void
1243gtk_paned_measure (GtkWidget *widget,
1244 GtkOrientation orientation,
1245 int for_size,
1246 int *minimum,
1247 int *natural,
1248 int *minimum_baseline,
1249 int *natural_baseline)
1250{
1251 GtkPaned *paned = GTK_PANED (widget);
1252
1253 if (orientation == paned->orientation)
1254 gtk_paned_get_preferred_size_for_orientation (widget, size: for_size, minimum, natural);
1255 else
1256 gtk_paned_get_preferred_size_for_opposite_orientation (widget, size: for_size, minimum, natural);
1257}
1258
1259static void
1260flip_child (int width,
1261 GtkAllocation *child_pos)
1262{
1263 child_pos->x = width - child_pos->x - child_pos->width;
1264}
1265
1266static void
1267gtk_paned_size_allocate (GtkWidget *widget,
1268 int width,
1269 int height,
1270 int baseline)
1271{
1272 GtkPaned *paned = GTK_PANED (widget);
1273
1274 if (paned->start_child && gtk_widget_get_visible (widget: paned->start_child) &&
1275 paned->end_child && gtk_widget_get_visible (widget: paned->end_child))
1276 {
1277 GtkAllocation start_child_allocation;
1278 GtkAllocation end_child_allocation;
1279 GtkAllocation handle_allocation;
1280 int handle_size;
1281
1282 gtk_widget_measure (widget: paned->handle_widget,
1283 orientation: paned->orientation,
1284 for_size: -1,
1285 NULL, natural: &handle_size,
1286 NULL, NULL);
1287
1288 if (paned->orientation == GTK_ORIENTATION_HORIZONTAL)
1289 {
1290 int start_child_width, end_child_width;
1291
1292 gtk_widget_measure (widget: paned->start_child, orientation: GTK_ORIENTATION_HORIZONTAL,
1293 for_size: height,
1294 minimum: &start_child_width, NULL, NULL, NULL);
1295 gtk_widget_measure (widget: paned->end_child, orientation: GTK_ORIENTATION_HORIZONTAL,
1296 for_size: height,
1297 minimum: &end_child_width, NULL, NULL, NULL);
1298
1299 gtk_paned_calc_position (paned,
1300 MAX (1, width - handle_size),
1301 start_child_req: start_child_width,
1302 end_child_req: end_child_width);
1303
1304 handle_allocation = (GdkRectangle){
1305 paned->start_child_size,
1306 0,
1307 handle_size,
1308 height
1309 };
1310
1311 start_child_allocation.height = end_child_allocation.height = height;
1312 start_child_allocation.width = MAX (1, paned->start_child_size);
1313 start_child_allocation.x = 0;
1314 start_child_allocation.y = end_child_allocation.y = 0;
1315
1316 end_child_allocation.x = start_child_allocation.x + paned->start_child_size + handle_size;
1317 end_child_allocation.width = MAX (1, width - paned->start_child_size - handle_size);
1318
1319 if (gtk_widget_get_direction (GTK_WIDGET (widget)) == GTK_TEXT_DIR_RTL)
1320 {
1321 flip_child (width, child_pos: &(end_child_allocation));
1322 flip_child (width, child_pos: &(start_child_allocation));
1323 flip_child (width, child_pos: &(handle_allocation));
1324 }
1325
1326 if (start_child_width > start_child_allocation.width)
1327 {
1328 if (gtk_widget_get_direction (GTK_WIDGET (widget)) == GTK_TEXT_DIR_LTR)
1329 start_child_allocation.x -= start_child_width - start_child_allocation.width;
1330 start_child_allocation.width = start_child_width;
1331 }
1332
1333 if (end_child_width > end_child_allocation.width)
1334 {
1335 if (gtk_widget_get_direction (GTK_WIDGET (widget)) == GTK_TEXT_DIR_RTL)
1336 end_child_allocation.x -= end_child_width - end_child_allocation.width;
1337 end_child_allocation.width = end_child_width;
1338 }
1339 }
1340 else
1341 {
1342 int start_child_height, end_child_height;
1343
1344 gtk_widget_measure (widget: paned->start_child, orientation: GTK_ORIENTATION_VERTICAL,
1345 for_size: width,
1346 minimum: &start_child_height, NULL, NULL, NULL);
1347 gtk_widget_measure (widget: paned->end_child, orientation: GTK_ORIENTATION_VERTICAL,
1348 for_size: width,
1349 minimum: &end_child_height, NULL, NULL, NULL);
1350
1351 gtk_paned_calc_position (paned,
1352 MAX (1, height - handle_size),
1353 start_child_req: start_child_height,
1354 end_child_req: end_child_height);
1355
1356 handle_allocation = (GdkRectangle){
1357 0,
1358 paned->start_child_size,
1359 width,
1360 handle_size,
1361 };
1362
1363 start_child_allocation.width = end_child_allocation.width = width;
1364 start_child_allocation.height = MAX (1, paned->start_child_size);
1365 start_child_allocation.x = end_child_allocation.x = 0;
1366 start_child_allocation.y = 0;
1367
1368 end_child_allocation.y = start_child_allocation.y + paned->start_child_size + handle_size;
1369 end_child_allocation.height = MAX (1, height - end_child_allocation.y);
1370
1371 if (start_child_height > start_child_allocation.height)
1372 {
1373 start_child_allocation.y -= start_child_height - start_child_allocation.height;
1374 start_child_allocation.height = start_child_height;
1375 }
1376
1377 if (end_child_height > end_child_allocation.height)
1378 end_child_allocation.height = end_child_height;
1379 }
1380
1381 gtk_widget_set_child_visible (widget: paned->handle_widget, TRUE);
1382
1383 gtk_widget_size_allocate (widget: paned->handle_widget, allocation: &handle_allocation, baseline: -1);
1384 gtk_widget_size_allocate (widget: paned->start_child, allocation: &start_child_allocation, baseline: -1);
1385 gtk_widget_size_allocate (widget: paned->end_child, allocation: &end_child_allocation, baseline: -1);
1386 }
1387 else
1388 {
1389 if (paned->start_child && gtk_widget_get_visible (widget: paned->start_child))
1390 {
1391 gtk_widget_set_child_visible (widget: paned->start_child, TRUE);
1392
1393 gtk_widget_size_allocate (widget: paned->start_child,
1394 allocation: &(GtkAllocation) {0, 0, width, height}, baseline: -1);
1395 }
1396 else if (paned->end_child && gtk_widget_get_visible (widget: paned->end_child))
1397 {
1398 gtk_widget_set_child_visible (widget: paned->end_child, TRUE);
1399
1400 gtk_widget_size_allocate (widget: paned->end_child,
1401 allocation: &(GtkAllocation) {0, 0, width, height}, baseline: -1);
1402
1403 }
1404
1405 gtk_widget_set_child_visible (widget: paned->handle_widget, FALSE);
1406 }
1407
1408 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: paned),
1409 first_property: GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, 0.0,
1410 GTK_ACCESSIBLE_PROPERTY_VALUE_MAX,
1411 (double) (paned->orientation == GTK_ORIENTATION_HORIZONTAL ? width : height),
1412 GTK_ACCESSIBLE_PROPERTY_VALUE_NOW,
1413 (double) paned->start_child_size,
1414 -1);
1415}
1416
1417
1418static void
1419gtk_paned_unrealize (GtkWidget *widget)
1420{
1421 GtkPaned *paned = GTK_PANED (widget);
1422
1423 gtk_paned_set_last_start_child_focus (paned, NULL);
1424 gtk_paned_set_last_end_child_focus (paned, NULL);
1425 gtk_paned_set_saved_focus (paned, NULL);
1426 gtk_paned_set_first_paned (paned, NULL);
1427
1428 GTK_WIDGET_CLASS (gtk_paned_parent_class)->unrealize (widget);
1429}
1430
1431static void
1432connect_drag_gesture_signals (GtkPaned *paned,
1433 GtkGesture *gesture)
1434{
1435 g_signal_connect (gesture, "drag-begin",
1436 G_CALLBACK (gesture_drag_begin_cb), paned);
1437 g_signal_connect (gesture, "drag-update",
1438 G_CALLBACK (gesture_drag_update_cb), paned);
1439 g_signal_connect (gesture, "drag-end",
1440 G_CALLBACK (gesture_drag_end_cb), paned);
1441}
1442
1443static void
1444gtk_paned_init (GtkPaned *paned)
1445{
1446 GtkGesture *gesture;
1447
1448 gtk_widget_set_focusable (GTK_WIDGET (paned), TRUE);
1449 gtk_widget_set_overflow (GTK_WIDGET (paned), overflow: GTK_OVERFLOW_HIDDEN);
1450
1451 paned->orientation = GTK_ORIENTATION_HORIZONTAL;
1452
1453 paned->start_child = NULL;
1454 paned->end_child = NULL;
1455
1456 paned->position_set = FALSE;
1457 paned->last_allocation = -1;
1458
1459 paned->last_start_child_focus = NULL;
1460 paned->last_end_child_focus = NULL;
1461 paned->in_recursion = FALSE;
1462 paned->original_position = -1;
1463 paned->max_position = G_MAXINT;
1464 paned->resize_start_child = TRUE;
1465 paned->resize_end_child = TRUE;
1466 paned->shrink_start_child = TRUE;
1467 paned->shrink_end_child = TRUE;
1468
1469 gtk_widget_update_orientation (GTK_WIDGET (paned), orientation: paned->orientation);
1470
1471 /* Touch gesture */
1472 gesture = gtk_gesture_pan_new (orientation: GTK_ORIENTATION_HORIZONTAL);
1473 connect_drag_gesture_signals (paned, gesture);
1474 gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), TRUE);
1475 gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
1476 phase: GTK_PHASE_CAPTURE);
1477 gtk_widget_add_controller (GTK_WIDGET (paned), GTK_EVENT_CONTROLLER (gesture));
1478 paned->pan_gesture = gesture;
1479
1480 /* Pointer gesture */
1481 gesture = gtk_gesture_drag_new ();
1482 gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
1483 phase: GTK_PHASE_CAPTURE);
1484 connect_drag_gesture_signals (paned, gesture);
1485 gtk_widget_add_controller (GTK_WIDGET (paned), GTK_EVENT_CONTROLLER (gesture));
1486 paned->drag_gesture = gesture;
1487
1488 paned->handle_widget = gtk_paned_handle_new ();
1489 gtk_widget_set_parent (widget: paned->handle_widget, GTK_WIDGET (paned));
1490 gtk_widget_set_cursor_from_name (widget: paned->handle_widget, name: "col-resize");
1491}
1492
1493static gboolean
1494is_rtl (GtkPaned *paned)
1495{
1496 return paned->orientation == GTK_ORIENTATION_HORIZONTAL &&
1497 gtk_widget_get_direction (GTK_WIDGET (paned)) == GTK_TEXT_DIR_RTL;
1498}
1499
1500static void
1501update_drag (GtkPaned *paned,
1502 int xpos,
1503 int ypos)
1504{
1505 int pos;
1506 int handle_size;
1507 int size;
1508
1509 if (paned->orientation == GTK_ORIENTATION_HORIZONTAL)
1510 pos = xpos;
1511 else
1512 pos = ypos;
1513
1514 pos -= paned->drag_pos;
1515
1516 if (is_rtl (paned))
1517 {
1518 gtk_widget_measure (widget: paned->handle_widget,
1519 orientation: GTK_ORIENTATION_HORIZONTAL,
1520 for_size: -1,
1521 NULL, natural: &handle_size,
1522 NULL, NULL);
1523
1524 size = gtk_widget_get_width (GTK_WIDGET (paned)) - pos - handle_size;
1525 }
1526 else
1527 {
1528 size = pos;
1529 }
1530
1531 size = CLAMP (size, paned->min_position, paned->max_position);
1532
1533 if (size != paned->start_child_size)
1534 gtk_paned_set_position (paned, position: size);
1535}
1536
1537static void
1538gtk_paned_css_changed (GtkWidget *widget,
1539 GtkCssStyleChange *change)
1540{
1541 GTK_WIDGET_CLASS (gtk_paned_parent_class)->css_changed (widget, change);
1542
1543 if (change == NULL ||
1544 gtk_css_style_change_affects (change, affects: GTK_CSS_AFFECTS_ICON_SIZE))
1545 {
1546 gtk_widget_queue_resize (widget);
1547 }
1548 else if (gtk_css_style_change_affects (change, affects: GTK_CSS_AFFECTS_ICON_TEXTURE |
1549 GTK_CSS_AFFECTS_ICON_REDRAW))
1550 {
1551 gtk_widget_queue_draw (widget);
1552 }
1553}
1554
1555/**
1556 * gtk_paned_new:
1557 * @orientation: the paned’s orientation.
1558 *
1559 * Creates a new `GtkPaned` widget.
1560 *
1561 * Returns: the newly created paned widget
1562 */
1563GtkWidget *
1564gtk_paned_new (GtkOrientation orientation)
1565{
1566 return g_object_new (GTK_TYPE_PANED,
1567 first_property_name: "orientation", orientation,
1568 NULL);
1569}
1570
1571/**
1572 * gtk_paned_set_start_child: (attributes org.gtk.Method.set_property=start-child)
1573 * @paned: a `GtkPaned`
1574 * @child: (nullable): the widget to add
1575 *
1576 * Sets the start child of @paned to @child.
1577 *
1578 * If @child is `NULL`, the existing child will be removed.
1579 */
1580void
1581gtk_paned_set_start_child (GtkPaned *paned,
1582 GtkWidget *child)
1583{
1584 g_return_if_fail (GTK_IS_PANED (paned));
1585 g_return_if_fail (child == NULL || GTK_IS_WIDGET (child));
1586
1587 g_clear_pointer (&paned->start_child, gtk_widget_unparent);
1588
1589 if (child)
1590 {
1591 paned->start_child = child;
1592 gtk_widget_insert_before (widget: child, GTK_WIDGET (paned), next_sibling: paned->handle_widget);
1593 }
1594
1595 g_object_notify (G_OBJECT (paned), property_name: "start-child");
1596}
1597
1598/**
1599 * gtk_paned_get_start_child: (attributes org.gtk.Method.get_property=start-child)
1600 * @paned: a `GtkPaned`
1601 *
1602 * Retrieves the start child of the given `GtkPaned`.
1603 *
1604 * Returns: (transfer none) (nullable): the start child widget
1605 */
1606GtkWidget *
1607gtk_paned_get_start_child (GtkPaned *paned)
1608{
1609 g_return_val_if_fail (GTK_IS_PANED (paned), NULL);
1610
1611 return paned->start_child;
1612}
1613
1614/**
1615 * gtk_paned_set_resize_start_child: (attributes org.gtk.Method.set_property=resize-start-child)
1616 * @paned: a `GtkPaned`
1617 * @resize: true to let the start child be resized
1618 *
1619 * Sets whether the [property@Gtk.Paned:start-child] can be resized.
1620 */
1621void
1622gtk_paned_set_resize_start_child (GtkPaned *paned,
1623 gboolean resize)
1624{
1625 g_return_if_fail (GTK_IS_PANED (paned));
1626
1627 if (paned->resize_start_child == resize)
1628 return;
1629
1630 paned->resize_start_child = resize;
1631
1632 g_object_notify (G_OBJECT (paned), property_name: "resize-start-child");
1633}
1634
1635/**
1636 * gtk_paned_get_resize_start_child: (attributes org.gtk.Method.get_property=resize-start-child)
1637 * @paned: a `GtkPaned`
1638 *
1639 * Returns whether the [property@Gtk.Paned:start-child] can be resized.
1640 *
1641 * Returns: true if the start child is resizable
1642 */
1643gboolean
1644gtk_paned_get_resize_start_child (GtkPaned *paned)
1645{
1646 g_return_val_if_fail (GTK_IS_PANED (paned), FALSE);
1647
1648 return paned->resize_start_child;
1649}
1650
1651/**
1652 * gtk_paned_set_shrink_start_child: (attributes org.gtk.Method.set_property=shrink-start-child)
1653 * @paned: a `GtkPaned`
1654 * @resize: true to let the start child be shrunk
1655 *
1656 * Sets whether the [property@Gtk.Paned:start-child] can shrink.
1657 */
1658void
1659gtk_paned_set_shrink_start_child (GtkPaned *paned,
1660 gboolean shrink)
1661{
1662 g_return_if_fail (GTK_IS_PANED (paned));
1663
1664 if (paned->shrink_start_child == shrink)
1665 return;
1666
1667 paned->shrink_start_child = shrink;
1668
1669 g_object_notify (G_OBJECT (paned), property_name: "shrink-start-child");
1670}
1671
1672/**
1673 * gtk_paned_get_shrink_start_child: (attributes org.gtk.Method.get_property=shrink-start-child)
1674 * @paned: a `GtkPaned`
1675 *
1676 * Returns whether the [property@Gtk.Paned:start-child] can shrink.
1677 *
1678 * Returns: true if the start child is shrinkable
1679 */
1680gboolean
1681gtk_paned_get_shrink_start_child (GtkPaned *paned)
1682{
1683 g_return_val_if_fail (GTK_IS_PANED (paned), FALSE);
1684
1685 return paned->shrink_start_child;
1686}
1687
1688/**
1689 * gtk_paned_set_end_child: (attributes org.gtk.Method.set_property=end-child)
1690 * @paned: a `GtkPaned`
1691 * @child: (nullable): the widget to add
1692 *
1693 * Sets the end child of @paned to @child.
1694 *
1695 * If @child is `NULL`, the existing child will be removed.
1696 */
1697void
1698gtk_paned_set_end_child (GtkPaned *paned,
1699 GtkWidget *child)
1700{
1701 g_return_if_fail (GTK_IS_PANED (paned));
1702 g_return_if_fail (child == NULL || GTK_IS_WIDGET (child));
1703
1704 g_clear_pointer (&paned->end_child, gtk_widget_unparent);
1705
1706 if (child)
1707 {
1708 paned->end_child = child;
1709 gtk_widget_insert_after (widget: child, GTK_WIDGET (paned), previous_sibling: paned->handle_widget);
1710 }
1711
1712 g_object_notify (G_OBJECT (paned), property_name: "end-child");
1713}
1714
1715/**
1716 * gtk_paned_get_end_child: (attributes org.gtk.Method.get_property=end-child)
1717 * @paned: a `GtkPaned`
1718 *
1719 * Retrieves the end child of the given `GtkPaned`.
1720 *
1721 * Returns: (transfer none) (nullable): the end child widget
1722 */
1723GtkWidget *
1724gtk_paned_get_end_child (GtkPaned *paned)
1725{
1726 g_return_val_if_fail (GTK_IS_PANED (paned), NULL);
1727
1728 return paned->end_child;
1729}
1730
1731/**
1732 * gtk_paned_set_resize_end_child: (attributes org.gtk.Method.set_property=resize-end-child)
1733 * @paned: a `GtkPaned`
1734 * @resize: true to let the end child be resized
1735 *
1736 * Sets whether the [property@Gtk.Paned:end-child] can be resized.
1737 */
1738void
1739gtk_paned_set_resize_end_child (GtkPaned *paned,
1740 gboolean resize)
1741{
1742 g_return_if_fail (GTK_IS_PANED (paned));
1743
1744 if (paned->resize_end_child == resize)
1745 return;
1746
1747 paned->resize_end_child = resize;
1748
1749 g_object_notify (G_OBJECT (paned), property_name: "resize-end-child");
1750}
1751
1752/**
1753 * gtk_paned_get_resize_end_child: (attributes org.gtk.Method.get_property=resize-end-child)
1754 * @paned: a `GtkPaned`
1755 *
1756 * Returns whether the [property@Gtk.Paned:end-child] can be resized.
1757 *
1758 * Returns: true if the end child is resizable
1759 */
1760gboolean
1761gtk_paned_get_resize_end_child (GtkPaned *paned)
1762{
1763 g_return_val_if_fail (GTK_IS_PANED (paned), FALSE);
1764
1765 return paned->resize_end_child;
1766}
1767
1768/**
1769 * gtk_paned_set_shrink_end_child: (attributes org.gtk.Method.set_property=shrink-end-child)
1770 * @paned: a `GtkPaned`
1771 * @resize: true to let the end child be shrunk
1772 *
1773 * Sets whether the [property@Gtk.Paned:end-child] can shrink.
1774 */
1775void
1776gtk_paned_set_shrink_end_child (GtkPaned *paned,
1777 gboolean shrink)
1778{
1779 g_return_if_fail (GTK_IS_PANED (paned));
1780
1781 if (paned->shrink_end_child == shrink)
1782 return;
1783
1784 paned->shrink_end_child = shrink;
1785
1786 g_object_notify (G_OBJECT (paned), property_name: "shrink-end-child");
1787}
1788
1789/**
1790 * gtk_paned_get_shrink_end_child: (attributes org.gtk.Method.get_property=shrink-end-child)
1791 * @paned: a `GtkPaned`
1792 *
1793 * Returns whether the [property@Gtk.Paned:end-child] can shrink.
1794 *
1795 * Returns: true if the end child is shrinkable
1796 */
1797gboolean
1798gtk_paned_get_shrink_end_child (GtkPaned *paned)
1799{
1800 g_return_val_if_fail (GTK_IS_PANED (paned), FALSE);
1801
1802 return paned->shrink_end_child;
1803}
1804
1805/**
1806 * gtk_paned_get_position: (attributes org.gtk.Method.get_property=position)
1807 * @paned: a `GtkPaned` widget
1808 *
1809 * Obtains the position of the divider between the two panes.
1810 *
1811 * Returns: the position of the divider, in pixels
1812 **/
1813int
1814gtk_paned_get_position (GtkPaned *paned)
1815{
1816 g_return_val_if_fail (GTK_IS_PANED (paned), 0);
1817
1818 return paned->start_child_size;
1819}
1820
1821/**
1822 * gtk_paned_set_position: (attributes org.gtk.Method.set_property=position)
1823 * @paned: a `GtkPaned` widget
1824 * @position: pixel position of divider, a negative value means that the position
1825 * is unset
1826 *
1827 * Sets the position of the divider between the two panes.
1828 */
1829void
1830gtk_paned_set_position (GtkPaned *paned,
1831 int position)
1832{
1833 g_return_if_fail (GTK_IS_PANED (paned));
1834
1835 g_object_freeze_notify (G_OBJECT (paned));
1836
1837 if (position >= 0)
1838 {
1839 /* We don't clamp here - the assumption is that
1840 * if the total allocation changes at the same time
1841 * as the position, the position set is with reference
1842 * to the new total size. If only the position changes,
1843 * then clamping will occur in gtk_paned_calc_position()
1844 */
1845
1846 if (!paned->position_set)
1847 g_object_notify_by_pspec (G_OBJECT (paned), pspec: paned_props[PROP_POSITION_SET]);
1848
1849 if (paned->start_child_size != position)
1850 {
1851 g_object_notify_by_pspec (G_OBJECT (paned), pspec: paned_props[PROP_POSITION]);
1852 gtk_widget_queue_allocate (GTK_WIDGET (paned));
1853 }
1854
1855 paned->start_child_size = position;
1856 paned->position_set = TRUE;
1857 }
1858 else
1859 {
1860 if (paned->position_set)
1861 g_object_notify_by_pspec (G_OBJECT (paned), pspec: paned_props[PROP_POSITION_SET]);
1862
1863 paned->position_set = FALSE;
1864 }
1865
1866 g_object_thaw_notify (G_OBJECT (paned));
1867
1868#ifdef G_OS_WIN32
1869 /* Hacky work-around for bug #144269 */
1870 if (paned->end_child != NULL)
1871 {
1872 gtk_widget_queue_draw (paned->end_child);
1873 }
1874#endif
1875}
1876
1877static void
1878gtk_paned_calc_position (GtkPaned *paned,
1879 int allocation,
1880 int start_child_req,
1881 int end_child_req)
1882{
1883 int old_position;
1884 int old_min_position;
1885 int old_max_position;
1886
1887 old_position = paned->start_child_size;
1888 old_min_position = paned->min_position;
1889 old_max_position = paned->max_position;
1890
1891 gtk_paned_compute_position (paned,
1892 allocation, start_child_req, end_child_req,
1893 min_pos: &paned->min_position, max_pos: &paned->max_position,
1894 out_pos: &paned->start_child_size);
1895
1896 gtk_widget_set_child_visible (widget: paned->start_child, child_visible: paned->start_child_size != 0);
1897 gtk_widget_set_child_visible (widget: paned->end_child, child_visible: paned->start_child_size != allocation);
1898
1899 g_object_freeze_notify (G_OBJECT (paned));
1900 if (paned->start_child_size != old_position)
1901 g_object_notify_by_pspec (G_OBJECT (paned), pspec: paned_props[PROP_POSITION]);
1902 if (paned->min_position != old_min_position)
1903 g_object_notify_by_pspec (G_OBJECT (paned), pspec: paned_props[PROP_MIN_POSITION]);
1904 if (paned->max_position != old_max_position)
1905 g_object_notify_by_pspec (G_OBJECT (paned), pspec: paned_props[PROP_MAX_POSITION]);
1906 g_object_thaw_notify (G_OBJECT (paned));
1907
1908 paned->last_allocation = allocation;
1909}
1910
1911static void
1912gtk_paned_set_saved_focus (GtkPaned *paned, GtkWidget *widget)
1913{
1914 if (paned->saved_focus)
1915 g_object_remove_weak_pointer (G_OBJECT (paned->saved_focus),
1916 weak_pointer_location: (gpointer *)&(paned->saved_focus));
1917
1918 paned->saved_focus = widget;
1919
1920 if (paned->saved_focus)
1921 g_object_add_weak_pointer (G_OBJECT (paned->saved_focus),
1922 weak_pointer_location: (gpointer *)&(paned->saved_focus));
1923}
1924
1925static void
1926gtk_paned_set_first_paned (GtkPaned *paned, GtkPaned *first_paned)
1927{
1928 if (paned->first_paned)
1929 g_object_remove_weak_pointer (G_OBJECT (paned->first_paned),
1930 weak_pointer_location: (gpointer *)&(paned->first_paned));
1931
1932 paned->first_paned = first_paned;
1933
1934 if (paned->first_paned)
1935 g_object_add_weak_pointer (G_OBJECT (paned->first_paned),
1936 weak_pointer_location: (gpointer *)&(paned->first_paned));
1937}
1938
1939static void
1940gtk_paned_set_last_start_child_focus (GtkPaned *paned, GtkWidget *widget)
1941{
1942 if (paned->last_start_child_focus)
1943 g_object_remove_weak_pointer (G_OBJECT (paned->last_start_child_focus),
1944 weak_pointer_location: (gpointer *)&(paned->last_start_child_focus));
1945
1946 paned->last_start_child_focus = widget;
1947
1948 if (paned->last_start_child_focus)
1949 g_object_add_weak_pointer (G_OBJECT (paned->last_start_child_focus),
1950 weak_pointer_location: (gpointer *)&(paned->last_start_child_focus));
1951}
1952
1953static void
1954gtk_paned_set_last_end_child_focus (GtkPaned *paned, GtkWidget *widget)
1955{
1956 if (paned->last_end_child_focus)
1957 g_object_remove_weak_pointer (G_OBJECT (paned->last_end_child_focus),
1958 weak_pointer_location: (gpointer *)&(paned->last_end_child_focus));
1959
1960 paned->last_end_child_focus = widget;
1961
1962 if (paned->last_end_child_focus)
1963 g_object_add_weak_pointer (G_OBJECT (paned->last_end_child_focus),
1964 weak_pointer_location: (gpointer *)&(paned->last_end_child_focus));
1965}
1966
1967static GtkWidget *
1968paned_get_focus_widget (GtkPaned *paned)
1969{
1970 GtkWidget *toplevel;
1971
1972 toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (paned)));
1973 if (GTK_IS_WINDOW (toplevel))
1974 return gtk_window_get_focus (GTK_WINDOW (toplevel));
1975
1976 return NULL;
1977}
1978
1979static void
1980gtk_paned_set_focus_child (GtkWidget *widget,
1981 GtkWidget *child)
1982{
1983 GtkPaned *paned = GTK_PANED (widget);
1984 GtkWidget *focus_child;
1985
1986 if (child == NULL)
1987 {
1988 GtkWidget *last_focus;
1989 GtkWidget *w;
1990
1991 last_focus = paned_get_focus_widget (paned);
1992
1993 if (last_focus)
1994 {
1995 /* If there is one or more paned widgets between us and the
1996 * focus widget, we want the topmost of those as last_focus
1997 */
1998 for (w = last_focus; w && w != GTK_WIDGET (paned); w = gtk_widget_get_parent (widget: w))
1999 if (GTK_IS_PANED (w))
2000 last_focus = w;
2001
2002 if (w == NULL)
2003 {
2004 g_warning ("Error finding last focus widget of GtkPaned %p, "
2005 "gtk_paned_set_focus_child was called on widget %p "
2006 "which is not child of %p.",
2007 widget, child, widget);
2008 return;
2009 }
2010
2011 focus_child = gtk_widget_get_focus_child (widget);
2012 if (focus_child == paned->start_child)
2013 gtk_paned_set_last_start_child_focus (paned, widget: last_focus);
2014 else if (focus_child == paned->end_child)
2015 gtk_paned_set_last_end_child_focus (paned, widget: last_focus);
2016 }
2017 }
2018
2019 GTK_WIDGET_CLASS (gtk_paned_parent_class)->set_focus_child (widget, child);
2020}
2021
2022static void
2023gtk_paned_get_cycle_chain (GtkPaned *paned,
2024 GtkDirectionType direction,
2025 GList **widgets)
2026{
2027 GtkWidget *ancestor = NULL;
2028 GtkWidget *focus_child;
2029 GtkWidget *parent;
2030 GtkWidget *widget = GTK_WIDGET (paned);
2031 GList *temp_list = NULL;
2032 GList *list;
2033
2034 if (paned->in_recursion)
2035 return;
2036
2037 g_assert (widgets != NULL);
2038
2039 if (paned->last_start_child_focus &&
2040 !gtk_widget_is_ancestor (widget: paned->last_start_child_focus, ancestor: widget))
2041 {
2042 gtk_paned_set_last_start_child_focus (paned, NULL);
2043 }
2044
2045 if (paned->last_end_child_focus &&
2046 !gtk_widget_is_ancestor (widget: paned->last_end_child_focus, ancestor: widget))
2047 {
2048 gtk_paned_set_last_end_child_focus (paned, NULL);
2049 }
2050
2051 parent = gtk_widget_get_parent (widget);
2052 if (parent)
2053 ancestor = gtk_widget_get_ancestor (widget: parent, GTK_TYPE_PANED);
2054
2055 /* The idea here is that temp_list is a list of widgets we want to cycle
2056 * to. The list is prioritized so that the first element is our first
2057 * choice, the next our second, and so on.
2058 *
2059 * We can't just use g_list_reverse(), because we want to try
2060 * paned->last_child?_focus before paned->child?, both when we
2061 * are going forward and backward.
2062 */
2063 focus_child = gtk_widget_get_focus_child (GTK_WIDGET (paned));
2064 if (direction == GTK_DIR_TAB_FORWARD)
2065 {
2066 if (focus_child == paned->start_child)
2067 {
2068 temp_list = g_list_append (list: temp_list, data: paned->last_end_child_focus);
2069 temp_list = g_list_append (list: temp_list, data: paned->end_child);
2070 temp_list = g_list_append (list: temp_list, data: ancestor);
2071 }
2072 else if (focus_child == paned->end_child)
2073 {
2074 temp_list = g_list_append (list: temp_list, data: ancestor);
2075 temp_list = g_list_append (list: temp_list, data: paned->last_start_child_focus);
2076 temp_list = g_list_append (list: temp_list, data: paned->start_child);
2077 }
2078 else
2079 {
2080 temp_list = g_list_append (list: temp_list, data: paned->last_start_child_focus);
2081 temp_list = g_list_append (list: temp_list, data: paned->start_child);
2082 temp_list = g_list_append (list: temp_list, data: paned->last_end_child_focus);
2083 temp_list = g_list_append (list: temp_list, data: paned->end_child);
2084 temp_list = g_list_append (list: temp_list, data: ancestor);
2085 }
2086 }
2087 else
2088 {
2089 if (focus_child == paned->start_child)
2090 {
2091 temp_list = g_list_append (list: temp_list, data: ancestor);
2092 temp_list = g_list_append (list: temp_list, data: paned->last_end_child_focus);
2093 temp_list = g_list_append (list: temp_list, data: paned->end_child);
2094 }
2095 else if (focus_child == paned->end_child)
2096 {
2097 temp_list = g_list_append (list: temp_list, data: paned->last_start_child_focus);
2098 temp_list = g_list_append (list: temp_list, data: paned->start_child);
2099 temp_list = g_list_append (list: temp_list, data: ancestor);
2100 }
2101 else
2102 {
2103 temp_list = g_list_append (list: temp_list, data: paned->last_end_child_focus);
2104 temp_list = g_list_append (list: temp_list, data: paned->end_child);
2105 temp_list = g_list_append (list: temp_list, data: paned->last_start_child_focus);
2106 temp_list = g_list_append (list: temp_list, data: paned->start_child);
2107 temp_list = g_list_append (list: temp_list, data: ancestor);
2108 }
2109 }
2110
2111 /* Walk the list and expand all the paned widgets. */
2112 for (list = temp_list; list != NULL; list = list->next)
2113 {
2114 widget = list->data;
2115
2116 if (widget)
2117 {
2118 if (GTK_IS_PANED (widget))
2119 {
2120 paned->in_recursion = TRUE;
2121 gtk_paned_get_cycle_chain (GTK_PANED (widget), direction, widgets);
2122 paned->in_recursion = FALSE;
2123 }
2124 else
2125 {
2126 *widgets = g_list_append (list: *widgets, data: widget);
2127 }
2128 }
2129 }
2130
2131 g_list_free (list: temp_list);
2132}
2133
2134static gboolean
2135gtk_paned_cycle_child_focus (GtkPaned *paned,
2136 gboolean reversed)
2137{
2138 GList *cycle_chain = NULL;
2139 GList *list;
2140
2141 GtkDirectionType direction = reversed? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD;
2142
2143 /* ignore f6 if the handle is focused */
2144 if (gtk_widget_is_focus (GTK_WIDGET (paned)))
2145 return TRUE;
2146
2147 /* we can't just let the event propagate up the hierarchy,
2148 * because the paned will want to cycle focus _unless_ an
2149 * ancestor paned handles the event
2150 */
2151 gtk_paned_get_cycle_chain (paned, direction, widgets: &cycle_chain);
2152
2153 for (list = cycle_chain; list != NULL; list = list->next)
2154 if (gtk_widget_child_focus (GTK_WIDGET (list->data), direction))
2155 break;
2156
2157 g_list_free (list: cycle_chain);
2158
2159 return TRUE;
2160}
2161
2162static void
2163get_child_panes (GtkWidget *widget,
2164 GList **panes)
2165{
2166 if (!widget || !gtk_widget_get_realized (widget))
2167 return;
2168
2169 if (GTK_IS_PANED (widget))
2170 {
2171 GtkPaned *paned = GTK_PANED (widget);
2172
2173 get_child_panes (widget: paned->start_child, panes);
2174 *panes = g_list_prepend (list: *panes, data: widget);
2175 get_child_panes (widget: paned->end_child, panes);
2176 }
2177 else
2178 {
2179 GtkWidget *child;
2180
2181 for (child = gtk_widget_get_first_child (widget);
2182 child != NULL;
2183 child = gtk_widget_get_next_sibling (widget: child))
2184 get_child_panes (widget: child, panes);
2185 }
2186}
2187
2188static GList *
2189get_all_panes (GtkPaned *paned)
2190{
2191 GtkPaned *topmost = NULL;
2192 GList *result = NULL;
2193 GtkWidget *w;
2194
2195 for (w = GTK_WIDGET (paned); w != NULL; w = gtk_widget_get_parent (widget: w))
2196 {
2197 if (GTK_IS_PANED (w))
2198 topmost = GTK_PANED (w);
2199 }
2200
2201 g_assert (topmost);
2202
2203 get_child_panes (GTK_WIDGET (topmost), panes: &result);
2204
2205 return g_list_reverse (list: result);
2206}
2207
2208static void
2209gtk_paned_find_neighbours (GtkPaned *paned,
2210 GtkPaned **next,
2211 GtkPaned **prev)
2212{
2213 GList *all_panes;
2214 GList *this_link;
2215
2216 all_panes = get_all_panes (paned);
2217 g_assert (all_panes);
2218
2219 this_link = g_list_find (list: all_panes, data: paned);
2220
2221 g_assert (this_link);
2222
2223 if (this_link->next)
2224 *next = this_link->next->data;
2225 else
2226 *next = all_panes->data;
2227
2228 if (this_link->prev)
2229 *prev = this_link->prev->data;
2230 else
2231 *prev = g_list_last (list: all_panes)->data;
2232
2233 g_list_free (list: all_panes);
2234}
2235
2236static gboolean
2237gtk_paned_move_handle (GtkPaned *paned,
2238 GtkScrollType scroll)
2239{
2240 if (gtk_widget_is_focus (GTK_WIDGET (paned)))
2241 {
2242 int old_position;
2243 int new_position;
2244 int increment;
2245
2246 enum {
2247 SINGLE_STEP_SIZE = 1,
2248 PAGE_STEP_SIZE = 75
2249 };
2250
2251 new_position = old_position = gtk_paned_get_position (paned);
2252 increment = 0;
2253
2254 switch (scroll)
2255 {
2256 case GTK_SCROLL_STEP_LEFT:
2257 case GTK_SCROLL_STEP_UP:
2258 case GTK_SCROLL_STEP_BACKWARD:
2259 increment = - SINGLE_STEP_SIZE;
2260 break;
2261
2262 case GTK_SCROLL_STEP_RIGHT:
2263 case GTK_SCROLL_STEP_DOWN:
2264 case GTK_SCROLL_STEP_FORWARD:
2265 increment = SINGLE_STEP_SIZE;
2266 break;
2267
2268 case GTK_SCROLL_PAGE_LEFT:
2269 case GTK_SCROLL_PAGE_UP:
2270 case GTK_SCROLL_PAGE_BACKWARD:
2271 increment = - PAGE_STEP_SIZE;
2272 break;
2273
2274 case GTK_SCROLL_PAGE_RIGHT:
2275 case GTK_SCROLL_PAGE_DOWN:
2276 case GTK_SCROLL_PAGE_FORWARD:
2277 increment = PAGE_STEP_SIZE;
2278 break;
2279
2280 case GTK_SCROLL_START:
2281 new_position = paned->min_position;
2282 break;
2283
2284 case GTK_SCROLL_END:
2285 new_position = paned->max_position;
2286 break;
2287
2288 case GTK_SCROLL_NONE:
2289 case GTK_SCROLL_JUMP:
2290 default:
2291 break;
2292 }
2293
2294 if (increment)
2295 {
2296 if (is_rtl (paned))
2297 increment = -increment;
2298
2299 new_position = old_position + increment;
2300 }
2301
2302 new_position = CLAMP (new_position, paned->min_position, paned->max_position);
2303
2304 if (old_position != new_position)
2305 gtk_paned_set_position (paned, position: new_position);
2306
2307 return TRUE;
2308 }
2309
2310 return FALSE;
2311}
2312
2313static void
2314gtk_paned_restore_focus (GtkPaned *paned)
2315{
2316 if (gtk_widget_is_focus (GTK_WIDGET (paned)))
2317 {
2318 if (paned->saved_focus &&
2319 gtk_widget_get_sensitive (widget: paned->saved_focus))
2320 {
2321 gtk_widget_grab_focus (widget: paned->saved_focus);
2322 }
2323 else
2324 {
2325 /* the saved focus is somehow not available for focusing,
2326 * try
2327 * 1) tabbing into the paned widget
2328 * if that didn't work,
2329 * 2) unset focus for the window if there is one
2330 */
2331
2332 if (!gtk_widget_child_focus (GTK_WIDGET (paned), direction: GTK_DIR_TAB_FORWARD))
2333 {
2334 GtkRoot *root = gtk_widget_get_root (GTK_WIDGET (paned));
2335 gtk_root_set_focus (self: root, NULL);
2336 }
2337 }
2338
2339 gtk_paned_set_saved_focus (paned, NULL);
2340 gtk_paned_set_first_paned (paned, NULL);
2341 }
2342}
2343
2344static gboolean
2345gtk_paned_accept_position (GtkPaned *paned)
2346{
2347 if (gtk_widget_is_focus (GTK_WIDGET (paned)))
2348 {
2349 paned->original_position = -1;
2350 gtk_paned_restore_focus (paned);
2351
2352 return TRUE;
2353 }
2354
2355 return FALSE;
2356}
2357
2358
2359static gboolean
2360gtk_paned_cancel_position (GtkPaned *paned)
2361{
2362 if (gtk_widget_is_focus (GTK_WIDGET (paned)))
2363 {
2364 if (paned->original_position != -1)
2365 {
2366 gtk_paned_set_position (paned, position: paned->original_position);
2367 paned->original_position = -1;
2368 }
2369
2370 gtk_paned_restore_focus (paned);
2371 return TRUE;
2372 }
2373
2374 return FALSE;
2375}
2376
2377static gboolean
2378gtk_paned_cycle_handle_focus (GtkPaned *paned,
2379 gboolean reversed)
2380{
2381 GtkPaned *next, *prev;
2382
2383 if (gtk_widget_is_focus (GTK_WIDGET (paned)))
2384 {
2385 GtkPaned *focus = NULL;
2386
2387 if (!paned->first_paned)
2388 {
2389 /* The first_pane has disappeared. As an ad-hoc solution,
2390 * we make the currently focused paned the first_paned. To the
2391 * user this will seem like the paned cycling has been reset.
2392 */
2393
2394 gtk_paned_set_first_paned (paned, first_paned: paned);
2395 }
2396
2397 gtk_paned_find_neighbours (paned, next: &next, prev: &prev);
2398
2399 if (reversed && prev &&
2400 prev != paned && paned != paned->first_paned)
2401 {
2402 focus = prev;
2403 }
2404 else if (!reversed && next &&
2405 next != paned && next != paned->first_paned)
2406 {
2407 focus = next;
2408 }
2409 else
2410 {
2411 gtk_paned_accept_position (paned);
2412 return TRUE;
2413 }
2414
2415 g_assert (focus);
2416
2417 gtk_paned_set_saved_focus (paned: focus, widget: paned->saved_focus);
2418 gtk_paned_set_first_paned (paned: focus, first_paned: paned->first_paned);
2419
2420 gtk_paned_set_saved_focus (paned, NULL);
2421 gtk_paned_set_first_paned (paned, NULL);
2422
2423 gtk_widget_grab_focus (GTK_WIDGET (focus));
2424
2425 if (!gtk_widget_is_focus (GTK_WIDGET (paned)))
2426 {
2427 paned->original_position = -1;
2428 paned->original_position = gtk_paned_get_position (paned: focus);
2429 }
2430 }
2431 else
2432 {
2433 GtkPaned *focus;
2434 GtkPaned *first;
2435 GtkWidget *focus_child;
2436
2437 gtk_paned_find_neighbours (paned, next: &next, prev: &prev);
2438 focus_child = gtk_widget_get_focus_child (GTK_WIDGET (paned));
2439
2440 if (focus_child == paned->start_child)
2441 {
2442 if (reversed)
2443 {
2444 focus = prev;
2445 first = paned;
2446 }
2447 else
2448 {
2449 focus = paned;
2450 first = paned;
2451 }
2452 }
2453 else if (focus_child == paned->end_child)
2454 {
2455 if (reversed)
2456 {
2457 focus = paned;
2458 first = next;
2459 }
2460 else
2461 {
2462 focus = next;
2463 first = next;
2464 }
2465 }
2466 else
2467 {
2468 /* Focus is not inside this paned, and we don't have focus.
2469 * Presumably this happened because the application wants us
2470 * to start keyboard navigating.
2471 */
2472 focus = paned;
2473
2474 if (reversed)
2475 first = paned;
2476 else
2477 first = next;
2478 }
2479
2480 gtk_paned_set_saved_focus (paned: focus, widget: gtk_root_get_focus (self: gtk_widget_get_root (GTK_WIDGET (paned))));
2481 gtk_paned_set_first_paned (paned: focus, first_paned: first);
2482 paned->original_position = gtk_paned_get_position (paned: focus);
2483
2484 gtk_widget_grab_focus (GTK_WIDGET (focus));
2485 }
2486
2487 return TRUE;
2488}
2489
2490static gboolean
2491gtk_paned_toggle_handle_focus (GtkPaned *paned)
2492{
2493 /* This function/signal has the wrong name. It is called when you
2494 * press Tab or Shift-Tab and what we do is act as if
2495 * the user pressed Return and then Tab or Shift-Tab
2496 */
2497 if (gtk_widget_is_focus (GTK_WIDGET (paned)))
2498 gtk_paned_accept_position (paned);
2499
2500 return FALSE;
2501}
2502
2503/**
2504 * gtk_paned_set_wide_handle: (attributes org.gtk.Method.set_propery=wide-handle)
2505 * @paned: a `GtkPaned`
2506 * @wide: the new value for the [property@Gtk.Paned:wide-handle] property
2507 *
2508 * Sets whether the separator should be wide.
2509 */
2510void
2511gtk_paned_set_wide_handle (GtkPaned *paned,
2512 gboolean wide)
2513{
2514 gboolean old_wide;
2515
2516 g_return_if_fail (GTK_IS_PANED (paned));
2517
2518 old_wide = gtk_paned_get_wide_handle (paned);
2519 if (old_wide != wide)
2520 {
2521 if (wide)
2522 gtk_widget_add_css_class (widget: paned->handle_widget, css_class: "wide");
2523 else
2524 gtk_widget_remove_css_class (widget: paned->handle_widget, css_class: "wide");
2525
2526 g_object_notify_by_pspec (G_OBJECT (paned), pspec: paned_props[PROP_WIDE_HANDLE]);
2527 }
2528}
2529
2530/**
2531 * gtk_paned_get_wide_handle: (attributes org.gtk.Method.get_property=wide-handle)
2532 * @paned: a `GtkPaned`
2533 *
2534 * Gets whether the separator should be wide.
2535 *
2536 * Returns: %TRUE if the paned should have a wide handle
2537 */
2538gboolean
2539gtk_paned_get_wide_handle (GtkPaned *paned)
2540{
2541 g_return_val_if_fail (GTK_IS_PANED (paned), FALSE);
2542
2543 return gtk_widget_has_css_class (widget: paned->handle_widget, css_class: "wide");
2544}
2545

source code of gtk/gtk/gtkpaned.c