Code

Add handling of focus tabbing for GdlDockItem + various DockItem focus
[inkscape.git] / src / libgdl / gdl-dock-paned.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  *
3  * gdl-dock-paned.h
4  *
5  * This file is part of the GNOME Devtools Libraries.
6  *
7  * Copyright (C) 2002 Gustavo Giráldez <gustavo.giraldez@gmx.net>
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
22  */
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
28 #include "gdl-i18n.h"
29 #include <string.h>
30 #include <gtk/gtkhpaned.h>
31 #include <gtk/gtkvpaned.h>
33 #include "gdl-tools.h"
34 #include "gdl-dock-paned.h"
37 /* Private prototypes */
39 static void     gdl_dock_paned_class_init     (GdlDockPanedClass *klass);
40 static void     gdl_dock_paned_instance_init  (GdlDockPaned      *paned);
41 static GObject *gdl_dock_paned_constructor    (GType              type,
42                                                guint              n_construct_properties,
43                                                GObjectConstructParam *construct_param);
44 static void     gdl_dock_paned_set_property   (GObject           *object,
45                                                guint              prop_id,
46                                                const GValue      *value,
47                                                GParamSpec        *pspec);
48 static void     gdl_dock_paned_get_property   (GObject           *object,
49                                                guint              prop_id,
50                                                GValue            *value,
51                                                GParamSpec        *pspec);
53 static void     gdl_dock_paned_destroy        (GtkObject         *object);
55 static void     gdl_dock_paned_add            (GtkContainer      *container,
56                                                GtkWidget         *widget);
57 static void     gdl_dock_paned_forall         (GtkContainer      *container,
58                                                gboolean           include_internals,
59                                                GtkCallback        callback,
60                                                gpointer           callback_data);
61 static GType    gdl_dock_paned_child_type     (GtkContainer      *container);
63 static gboolean gdl_dock_paned_dock_request   (GdlDockObject     *object, 
64                                                gint               x,
65                                                gint               y, 
66                                                GdlDockRequest    *request);
67 static void     gdl_dock_paned_dock           (GdlDockObject    *object,
68                                                GdlDockObject    *requestor,
69                                                GdlDockPlacement  position,
70                                                GValue           *other_data);
72 static void     gdl_dock_paned_set_orientation (GdlDockItem    *item,
73                                                 GtkOrientation  orientation);
75 static gboolean gdl_dock_paned_child_placement (GdlDockObject    *object,
76                                                 GdlDockObject    *child,
77                                                 GdlDockPlacement *placement);
80 /* ----- Class variables and definitions ----- */
82 #define SPLIT_RATIO  0.3
84 enum {
85     PROP_0,
86     PROP_POSITION
87 };
90 /* ----- Private functions ----- */
92 GDL_CLASS_BOILERPLATE (GdlDockPaned, gdl_dock_paned, GdlDockItem, GDL_TYPE_DOCK_ITEM);
94 static void
95 gdl_dock_paned_class_init (GdlDockPanedClass *klass)
96 {
97     GObjectClass       *g_object_class;
98     GtkObjectClass     *gtk_object_class;
99     GtkWidgetClass     *widget_class;
100     GtkContainerClass  *container_class;
101     GdlDockObjectClass *object_class;
102     GdlDockItemClass   *item_class;
104     g_object_class = G_OBJECT_CLASS (klass);
105     gtk_object_class = GTK_OBJECT_CLASS (klass);
106     widget_class = GTK_WIDGET_CLASS (klass);
107     container_class = GTK_CONTAINER_CLASS (klass);
108     object_class = GDL_DOCK_OBJECT_CLASS (klass);
109     item_class = GDL_DOCK_ITEM_CLASS (klass);
111     g_object_class->set_property = gdl_dock_paned_set_property;
112     g_object_class->get_property = gdl_dock_paned_get_property;
113     g_object_class->constructor = gdl_dock_paned_constructor;
114     
115     gtk_object_class->destroy = gdl_dock_paned_destroy;
117     container_class->add = gdl_dock_paned_add;
118     container_class->forall = gdl_dock_paned_forall;
119     container_class->child_type = gdl_dock_paned_child_type;
120     
121     object_class->is_compound = TRUE;
122     
123     object_class->dock_request = gdl_dock_paned_dock_request;
124     object_class->dock = gdl_dock_paned_dock;
125     object_class->child_placement = gdl_dock_paned_child_placement;
126     
127     item_class->has_grip = FALSE;
128     item_class->set_orientation = gdl_dock_paned_set_orientation;    
130     g_object_class_install_property (
131         g_object_class, PROP_POSITION,
132         g_param_spec_uint ("position", _("Position"),
133                            _("Position of the divider in pixels"),
134                            0, G_MAXINT, 0,
135                            G_PARAM_READWRITE |
136                            GDL_DOCK_PARAM_EXPORT | GDL_DOCK_PARAM_AFTER));
139 static void
140 gdl_dock_paned_instance_init (GdlDockPaned *paned)
142     paned->position_changed = FALSE;
145 static void 
146 gdl_dock_paned_notify_cb (GObject    *g_object,
147                           GParamSpec *pspec,
148                           gpointer    user_data) 
150     GdlDockPaned *paned;
151     
152     g_return_if_fail (user_data != NULL && GDL_IS_DOCK_PANED (user_data));
153     
154     /* chain the notification to the GdlDockPaned */
155     g_object_notify (G_OBJECT (user_data), pspec->name);
156     
157     paned = GDL_DOCK_PANED (user_data);
158     
159     if (GDL_DOCK_ITEM_USER_ACTION (user_data) && !strcmp (pspec->name, "position"))
160         paned->position_changed = TRUE;
163 static gboolean 
164 gdl_dock_paned_button_cb (GtkWidget      *widget,
165                           GdkEventButton *event,
166                           gpointer        user_data)
168     GdlDockPaned *paned;
169     
170     g_return_val_if_fail (user_data != NULL && GDL_IS_DOCK_PANED (user_data), FALSE);
171     
172     paned = GDL_DOCK_PANED (user_data);
173     if (event->button == 1) {
174         if (event->type == GDK_BUTTON_PRESS)
175             GDL_DOCK_ITEM_SET_FLAGS (user_data, GDL_DOCK_USER_ACTION);
176         else {
177             GDL_DOCK_ITEM_UNSET_FLAGS (user_data, GDL_DOCK_USER_ACTION);
178             if (paned->position_changed) {
179                 /* emit pending layout changed signal to track separator position */
180                 if (GDL_DOCK_OBJECT (paned)->master)
181                     g_signal_emit_by_name (GDL_DOCK_OBJECT (paned)->master, "layout-changed");
182                 paned->position_changed = FALSE;
183             }
184         }
185     }
186     
187     return FALSE;
190 static void 
191 gdl_dock_paned_create_child (GdlDockPaned   *paned,
192                              GtkOrientation  orientation) 
194     GdlDockItem *item;
195     
196     item = GDL_DOCK_ITEM (paned);
197     
198     if (item->child)
199         gtk_widget_unparent (GTK_WIDGET (item->child));
200     
201     /* create the container paned */
202     if (orientation == GTK_ORIENTATION_HORIZONTAL)
203         item->child = gtk_hpaned_new ();
204     else
205         item->child = gtk_vpaned_new ();
206     
207     /* get notification for propagation */
208     g_signal_connect (item->child, "notify::position",
209                       (GCallback) gdl_dock_paned_notify_cb, (gpointer) item);
210     g_signal_connect (item->child, "button-press-event",
211                       (GCallback) gdl_dock_paned_button_cb, (gpointer) item);
212     g_signal_connect (item->child, "button-release-event",
213                       (GCallback) gdl_dock_paned_button_cb, (gpointer) item);
214     
215     gtk_widget_set_parent (item->child, GTK_WIDGET (item));
216     gtk_widget_show (item->child);
219 static GObject *
220 gdl_dock_paned_constructor (GType                  type,
221                             guint                  n_construct_properties,
222                             GObjectConstructParam *construct_param)
224     GObject *g_object;
225     
226     g_object = GDL_CALL_PARENT_WITH_DEFAULT (G_OBJECT_CLASS, 
227                                                constructor, 
228                                                (type,
229                                                 n_construct_properties,
230                                                 construct_param),
231                                                NULL);
232     if (g_object) {
233         GdlDockItem *item = GDL_DOCK_ITEM (g_object);
234         
235         if (!item->child)
236             gdl_dock_paned_create_child (GDL_DOCK_PANED (g_object),
237                                          item->orientation);
238         /* otherwise, the orientation was set as a construction
239            parameter and the child is already created */
240     }
241     
242     return g_object;
245 static void
246 gdl_dock_paned_set_property (GObject        *object,
247                              guint           prop_id,
248                              const GValue   *value,
249                              GParamSpec     *pspec)
251     GdlDockItem *item = GDL_DOCK_ITEM (object);
252       
253     switch (prop_id) {
254         case PROP_POSITION:
255             if (item->child && GTK_IS_PANED (item->child))
256                 gtk_paned_set_position (GTK_PANED (item->child),
257                                         g_value_get_uint (value));
258             break;
259         default:
260             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
261             break;
262     }
265 static void
266 gdl_dock_paned_get_property (GObject        *object,
267                              guint           prop_id,
268                              GValue         *value,
269                              GParamSpec     *pspec)
271     GdlDockItem *item = GDL_DOCK_ITEM (object);
272       
273     switch (prop_id) {
274         case PROP_POSITION:
275             if (item->child && GTK_IS_PANED (item->child))
276                 g_value_set_uint (value,
277                                   gtk_paned_get_position (GTK_PANED (item->child)));
278             else
279                 g_value_set_uint (value, 0);
280             break;
281         default:
282             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
283             break;
284     }
287 static void
288 gdl_dock_paned_destroy (GtkObject *object)
290     GdlDockItem *item = GDL_DOCK_ITEM (object);
292     /* we need to call the virtual first, since in GdlDockDestroy our
293        children dock objects are detached */
294     GDL_CALL_PARENT (GTK_OBJECT_CLASS, destroy, (object));
296     /* after that we can remove the GtkNotebook */
297     if (item->child) {
298         gtk_widget_unparent (item->child);
299         item->child = NULL;
300     };
303 static void
304 gdl_dock_paned_add (GtkContainer *container,
305                     GtkWidget    *widget)
307     GdlDockItem     *item;
308     GtkPaned        *paned;
309     GdlDockPlacement pos = GDL_DOCK_NONE;
310     
311     g_return_if_fail (container != NULL && widget != NULL);
312     g_return_if_fail (GDL_IS_DOCK_PANED (container));
313     g_return_if_fail (GDL_IS_DOCK_ITEM (widget));
315     item = GDL_DOCK_ITEM (container);
316     g_return_if_fail (item->child != NULL);
317     paned = GTK_PANED (item->child);
318     g_return_if_fail (!paned->child1 || !paned->child2);
320     if (!paned->child1)
321         pos = item->orientation == GTK_ORIENTATION_HORIZONTAL ?
322             GDL_DOCK_LEFT : GDL_DOCK_TOP;
323     else if (!paned->child2)
324         pos = item->orientation == GTK_ORIENTATION_HORIZONTAL ?
325             GDL_DOCK_RIGHT : GDL_DOCK_BOTTOM;
327     if (pos != GDL_DOCK_NONE)
328         gdl_dock_object_dock (GDL_DOCK_OBJECT (container),
329                               GDL_DOCK_OBJECT (widget),
330                               pos, NULL);
333 static void
334 gdl_dock_paned_forall (GtkContainer *container,
335                        gboolean      include_internals,
336                        GtkCallback   callback,
337                        gpointer      callback_data)
339     GdlDockItem *item;
341     g_return_if_fail (container != NULL);
342     g_return_if_fail (GDL_IS_DOCK_PANED (container));
343     g_return_if_fail (callback != NULL);
345     if (include_internals) {
346         /* use GdlDockItem's forall */
347         GDL_CALL_PARENT (GTK_CONTAINER_CLASS, forall, 
348                            (container, include_internals, callback, callback_data));
349     }
350     else {
351         item = GDL_DOCK_ITEM (container);
352         if (item->child)
353             gtk_container_foreach (GTK_CONTAINER (item->child), callback, callback_data);
354     }
357 static GType
358 gdl_dock_paned_child_type (GtkContainer *container)
360     GdlDockItem *item = GDL_DOCK_ITEM (container);
362     if (gtk_container_child_type (GTK_CONTAINER (item->child)) == G_TYPE_NONE)
363         return G_TYPE_NONE;
364     else
365         return GDL_TYPE_DOCK_ITEM;
368 static void
369 gdl_dock_paned_request_foreach (GdlDockObject *object,
370                                 gpointer       user_data)
372     struct {
373         gint            x, y;
374         GdlDockRequest *request;
375         gboolean        may_dock;
376     } *data = user_data;
377     
378     GdlDockRequest my_request;
379     gboolean       may_dock;
380     
381     my_request = *data->request;
382     may_dock = gdl_dock_object_dock_request (object, data->x, data->y, &my_request);
383     if (may_dock) {
384         data->may_dock = TRUE;
385         *data->request = my_request;
386     }
389 static gboolean
390 gdl_dock_paned_dock_request (GdlDockObject  *object, 
391                              gint            x,
392                              gint            y, 
393                              GdlDockRequest *request)
395     GdlDockItem        *item;
396     guint               bw;
397     gint                rel_x, rel_y;
398     GtkAllocation      *alloc;
399     gboolean            may_dock = FALSE;
400     GdlDockRequest      my_request;
402     g_return_val_if_fail (GDL_IS_DOCK_ITEM (object), FALSE);
404     /* we get (x,y) in our allocation coordinates system */
405     
406     item = GDL_DOCK_ITEM (object);
407     
408     /* Get item's allocation. */
409     alloc = &(GTK_WIDGET (object)->allocation);
410     bw = GTK_CONTAINER (object)->border_width;
412     /* Get coordinates relative to our window. */
413     rel_x = x - alloc->x;
414     rel_y = y - alloc->y;
416     if (request)
417         my_request = *request;
418         
419     /* Check if coordinates are inside the widget. */
420     if (rel_x > 0 && rel_x < alloc->width &&
421         rel_y > 0 && rel_y < alloc->height) {
422         GtkRequisition my, other;
423         gint divider = -1;
424         
425         gdl_dock_item_preferred_size (GDL_DOCK_ITEM (my_request.applicant), &other);
426         gdl_dock_item_preferred_size (GDL_DOCK_ITEM (object), &my);
428         /* It's inside our area. */
429         may_dock = TRUE;
431         /* Set docking indicator rectangle to the widget size. */
432         my_request.rect.x = bw;
433         my_request.rect.y = bw;
434         my_request.rect.width = alloc->width - 2*bw;
435         my_request.rect.height = alloc->height - 2*bw;
437         my_request.target = object;
439         /* See if it's in the border_width band. */
440         if (rel_x < bw) {
441             my_request.position = GDL_DOCK_LEFT;
442             my_request.rect.width *= SPLIT_RATIO;
443             divider = other.width;
444         } else if (rel_x > alloc->width - bw) {
445             my_request.position = GDL_DOCK_RIGHT;
446             my_request.rect.x += my_request.rect.width * (1 - SPLIT_RATIO);
447             my_request.rect.width *= SPLIT_RATIO;
448             divider = MAX (0, my.width - other.width);
449         } else if (rel_y < bw) {
450             my_request.position = GDL_DOCK_TOP;
451             my_request.rect.height *= SPLIT_RATIO;
452             divider = other.height;
453         } else if (rel_y > alloc->height - bw) {
454             my_request.position = GDL_DOCK_BOTTOM;
455             my_request.rect.y += my_request.rect.height * (1 - SPLIT_RATIO);
456             my_request.rect.height *= SPLIT_RATIO;
457             divider = MAX (0, my.height - other.height);
458             
459         } else { /* Otherwise try our children. */
460             struct {
461                 gint            x, y;
462                 GdlDockRequest *request;
463                 gboolean        may_dock;
464             } data;
466             /* give them coordinates in their allocation system... the
467                GtkPaned has no window, so our children allocation
468                coordinates are our window coordinates */
469             data.x = rel_x;
470             data.y = rel_y;
471             data.request = &my_request;
472             data.may_dock = FALSE;
473             
474             gtk_container_foreach (GTK_CONTAINER (object),
475                                    (GtkCallback) gdl_dock_paned_request_foreach,
476                                    &data);
478             may_dock = data.may_dock;
479             if (!may_dock) {
480                 /* the pointer is on the handle, so snap to top/bottom
481                    or left/right */
482                 may_dock = TRUE;
483                 if (item->orientation == GTK_ORIENTATION_HORIZONTAL) {
484                     if (rel_y < alloc->height / 2) {
485                         my_request.position = GDL_DOCK_TOP;
486                         my_request.rect.height *= SPLIT_RATIO;
487                         divider = other.height;
488                     } else {
489                         my_request.position = GDL_DOCK_BOTTOM;
490                         my_request.rect.y += my_request.rect.height * (1 - SPLIT_RATIO);
491                         my_request.rect.height *= SPLIT_RATIO;
492                         divider = MAX (0, my.height - other.height);
493                     }
494                 } else {
495                     if (rel_x < alloc->width / 2) {
496                         my_request.position = GDL_DOCK_LEFT;
497                         my_request.rect.width *= SPLIT_RATIO;
498                         divider = other.width;
499                     } else {
500                         my_request.position = GDL_DOCK_RIGHT;
501                         my_request.rect.x += my_request.rect.width * (1 - SPLIT_RATIO);
502                         my_request.rect.width *= SPLIT_RATIO;
503                         divider = MAX (0, my.width - other.width);
504                     }
505                 }
506             }
507         }
509         if (divider >= 0 && my_request.position != GDL_DOCK_CENTER) {
510             if (G_IS_VALUE (&my_request.extra))
511                 g_value_unset (&my_request.extra);
512             g_value_init (&my_request.extra, G_TYPE_UINT);
513             g_value_set_uint (&my_request.extra, (guint) divider);
514         }
515         
516         if (may_dock) {
517             /* adjust returned coordinates so they are relative to
518                our allocation */
519             my_request.rect.x += alloc->x;
520             my_request.rect.y += alloc->y;
521         }
522     }
524     if (may_dock && request)
525         *request = my_request;
526     
527     return may_dock;
530 static void
531 gdl_dock_paned_dock (GdlDockObject    *object,
532                      GdlDockObject    *requestor,
533                      GdlDockPlacement  position,
534                      GValue           *other_data)
536     GtkPaned *paned;
537     gboolean  done = FALSE;
538     gboolean  hresize = FALSE;
539     gboolean  wresize = FALSE;
540     gint      temp = 0;
541     
542     g_return_if_fail (GDL_IS_DOCK_PANED (object));
543     g_return_if_fail (GDL_DOCK_ITEM (object)->child != NULL);
545     paned = GTK_PANED (GDL_DOCK_ITEM (object)->child);
547     if (GDL_IS_DOCK_ITEM (requestor)) {
548         g_object_get (G_OBJECT (requestor), "preferred_height", &temp, NULL);
549         if (temp == -2)
550             hresize = TRUE;
551         temp = 0;
552         g_object_get (G_OBJECT (requestor), "preferred_width", &temp, NULL);
553         if (temp == -2)
554             wresize = TRUE;
555     }
557     /* see if we can dock the item in our paned */
558     switch (GDL_DOCK_ITEM (object)->orientation) {
559         case GTK_ORIENTATION_HORIZONTAL:
560             if (!paned->child1 && position == GDL_DOCK_LEFT) {
561                 gtk_paned_pack1 (paned, GTK_WIDGET (requestor), FALSE, FALSE);
562                 done = TRUE;
563             } else if (!paned->child2 && position == GDL_DOCK_RIGHT) {
564                 gtk_paned_pack2 (paned, GTK_WIDGET (requestor), TRUE, FALSE);
565                 done = TRUE;
566             }
567             break;
568         case GTK_ORIENTATION_VERTICAL:
569             if (!paned->child1 && position == GDL_DOCK_TOP) {
570                 gtk_paned_pack1 (paned, GTK_WIDGET (requestor), hresize, FALSE);
571                 done = TRUE;
572             } else if (!paned->child2 && position == GDL_DOCK_BOTTOM) {
573                 gtk_paned_pack2 (paned, GTK_WIDGET (requestor), hresize, FALSE);
574                 done = TRUE;
575             }
576             break;
577         default:
578             break;
579     }
581     if (!done) {
582         /* this will create another paned and reparent us there */
583         GDL_CALL_PARENT (GDL_DOCK_OBJECT_CLASS, dock, (object, requestor, position,
584                                                          other_data));
585     }
586     else {
587         gdl_dock_item_show_grip (GDL_DOCK_ITEM (requestor));
588         GDL_DOCK_OBJECT_SET_FLAGS (requestor, GDL_DOCK_ATTACHED);
589     }
592 static void
593 gdl_dock_paned_set_orientation (GdlDockItem    *item,
594                                 GtkOrientation  orientation)
596     GtkPaned    *old_paned = NULL, *new_paned;
597     GtkWidget   *child1, *child2;
598     
599     g_return_if_fail (GDL_IS_DOCK_PANED (item));
601     if (item->child) {
602         old_paned = GTK_PANED (item->child);
603         g_object_ref (old_paned);
604         gtk_widget_unparent (GTK_WIDGET (old_paned));
605         item->child = NULL;
606     }
607     
608     gdl_dock_paned_create_child (GDL_DOCK_PANED (item), orientation);
609     
610     if (old_paned) {
611         new_paned = GTK_PANED (item->child);
612         child1 = old_paned->child1;
613         child2 = old_paned->child2;
614     
615         if (child1) {
616             g_object_ref (child1);
617             gtk_container_remove (GTK_CONTAINER (old_paned), child1);
618             gtk_paned_pack1 (new_paned, child1, TRUE, FALSE);
619             g_object_unref (child1);
620         }
621         if (child2) {
622             g_object_ref (child2);
623             gtk_container_remove (GTK_CONTAINER (old_paned), child2);
624             gtk_paned_pack1 (new_paned, child2, TRUE, FALSE);
625             g_object_unref (child2);
626         }
627     }
628     
629     GDL_CALL_PARENT (GDL_DOCK_ITEM_CLASS, set_orientation, (item, orientation));
632 static gboolean 
633 gdl_dock_paned_child_placement (GdlDockObject    *object,
634                                 GdlDockObject    *child,
635                                 GdlDockPlacement *placement)
637     GdlDockItem      *item = GDL_DOCK_ITEM (object);
638     GtkPaned         *paned;
639     GdlDockPlacement  pos = GDL_DOCK_NONE;
640     
641     if (item->child) {
642         paned = GTK_PANED (item->child);
643         if (GTK_WIDGET (child) == paned->child1)
644             pos = item->orientation == GTK_ORIENTATION_HORIZONTAL ?
645                 GDL_DOCK_LEFT : GDL_DOCK_TOP;
646         else if (GTK_WIDGET (child) == paned->child2)
647             pos = item->orientation == GTK_ORIENTATION_HORIZONTAL ?
648                 GDL_DOCK_RIGHT : GDL_DOCK_BOTTOM;
649     }
651     if (pos != GDL_DOCK_NONE) {
652         if (placement)
653             *placement = pos;
654         return TRUE;
655     }
656     else
657         return FALSE;
661 /* ----- Public interface ----- */
663 GtkWidget *
664 gdl_dock_paned_new (GtkOrientation orientation)
666     GdlDockPaned *paned;
668     paned = GDL_DOCK_PANED (g_object_new (GDL_TYPE_DOCK_PANED,
669                                           "orientation", orientation, NULL));
670     GDL_DOCK_OBJECT_UNSET_FLAGS (paned, GDL_DOCK_AUTOMATIC);
671     
672     return GTK_WIDGET (paned);