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