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