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