]> Pileus Git - ~andy/gtk/blob - gtk/gtkpaned.c
Keyboard navigation for GtkPaned F6: cycle between panes. F8: focus handle
[~andy/gtk] / gtk / gtkpaned.c
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, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /*
21  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
22  * file for a list of people on the GTK+ Team.  See the ChangeLog
23  * files for a list of changes.  These files are distributed with
24  * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
25  */
26
27 #include "gtkintl.h"
28 #include "gtkpaned.h"
29 #include "gtkbindings.h"
30 #include "gtksignal.h"
31 #include "gdk/gdkkeysyms.h"
32 #include "gtkwindow.h"
33 #include "gtkmain.h"
34 #include "gtkmarshalers.h"
35
36 enum {
37   PROP_0,
38   PROP_POSITION,
39   PROP_POSITION_SET
40 };
41
42 enum {
43   CYCLE_CHILD_FOCUS,
44   TOGGLE_HANDLE_FOCUS,
45   MOVE_HANDLE,
46   CYCLE_HANDLE_FOCUS,
47   LAST_SIGNAL,
48   ACCEPT_POSITION,
49   CANCEL_POSITION
50 };
51
52 static void     gtk_paned_class_init            (GtkPanedClass    *klass);
53 static void     gtk_paned_init                  (GtkPaned         *paned);
54 static void     gtk_paned_set_property          (GObject          *object,
55                                                  guint             prop_id,
56                                                  const GValue     *value,
57                                                  GParamSpec       *pspec);
58 static void     gtk_paned_get_property          (GObject          *object,
59                                                  guint             prop_id,
60                                                  GValue           *value,
61                                                  GParamSpec       *pspec);
62 static void     gtk_paned_realize               (GtkWidget        *widget);
63 static void     gtk_paned_unrealize             (GtkWidget        *widget);
64 static void     gtk_paned_map                   (GtkWidget        *widget);
65 static void     gtk_paned_unmap                 (GtkWidget        *widget);
66 static gint     gtk_paned_expose                (GtkWidget        *widget,
67                                                  GdkEventExpose   *event);
68 static gboolean gtk_paned_focus                 (GtkWidget        *widget,
69                                                  GtkDirectionType  direction);
70 static void     gtk_paned_add                   (GtkContainer     *container,
71                                                  GtkWidget        *widget);
72 static void     gtk_paned_remove                (GtkContainer     *container,
73                                                  GtkWidget        *widget);
74 static void     gtk_paned_forall                (GtkContainer     *container,
75                                                  gboolean          include_internals,
76                                                  GtkCallback       callback,
77                                                  gpointer          callback_data);
78 static void     gtk_paned_set_focus_child       (GtkContainer     *container,
79                                                  GtkWidget        *child);
80 static void     gtk_paned_set_saved_focus       (GtkPaned         *paned,
81                                                  GtkWidget        *widget);
82 static void     gtk_paned_set_last_child1_focus (GtkPaned         *paned,
83                                                  GtkWidget        *widget);
84 static void     gtk_paned_set_last_child2_focus (GtkPaned         *paned,
85                                                  GtkWidget        *widget);
86 static gboolean gtk_paned_cycle_child_focus     (GtkPaned         *paned,
87                                                  gboolean          reverse);
88 static gboolean gtk_paned_cycle_handle_focus    (GtkPaned         *paned,
89                                                  gboolean          reverse);
90 static gboolean gtk_paned_move_handle           (GtkPaned         *paned,
91                                                  GtkScrollType     scroll);
92 static gboolean gtk_paned_accept_position       (GtkPaned         *paned);
93 static gboolean gtk_paned_cancel_position       (GtkPaned         *paned);
94 static gboolean gtk_paned_toggle_handle_focus   (GtkPaned         *paned);
95 static GtkType  gtk_paned_child_type            (GtkContainer     *container);
96
97 static GtkContainerClass *parent_class = NULL;
98
99
100 GtkType
101 gtk_paned_get_type (void)
102 {
103   static GtkType paned_type = 0;
104   
105   if (!paned_type)
106     {
107       static const GtkTypeInfo paned_info =
108       {
109         "GtkPaned",
110         sizeof (GtkPaned),
111         sizeof (GtkPanedClass),
112         (GtkClassInitFunc) gtk_paned_class_init,
113         (GtkObjectInitFunc) gtk_paned_init,
114         /* reserved_1 */ NULL,
115         /* reserved_2 */ NULL,
116         (GtkClassInitFunc) NULL,
117       };
118
119       paned_type = gtk_type_unique (GTK_TYPE_CONTAINER, &paned_info);
120     }
121   
122   return paned_type;
123 }
124
125 static guint signals[LAST_SIGNAL] = { 0 };
126
127 static void
128 add_tab_bindings (GtkBindingSet    *binding_set,
129                   GdkModifierType   modifiers,
130                   gboolean          reverse)
131 {
132   gtk_binding_entry_add_signal (binding_set, GDK_Tab, modifiers,
133                                 "cycle_handle_focus", 1,
134                                 G_TYPE_BOOLEAN, reverse);
135   gtk_binding_entry_add_signal (binding_set, GDK_KP_Tab, modifiers,
136                                 "cycle_handle_focus", 1,
137                                 G_TYPE_BOOLEAN, reverse);
138   gtk_binding_entry_add_signal (binding_set, GDK_ISO_Left_Tab, modifiers,
139                                 "cycle_handle_focus", 1,
140                                 G_TYPE_BOOLEAN, reverse);
141 }
142
143 static void
144 add_move_binding (GtkBindingSet   *binding_set,
145                   guint            keyval,
146                   GdkModifierType  mask,
147                   GtkScrollType    scroll)
148 {
149   gtk_binding_entry_add_signal (binding_set, keyval, mask,
150                                 "move_handle", 1,
151                                 GTK_TYPE_SCROLL_TYPE, scroll);
152 }
153
154 static void
155 gtk_paned_class_init (GtkPanedClass *class)
156 {
157   GObjectClass *object_class;
158   GtkWidgetClass *widget_class;
159   GtkContainerClass *container_class;
160   GtkPanedClass *paned_class;
161   GtkBindingSet *binding_set;
162
163   object_class = (GObjectClass *) class;
164   widget_class = (GtkWidgetClass *) class;
165   container_class = (GtkContainerClass *) class;
166   paned_class = (GtkPanedClass *) class;
167
168   parent_class = gtk_type_class (GTK_TYPE_CONTAINER);
169
170   object_class->set_property = gtk_paned_set_property;
171   object_class->get_property = gtk_paned_get_property;
172
173   widget_class->realize = gtk_paned_realize;
174   widget_class->unrealize = gtk_paned_unrealize;
175   widget_class->map = gtk_paned_map;
176   widget_class->unmap = gtk_paned_unmap;
177   widget_class->expose_event = gtk_paned_expose;
178   widget_class->focus = gtk_paned_focus;
179   
180   container_class->add = gtk_paned_add;
181   container_class->remove = gtk_paned_remove;
182   container_class->forall = gtk_paned_forall;
183   container_class->child_type = gtk_paned_child_type;
184   container_class->set_focus_child = gtk_paned_set_focus_child;
185
186   paned_class->cycle_child_focus = gtk_paned_cycle_child_focus;
187   paned_class->toggle_handle_focus = gtk_paned_toggle_handle_focus;
188   paned_class->move_handle = gtk_paned_move_handle;
189   paned_class->cycle_handle_focus = gtk_paned_cycle_handle_focus;
190   paned_class->accept_position = gtk_paned_accept_position;
191   paned_class->cancel_position = gtk_paned_cancel_position;
192   
193   g_object_class_install_property (object_class,
194                                    PROP_POSITION,
195                                    g_param_spec_int ("position",
196                                                      _("Position"),
197                                                      _("Position of paned separator in pixels (0 means all the way to the left/top)"),
198                                                      0,
199                                                      G_MAXINT,
200                                                      0,
201                                                      G_PARAM_READABLE | G_PARAM_WRITABLE));
202   g_object_class_install_property (object_class,
203                                    PROP_POSITION_SET,
204                                    g_param_spec_boolean ("position_set",
205                                                          _("Position Set"),
206                                                          _("TRUE if the Position property should be used"),
207                                                          FALSE,
208                                                          G_PARAM_READABLE | G_PARAM_WRITABLE));
209                                    
210   gtk_widget_class_install_style_property (widget_class,
211                                            g_param_spec_int ("handle_size",
212                                                              _("Handle Size"),
213                                                              _("Width of handle"),
214                                                              0,
215                                                              G_MAXINT,
216                                                              5,
217                                                              G_PARAM_READABLE));
218
219   signals [CYCLE_HANDLE_FOCUS] =
220     g_signal_new ("cycle_child_focus",
221                   G_TYPE_FROM_CLASS (object_class),
222                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
223                   G_STRUCT_OFFSET (GtkPanedClass, cycle_child_focus),
224                   NULL, NULL,
225                   _gtk_marshal_BOOLEAN__BOOLEAN,
226                   G_TYPE_BOOLEAN, 1,
227                   G_TYPE_BOOLEAN);
228
229   signals [TOGGLE_HANDLE_FOCUS] =
230     g_signal_new ("toggle_handle_focus",
231                   G_TYPE_FROM_CLASS (object_class),
232                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
233                   G_STRUCT_OFFSET (GtkPanedClass, toggle_handle_focus),
234                   NULL, NULL,
235                   _gtk_marshal_BOOLEAN__VOID,
236                   G_TYPE_BOOLEAN, 0);
237
238   signals[MOVE_HANDLE] =
239     g_signal_new ("move_handle",
240                   G_TYPE_FROM_CLASS (object_class),
241                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
242                   G_STRUCT_OFFSET (GtkPanedClass, move_handle),
243                   NULL, NULL,
244                   _gtk_marshal_BOOLEAN__ENUM,
245                   G_TYPE_BOOLEAN, 1,
246                   GTK_TYPE_SCROLL_TYPE);
247
248   signals [CYCLE_HANDLE_FOCUS] =
249     g_signal_new ("cycle_handle_focus",
250                   G_TYPE_FROM_CLASS (object_class),
251                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
252                   G_STRUCT_OFFSET (GtkPanedClass, cycle_handle_focus),
253                   NULL, NULL,
254                   _gtk_marshal_BOOLEAN__BOOLEAN,
255                   G_TYPE_BOOLEAN, 1,
256                   G_TYPE_BOOLEAN);
257
258   signals [ACCEPT_POSITION] =
259     g_signal_new ("accept_position",
260                   G_TYPE_FROM_CLASS (object_class),
261                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
262                   G_STRUCT_OFFSET (GtkPanedClass, accept_position),
263                   NULL, NULL,
264                   _gtk_marshal_BOOLEAN__VOID,
265                   G_TYPE_BOOLEAN, 0);
266
267   signals [CANCEL_POSITION] =
268     g_signal_new ("cancel_position",
269                   G_TYPE_FROM_CLASS (object_class),
270                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
271                   G_STRUCT_OFFSET (GtkPanedClass, cancel_position),
272                   NULL, NULL,
273                   _gtk_marshal_BOOLEAN__VOID,
274                   G_TYPE_BOOLEAN, 0);
275
276   binding_set = gtk_binding_set_by_class (object_class);
277
278   /* F6 and friends */
279   gtk_binding_entry_add_signal (binding_set,                            
280                                 GDK_F6, 0,
281                                 "cycle_child_focus", 1, 
282                                 G_TYPE_BOOLEAN, FALSE);
283   gtk_binding_entry_add_signal (binding_set,
284                                 GDK_F6, GDK_SHIFT_MASK,
285                                 "cycle_child_focus", 1,
286                                 G_TYPE_BOOLEAN, TRUE);
287
288   /* F8 and friends */
289   gtk_binding_entry_add_signal (binding_set,
290                                 GDK_F8, 0,
291                                 "toggle_handle_focus", 0);
292  
293   add_tab_bindings (binding_set, 0, GTK_DIR_TAB_FORWARD);
294   add_tab_bindings (binding_set, GDK_CONTROL_MASK, GTK_DIR_TAB_FORWARD);
295   add_tab_bindings (binding_set, GDK_SHIFT_MASK, GTK_DIR_TAB_BACKWARD);
296   add_tab_bindings (binding_set, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_DIR_TAB_BACKWARD);
297
298   /* accept and cancel positions */
299   gtk_binding_entry_add_signal (binding_set,
300                                 GDK_Escape, 0,
301                                 "cancel_position", 0);
302
303   gtk_binding_entry_add_signal (binding_set,
304                                 GDK_Return, 0,
305                                 "accept_position", 0);
306   gtk_binding_entry_add_signal (binding_set,
307                                 GDK_KP_Enter, 0,
308                                 "accept_position", 0);
309   gtk_binding_entry_add_signal (binding_set,
310                                 GDK_space, 0,
311                                 "accept_position", 0);
312   gtk_binding_entry_add_signal (binding_set,
313                                 GDK_KP_Space, 0,
314                                 "accept_position", 0);
315
316   /* move handle */
317   add_move_binding (binding_set, GDK_Left, 0, GTK_SCROLL_STEP_LEFT);
318   add_move_binding (binding_set, GDK_KP_Left, 0, GTK_SCROLL_STEP_LEFT);
319   add_move_binding (binding_set, GDK_Left, GDK_CONTROL_MASK, GTK_SCROLL_PAGE_LEFT);
320   add_move_binding (binding_set, GDK_KP_Left, GDK_CONTROL_MASK, GTK_SCROLL_PAGE_LEFT);
321
322   add_move_binding (binding_set, GDK_Right, 0, GTK_SCROLL_STEP_RIGHT);
323   add_move_binding (binding_set, GDK_Right, GDK_CONTROL_MASK, GTK_SCROLL_PAGE_RIGHT);
324   add_move_binding (binding_set, GDK_KP_Right, 0, GTK_SCROLL_STEP_RIGHT);
325   add_move_binding (binding_set, GDK_KP_Right, GDK_CONTROL_MASK, GTK_SCROLL_PAGE_RIGHT);
326
327   add_move_binding (binding_set, GDK_Up, 0, GTK_SCROLL_STEP_UP);
328   add_move_binding (binding_set, GDK_Up, GDK_CONTROL_MASK, GTK_SCROLL_PAGE_UP);
329   add_move_binding (binding_set, GDK_KP_Up, 0, GTK_SCROLL_STEP_UP);
330   add_move_binding (binding_set, GDK_KP_Up, GDK_CONTROL_MASK, GTK_SCROLL_PAGE_UP);
331   add_move_binding (binding_set, GDK_Page_Up, 0, GTK_SCROLL_PAGE_UP);
332   add_move_binding (binding_set, GDK_KP_Page_Up, 0, GTK_SCROLL_PAGE_UP);
333
334   add_move_binding (binding_set, GDK_Down, 0, GTK_SCROLL_STEP_DOWN);
335   add_move_binding (binding_set, GDK_Down, GDK_CONTROL_MASK, GTK_SCROLL_PAGE_DOWN);
336   add_move_binding (binding_set, GDK_KP_Down, 0, GTK_SCROLL_STEP_DOWN);
337   add_move_binding (binding_set, GDK_KP_Down, GDK_CONTROL_MASK, GTK_SCROLL_PAGE_DOWN);
338   add_move_binding (binding_set, GDK_Page_Down, 0, GTK_SCROLL_PAGE_RIGHT);
339   add_move_binding (binding_set, GDK_KP_Page_Down, 0, GTK_SCROLL_PAGE_RIGHT);
340
341   add_move_binding (binding_set, GDK_Home, 0, GTK_SCROLL_START);
342   add_move_binding (binding_set, GDK_KP_Home, 0, GTK_SCROLL_START);
343   add_move_binding (binding_set, GDK_End, 0, GTK_SCROLL_END);
344   add_move_binding (binding_set, GDK_KP_End, 0, GTK_SCROLL_END);
345 }
346
347 static GtkType
348 gtk_paned_child_type (GtkContainer *container)
349 {
350   if (!GTK_PANED (container)->child1 || !GTK_PANED (container)->child2)
351     return GTK_TYPE_WIDGET;
352   else
353     return GTK_TYPE_NONE;
354 }
355
356 static void
357 gtk_paned_init (GtkPaned *paned)
358 {
359   GTK_WIDGET_SET_FLAGS (paned, GTK_NO_WINDOW | GTK_CAN_FOCUS);
360   
361   paned->child1 = NULL;
362   paned->child2 = NULL;
363   paned->handle = NULL;
364   paned->xor_gc = NULL;
365   paned->cursor_type = GDK_CROSS;
366   
367   paned->handle_pos.width = 5;
368   paned->handle_pos.height = 5;
369   paned->position_set = FALSE;
370   paned->last_allocation = -1;
371   paned->in_drag = FALSE;
372
373   paned->saved_focus = NULL;
374   paned->last_child1_focus = NULL;
375   paned->last_child2_focus = NULL;
376   paned->in_recursion = FALSE;
377   paned->original_position = -1;
378   
379   paned->handle_pos.x = -1;
380   paned->handle_pos.y = -1;
381 }
382
383 static void
384 gtk_paned_set_property (GObject        *object,
385                         guint           prop_id,
386                         const GValue   *value,
387                         GParamSpec     *pspec)
388 {
389   GtkPaned *paned = GTK_PANED (object);
390   
391   switch (prop_id)
392     {
393     case PROP_POSITION:
394       gtk_paned_set_position (paned, g_value_get_int (value));
395       break;
396     case PROP_POSITION_SET:
397       paned->position_set = g_value_get_boolean (value);
398       gtk_widget_queue_resize (GTK_WIDGET (paned));
399       break;
400     default:
401       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
402       break;
403     }
404 }
405
406 static void
407 gtk_paned_get_property (GObject        *object,
408                         guint           prop_id,
409                         GValue         *value,
410                         GParamSpec     *pspec)
411 {
412   GtkPaned *paned = GTK_PANED (object);
413   
414   switch (prop_id)
415     {
416     case PROP_POSITION:
417       g_value_set_int (value, paned->child1_size);
418       break;
419     case PROP_POSITION_SET:
420       g_value_set_boolean (value, paned->position_set);
421       break;
422     default:
423       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
424       break;
425     }
426 }
427
428 static void
429 gtk_paned_realize (GtkWidget *widget)
430 {
431   GtkPaned *paned;
432   GdkWindowAttr attributes;
433   gint attributes_mask;
434
435   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
436   paned = GTK_PANED (widget);
437
438   widget->window = gtk_widget_get_parent_window (widget);
439   gdk_window_ref (widget->window);
440   
441   attributes.window_type = GDK_WINDOW_CHILD;
442   attributes.wclass = GDK_INPUT_ONLY;
443   attributes.x = paned->handle_pos.x;
444   attributes.y = paned->handle_pos.y;
445   attributes.width = paned->handle_pos.width;
446   attributes.height = paned->handle_pos.height;
447   attributes.cursor = gdk_cursor_new (paned->cursor_type);
448   attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
449                             GDK_BUTTON_RELEASE_MASK |
450                             GDK_POINTER_MOTION_MASK |
451                             GDK_POINTER_MOTION_HINT_MASK);
452   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_CURSOR;
453
454   paned->handle = gdk_window_new (widget->window,
455                                   &attributes, attributes_mask);
456   gdk_window_set_user_data (paned->handle, paned);
457   gdk_cursor_destroy (attributes.cursor);
458
459   widget->style = gtk_style_attach (widget->style, widget->window);
460
461   if (paned->child1 && GTK_WIDGET_VISIBLE (paned->child1) &&
462       paned->child2 && GTK_WIDGET_VISIBLE (paned->child2))
463     gdk_window_show (paned->handle);
464 }
465
466 static void
467 gtk_paned_unrealize (GtkWidget *widget)
468 {
469   GtkPaned *paned = GTK_PANED (widget);
470
471   if (paned->xor_gc)
472     {
473       gdk_gc_destroy (paned->xor_gc);
474       paned->xor_gc = NULL;
475     }
476
477   if (paned->handle)
478     {
479       gdk_window_set_user_data (paned->handle, NULL);
480       gdk_window_destroy (paned->handle);
481       paned->handle = NULL;
482     }
483
484   gtk_paned_set_last_child1_focus (paned, NULL);
485   gtk_paned_set_last_child2_focus (paned, NULL);
486   gtk_paned_set_saved_focus (paned, NULL);
487   
488   if (GTK_WIDGET_CLASS (parent_class)->unrealize)
489     (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
490 }
491
492 static void
493 gtk_paned_map (GtkWidget *widget)
494 {
495   GtkPaned *paned = GTK_PANED (widget);
496
497   gdk_window_show (paned->handle);
498
499   GTK_WIDGET_CLASS (parent_class)->map (widget);
500 }
501
502 static void
503 gtk_paned_unmap (GtkWidget *widget)
504 {
505   GtkPaned *paned = GTK_PANED (widget);
506     
507   gdk_window_hide (paned->handle);
508
509   GTK_WIDGET_CLASS (parent_class)->unmap (widget);
510 }
511
512 static gint
513 gtk_paned_expose (GtkWidget      *widget,
514                   GdkEventExpose *event)
515 {
516   GtkPaned *paned = GTK_PANED (widget);
517
518   if (GTK_WIDGET_VISIBLE (widget) && GTK_WIDGET_MAPPED (widget) &&
519       paned->child1 && GTK_WIDGET_VISIBLE (paned->child1) &&
520       paned->child2 && GTK_WIDGET_VISIBLE (paned->child2))
521     {
522       GdkRegion *region;
523
524       region = gdk_region_rectangle (&paned->handle_pos);
525       gdk_region_intersect (region, event->region);
526
527       if (!gdk_region_empty (region))
528         {
529           GtkStateType state;
530           GdkRectangle clip;
531
532           gdk_region_get_clipbox (region, &clip);
533
534           state = GTK_WIDGET_STATE (widget);
535           if (gtk_widget_is_focus (widget))
536             state = GTK_STATE_SELECTED;
537           
538           gtk_paint_handle (widget->style, widget->window,
539                             state, GTK_SHADOW_NONE,
540                             &clip, widget, "paned",
541                             paned->handle_pos.x, paned->handle_pos.y,
542                             paned->handle_pos.width, paned->handle_pos.height,
543                             paned->orientation);
544         }
545
546       gdk_region_destroy (region);
547     }
548   
549   /* Chain up to draw children */
550   GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
551
552   return FALSE;
553 }
554
555 static gboolean
556 gtk_paned_focus (GtkWidget        *widget,
557                  GtkDirectionType  direction)
558
559 {
560   gboolean retval;
561   
562   /* This is a hack, but how can this be done without
563    * excessive cut-and-paste from gtkcontainer.c?
564    */
565
566   GTK_WIDGET_UNSET_FLAGS (widget, GTK_CAN_FOCUS);
567   retval = (* GTK_WIDGET_CLASS (parent_class)->focus) (widget, direction);
568   GTK_WIDGET_SET_FLAGS (widget, GTK_CAN_FOCUS);
569
570   return retval;
571 }
572
573 void
574 gtk_paned_add1 (GtkPaned  *paned,
575                 GtkWidget *widget)
576 {
577   gtk_paned_pack1 (paned, widget, FALSE, TRUE);
578 }
579
580 void
581 gtk_paned_add2 (GtkPaned  *paned,
582                 GtkWidget *widget)
583 {
584   gtk_paned_pack2 (paned, widget, TRUE, TRUE);
585 }
586
587 void
588 gtk_paned_pack1 (GtkPaned  *paned,
589                  GtkWidget *child,
590                  gboolean   resize,
591                  gboolean   shrink)
592 {
593   g_return_if_fail (GTK_IS_PANED (paned));
594   g_return_if_fail (GTK_IS_WIDGET (child));
595
596   if (!paned->child1)
597     {
598       paned->child1 = child;
599       paned->child1_resize = resize;
600       paned->child1_shrink = shrink;
601
602       gtk_widget_set_parent (child, GTK_WIDGET (paned));
603     }
604 }
605
606 void
607 gtk_paned_pack2 (GtkPaned  *paned,
608                  GtkWidget *child,
609                  gboolean   resize,
610                  gboolean   shrink)
611 {
612   g_return_if_fail (GTK_IS_PANED (paned));
613   g_return_if_fail (GTK_IS_WIDGET (child));
614
615   if (!paned->child2)
616     {
617       paned->child2 = child;
618       paned->child2_resize = resize;
619       paned->child2_shrink = shrink;
620
621       gtk_widget_set_parent (child, GTK_WIDGET (paned));
622     }
623 }
624
625
626 static void
627 gtk_paned_add (GtkContainer *container,
628                GtkWidget    *widget)
629 {
630   GtkPaned *paned;
631
632   g_return_if_fail (GTK_IS_PANED (container));
633
634   paned = GTK_PANED (container);
635
636   if (!paned->child1)
637     gtk_paned_add1 (paned, widget);
638   else if (!paned->child2)
639     gtk_paned_add2 (paned, widget);
640 }
641
642 static void
643 gtk_paned_remove (GtkContainer *container,
644                   GtkWidget    *widget)
645 {
646   GtkPaned *paned;
647   gboolean was_visible;
648
649   paned = GTK_PANED (container);
650   was_visible = GTK_WIDGET_VISIBLE (widget);
651
652   if (paned->child1 == widget)
653     {
654       gtk_widget_unparent (widget);
655
656       paned->child1 = NULL;
657
658       if (was_visible && GTK_WIDGET_VISIBLE (container))
659         gtk_widget_queue_resize (GTK_WIDGET (container));
660     }
661   else if (paned->child2 == widget)
662     {
663       gtk_widget_unparent (widget);
664
665       paned->child2 = NULL;
666
667       if (was_visible && GTK_WIDGET_VISIBLE (container))
668         gtk_widget_queue_resize (GTK_WIDGET (container));
669     }
670 }
671
672 static void
673 gtk_paned_forall (GtkContainer *container,
674                   gboolean      include_internals,
675                   GtkCallback   callback,
676                   gpointer      callback_data)
677 {
678   GtkPaned *paned;
679
680   g_return_if_fail (callback != NULL);
681
682   paned = GTK_PANED (container);
683
684   if (paned->child1)
685     (*callback) (paned->child1, callback_data);
686   if (paned->child2)
687     (*callback) (paned->child2, callback_data);
688 }
689
690 /**
691  * gtk_paned_get_position:
692  * @paned: a #GtkPaned widget
693  * 
694  * Obtains the position of the divider between the two panes.
695  * 
696  * Return value: position of the divider
697  **/
698 gint
699 gtk_paned_get_position (GtkPaned  *paned)
700 {
701   g_return_val_if_fail (GTK_IS_PANED (paned), 0);
702
703   return paned->child1_size;
704 }
705
706 /**
707  * gtk_paned_set_position:
708  * @paned: a #GtkPaned widget
709  * @position: pixel position of divider, a negative value means that the position
710  *            is unset.
711  * 
712  * Sets the position of the divider between the two panes.
713  **/
714 void
715 gtk_paned_set_position (GtkPaned *paned,
716                         gint      position)
717 {
718   GObject *object;
719   
720   g_return_if_fail (GTK_IS_PANED (paned));
721
722   object = G_OBJECT (paned);
723   
724   if (position >= 0)
725     {
726       /* We don't clamp here - the assumption is that
727        * if the total allocation changes at the same time
728        * as the position, the position set is with reference
729        * to the new total size. If only the position changes,
730        * then clamping will occur in gtk_paned_compute_position()
731        */
732
733       paned->child1_size = position;
734       paned->position_set = TRUE;
735     }
736   else
737     {
738       paned->position_set = FALSE;
739     }
740
741   g_object_freeze_notify (object);
742   g_object_notify (object, "position");
743   g_object_notify (object, "position_set");
744   g_object_thaw_notify (object);
745
746   gtk_widget_queue_resize (GTK_WIDGET (paned));
747 }
748
749 void
750 gtk_paned_compute_position (GtkPaned *paned,
751                             gint      allocation,
752                             gint      child1_req,
753                             gint      child2_req)
754 {
755   gint old_position;
756   
757   g_return_if_fail (GTK_IS_PANED (paned));
758
759   old_position = paned->child1_size;
760
761   paned->min_position = paned->child1_shrink ? 0 : child1_req;
762
763   paned->max_position = allocation;
764   if (!paned->child2_shrink)
765     paned->max_position = MAX (1, paned->max_position - child2_req);
766
767   if (!paned->position_set)
768     {
769       if (paned->child1_resize && !paned->child2_resize)
770         paned->child1_size = MAX (1, allocation - child2_req);
771       else if (!paned->child1_resize && paned->child2_resize)
772         paned->child1_size = child1_req;
773       else if (child1_req + child2_req != 0)
774         paned->child1_size = allocation * ((gdouble)child1_req / (child1_req + child2_req));
775       else
776         paned->child1_size = allocation * 0.5;
777     }
778   else
779     {
780       /* If the position was set before the initial allocation.
781        * (paned->last_allocation <= 0) just clamp it and leave it.
782        */
783       if (paned->last_allocation > 0)
784         {
785           if (paned->child1_resize && !paned->child2_resize)
786             paned->child1_size += allocation - paned->last_allocation;
787           else if (!(!paned->child1_resize && paned->child2_resize))
788             paned->child1_size = allocation * ((gdouble) paned->child1_size / (paned->last_allocation));
789         }
790     }
791
792   paned->child1_size = CLAMP (paned->child1_size,
793                               paned->min_position,
794                               paned->max_position);
795
796   if (paned->child1_size != old_position)
797     g_object_notify (G_OBJECT (paned), "position");
798
799   paned->last_allocation = allocation;
800 }
801
802 static void
803 gtk_paned_set_saved_focus (GtkPaned *paned, GtkWidget *widget)
804 {
805   if (paned->saved_focus)
806     g_object_remove_weak_pointer (G_OBJECT (paned->saved_focus),
807                                   (gpointer *)&(paned->saved_focus));
808
809   paned->saved_focus = widget;
810
811   if (paned->saved_focus)
812     g_object_add_weak_pointer (G_OBJECT (paned->saved_focus),
813                                (gpointer *)&(paned->saved_focus));
814 }
815
816 static void
817 gtk_paned_set_last_child1_focus (GtkPaned *paned, GtkWidget *widget)
818 {
819   if (paned->last_child1_focus)
820     g_object_remove_weak_pointer (G_OBJECT (paned->last_child1_focus),
821                                   (gpointer *)&(paned->last_child1_focus));
822
823   paned->last_child1_focus = widget;
824
825   if (paned->last_child1_focus)
826     g_object_add_weak_pointer (G_OBJECT (paned->last_child1_focus),
827                                (gpointer *)&(paned->last_child1_focus));
828 }
829
830 static void
831 gtk_paned_set_last_child2_focus (GtkPaned *paned, GtkWidget *widget)
832 {
833   if (paned->last_child2_focus)
834     g_object_remove_weak_pointer (G_OBJECT (paned->last_child2_focus),
835                                   (gpointer *)&(paned->last_child2_focus));
836
837   paned->last_child2_focus = widget;
838
839   if (paned->last_child2_focus)
840     g_object_add_weak_pointer (G_OBJECT (paned->last_child2_focus),
841                                (gpointer *)&(paned->last_child2_focus));
842 }
843
844 static GtkWidget *
845 paned_get_focus_widget (GtkPaned *paned)
846 {
847   GtkWidget *toplevel;
848
849   toplevel = gtk_widget_get_toplevel (GTK_WIDGET (paned));
850   if (GTK_WIDGET_TOPLEVEL (toplevel))
851     return GTK_WINDOW (toplevel)->focus_widget;
852
853   return NULL;
854 }
855
856 static void
857 gtk_paned_set_focus_child (GtkContainer *container,
858                            GtkWidget    *focus_child)
859 {
860   GtkPaned *paned;
861   
862   g_return_if_fail (GTK_IS_PANED (container));
863
864   paned = GTK_PANED (container);
865  
866   if (focus_child == NULL)
867     {
868       GtkWidget *last_focus;
869       GtkWidget *w;
870       
871       last_focus = paned_get_focus_widget (paned);
872
873       if (last_focus)
874         {
875           /* If there is one or more paned widgets between us and the
876            * focus widget, we want the topmost of those as last_focus
877            */
878           for (w = last_focus; w != GTK_WIDGET (paned); w = w->parent)
879             if (GTK_IS_PANED (w))
880               last_focus = w;
881           
882           if (container->focus_child == paned->child1)
883             gtk_paned_set_last_child1_focus (paned, last_focus);
884           else if (container->focus_child == paned->child2)
885             gtk_paned_set_last_child2_focus (paned, last_focus);
886         }
887     }
888
889   if (parent_class->set_focus_child)
890     (* parent_class->set_focus_child) (container, focus_child);
891 }
892
893 static void
894 gtk_paned_get_cycle_chain (GtkPaned          *paned,
895                            GtkDirectionType   direction,
896                            GList            **widgets)
897 {
898   GtkContainer *container = GTK_CONTAINER (paned);
899   GtkWidget *ancestor = NULL;
900   GList *temp_list = NULL;
901   GList *list;
902
903   if (paned->in_recursion)
904     return;
905
906   g_assert (widgets != NULL);
907
908   if (paned->last_child1_focus &&
909       !gtk_widget_is_ancestor (paned->last_child1_focus, GTK_WIDGET (paned)))
910     {
911       gtk_paned_set_last_child1_focus (paned, NULL);
912     }
913
914   if (paned->last_child2_focus &&
915       !gtk_widget_is_ancestor (paned->last_child2_focus, GTK_WIDGET (paned)))
916     {
917       gtk_paned_set_last_child2_focus (paned, NULL);
918     }
919
920   if (GTK_WIDGET (paned)->parent)
921     ancestor = gtk_widget_get_ancestor (GTK_WIDGET (paned)->parent, GTK_TYPE_PANED);
922
923   /* The idea here is that temp_list is a list of widgets we want to cycle
924    * to. The list is prioritized so that the first element is our first
925    * choice, the next our second, and so on.
926    *
927    * We can't just use g_list_reverse(), because we want to try
928    * paned->last_child?_focus before paned->child?, both when we
929    * are going forward and backward.
930    */
931   if (direction == GTK_DIR_TAB_FORWARD)
932     {
933       if (container->focus_child == paned->child1)
934         {
935           temp_list = g_list_append (temp_list, paned->last_child2_focus);
936           temp_list = g_list_append (temp_list, paned->child2);
937           temp_list = g_list_append (temp_list, ancestor);
938         }
939       else if (container->focus_child == paned->child2)
940         {
941           temp_list = g_list_append (temp_list, ancestor);
942           temp_list = g_list_append (temp_list, paned->last_child1_focus);
943           temp_list = g_list_append (temp_list, paned->child1);
944         }
945       else
946         {
947           temp_list = g_list_append (temp_list, paned->last_child1_focus);
948           temp_list = g_list_append (temp_list, paned->child1);
949           temp_list = g_list_append (temp_list, paned->last_child2_focus);
950           temp_list = g_list_append (temp_list, paned->child2);
951           temp_list = g_list_append (temp_list, ancestor);
952         }
953     }
954   else
955     {
956       if (container->focus_child == paned->child1)
957         {
958           temp_list = g_list_append (temp_list, ancestor);
959           temp_list = g_list_append (temp_list, paned->last_child2_focus);
960           temp_list = g_list_append (temp_list, paned->child2);
961         }
962       else if (container->focus_child == paned->child2)
963         {
964           temp_list = g_list_append (temp_list, paned->last_child1_focus);
965           temp_list = g_list_append (temp_list, paned->child1);
966           temp_list = g_list_append (temp_list, ancestor);
967         }
968       else
969         {
970           temp_list = g_list_append (temp_list, paned->last_child2_focus);
971           temp_list = g_list_append (temp_list, paned->child2);
972           temp_list = g_list_append (temp_list, paned->last_child1_focus);
973           temp_list = g_list_append (temp_list, paned->child1);
974           temp_list = g_list_append (temp_list, ancestor);
975         }
976     }
977
978   /* Walk through the list and expand all the paned widgets. */
979   for (list = temp_list; list != NULL; list = list->next)
980     {
981       GtkWidget *widget = list->data;
982
983       if (widget)
984         {
985           if (GTK_IS_PANED (widget))
986             {
987               paned->in_recursion = TRUE;
988               gtk_paned_get_cycle_chain (GTK_PANED (widget), direction, widgets);
989               paned->in_recursion = FALSE;
990             }
991           else
992             {
993               *widgets = g_list_append (*widgets, widget);
994             }
995         }
996     }
997
998   g_list_free (temp_list);
999 }
1000
1001 static gboolean
1002 gtk_paned_cycle_child_focus (GtkPaned *paned,
1003                              gboolean  reversed)
1004 {
1005   GList *cycle_chain = NULL;
1006   GList *list;
1007
1008   GtkDirectionType direction = reversed? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD;
1009
1010   /* ignore f6 if the handle is focused */
1011   if (gtk_widget_is_focus (GTK_WIDGET (paned)))
1012     return TRUE;
1013   
1014   /* we can't just let the event propagate up the hierarchy,
1015    * because the paned will want to cycle focus _unless_ an
1016    * ancestor paned handles the event
1017    */
1018   gtk_paned_get_cycle_chain (paned, direction, &cycle_chain);
1019
1020   for (list = cycle_chain; list != NULL; list = list->next)
1021     if (gtk_widget_child_focus (GTK_WIDGET (list->data), direction))
1022       break;
1023
1024   g_list_free (cycle_chain);
1025   
1026   return TRUE;
1027 }
1028
1029 static void
1030 get_child_panes (GtkWidget  *widget,
1031                  GList     **panes)
1032 {
1033   if (GTK_IS_PANED (widget))
1034     {
1035       GtkPaned *paned = GTK_PANED (widget);
1036       
1037       get_child_panes (paned->child1, panes);
1038       *panes = g_list_prepend (*panes, widget);
1039       get_child_panes (paned->child2, panes);
1040     }
1041   else if (GTK_IS_CONTAINER (widget))
1042     {
1043       gtk_container_foreach (GTK_CONTAINER (widget),
1044                              (GtkCallback)get_child_panes, panes);
1045     }
1046 }
1047
1048 static GList *
1049 get_all_panes (GtkPaned *paned)
1050 {
1051   GtkPaned *topmost = NULL;
1052   GList *result = NULL;
1053   GtkWidget *w;
1054   
1055   for (w = GTK_WIDGET (paned); w != NULL; w = w->parent)
1056     {
1057       if (GTK_IS_PANED (w))
1058         topmost = GTK_PANED (w);
1059     }
1060
1061   g_assert (topmost);
1062
1063   get_child_panes (GTK_WIDGET (topmost), &result);
1064
1065   return g_list_reverse (result);
1066 }
1067
1068 static void
1069 gtk_paned_find_neighbours (GtkPaned  *paned,
1070                            GtkPaned **next,
1071                            GtkPaned **prev)
1072 {
1073   GList *all_panes = get_all_panes (paned);
1074   GList *this_link;
1075
1076   g_assert (all_panes);
1077
1078   this_link = g_list_find (all_panes, paned);
1079
1080   g_assert (this_link);
1081   
1082   if (this_link->next)
1083     *next = this_link->next->data;
1084   else
1085     *next = all_panes->data;
1086
1087   if (this_link->prev)
1088     *prev = this_link->prev->data;
1089   else
1090     *prev = g_list_last (all_panes)->data;
1091
1092   if (*next == paned)
1093     *next = NULL;
1094
1095   if (*prev == paned)
1096     *prev = NULL;
1097
1098   g_list_free (all_panes);
1099 }
1100
1101 static gboolean
1102 gtk_paned_move_handle (GtkPaned      *paned,
1103                        GtkScrollType  scroll)
1104 {
1105   if (gtk_widget_is_focus (GTK_WIDGET (paned)))
1106     {
1107       gint old_position;
1108       gint new_position;
1109       
1110       enum {
1111         SINGLE_STEP_SIZE = 1,
1112         PAGE_STEP_SIZE   = 75,
1113       };
1114       
1115       old_position = gtk_paned_get_position (paned);
1116       
1117       switch (scroll)
1118         {
1119         case GTK_SCROLL_STEP_LEFT:
1120         case GTK_SCROLL_STEP_UP:
1121         case GTK_SCROLL_STEP_BACKWARD:
1122           new_position = old_position - SINGLE_STEP_SIZE;
1123           break;
1124           
1125         case GTK_SCROLL_STEP_RIGHT:
1126         case GTK_SCROLL_STEP_DOWN:
1127         case GTK_SCROLL_STEP_FORWARD:
1128           new_position = old_position + SINGLE_STEP_SIZE;
1129           break;
1130           
1131         case GTK_SCROLL_PAGE_LEFT:
1132         case GTK_SCROLL_PAGE_UP:
1133         case GTK_SCROLL_PAGE_BACKWARD:
1134           new_position = old_position - PAGE_STEP_SIZE;
1135           break;
1136           
1137         case GTK_SCROLL_PAGE_RIGHT:
1138         case GTK_SCROLL_PAGE_DOWN:
1139         case GTK_SCROLL_PAGE_FORWARD:
1140           new_position = old_position + PAGE_STEP_SIZE;
1141           break;
1142           
1143         case GTK_SCROLL_START:
1144           new_position = paned->min_position;
1145           break;
1146           
1147         case GTK_SCROLL_END:
1148           new_position = paned->max_position;
1149           break;
1150           
1151         default:
1152           new_position = old_position;
1153           break;
1154         }
1155       
1156       new_position = CLAMP (new_position, paned->min_position, paned->max_position);
1157       
1158       if (old_position != new_position)
1159         gtk_paned_set_position (paned, new_position);
1160
1161       return TRUE;
1162     }
1163
1164   return FALSE;
1165 }
1166
1167 static void
1168 gtk_paned_restore_focus (GtkPaned *paned)
1169 {
1170   if (gtk_widget_is_focus (GTK_WIDGET (paned)))
1171     {
1172       if (paned->saved_focus && GTK_WIDGET_SENSITIVE (paned->saved_focus))
1173         {
1174           gtk_widget_grab_focus (paned->saved_focus);
1175         }
1176       else
1177         {
1178           /* the saved focus is somehow not available for focusing,
1179            * try
1180            *   1) tabbing into the paned widget
1181            * if that didn't work,
1182            *   2) unset focus for the window if there is one
1183            */
1184           
1185           if (!gtk_widget_child_focus (GTK_WIDGET (paned), GTK_DIR_TAB_FORWARD))
1186             {
1187               GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (paned));
1188               
1189               if (GTK_IS_WINDOW (toplevel))
1190                 gtk_window_set_focus (GTK_WINDOW (toplevel), NULL);
1191             }
1192         }
1193       
1194       gtk_paned_set_saved_focus (paned, NULL);
1195     }
1196 }
1197
1198 static gboolean
1199 gtk_paned_accept_position (GtkPaned *paned)
1200 {
1201   if (gtk_widget_is_focus (GTK_WIDGET (paned)))
1202     {
1203       paned->original_position = -1;
1204       gtk_paned_restore_focus (paned);
1205
1206       return TRUE;
1207     }
1208
1209   return FALSE;
1210 }
1211
1212
1213 static gboolean
1214 gtk_paned_cancel_position (GtkPaned *paned)
1215 {
1216   if (gtk_widget_is_focus (GTK_WIDGET (paned)))
1217     {
1218       if (paned->original_position != -1)
1219         {
1220           gtk_paned_set_position (paned, paned->original_position);
1221           paned->original_position = -1;
1222         }
1223
1224       gtk_paned_restore_focus (paned);
1225       return TRUE;
1226     }
1227
1228   return FALSE;
1229 }
1230
1231 static gboolean
1232 gtk_paned_cycle_handle_focus (GtkPaned *paned,
1233                               gboolean  reversed)
1234 {
1235   if (gtk_widget_is_focus (GTK_WIDGET (paned)))
1236     {
1237       GtkPaned *next, *prev;
1238       GtkPaned *focus = NULL;
1239
1240       gtk_paned_find_neighbours (paned, &next, &prev);
1241
1242       if (reversed && prev)
1243         focus = prev;
1244       else if (!reversed && next)
1245         focus = next;
1246
1247       if (focus)
1248         {
1249           gtk_paned_set_saved_focus (focus, paned->saved_focus);
1250           gtk_paned_set_saved_focus (paned, NULL);
1251           gtk_widget_grab_focus (GTK_WIDGET (focus));
1252
1253           if (!gtk_widget_is_focus (GTK_WIDGET (paned)))
1254             {
1255               paned->original_position = -1;
1256               focus->original_position = gtk_paned_get_position (focus);
1257             }
1258         }
1259       
1260       return TRUE;
1261     }
1262
1263   return FALSE;
1264 }
1265
1266 static gboolean
1267 gtk_paned_toggle_handle_focus (GtkPaned *paned)
1268 {
1269   if (gtk_widget_is_focus (GTK_WIDGET (paned)))
1270     {
1271       gtk_paned_accept_position (paned);
1272     }
1273   else
1274     {
1275       GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (paned));
1276
1277       if (GTK_IS_WINDOW (toplevel))
1278         gtk_paned_set_saved_focus (paned, GTK_WINDOW (toplevel)->focus_widget);
1279   
1280       gtk_widget_grab_focus (GTK_WIDGET (paned));
1281       paned->original_position = gtk_paned_get_position (paned);
1282     }
1283
1284   return TRUE;
1285 }