eab32e3f5fcc5b088feb2c9b22b7b1eac4ea6e04
[hildon] / hildon-widgets / hildon-scroll-area.c
1 /*
2  * This file is part of hildon-libs
3  *
4  * Copyright (C) 2005 Nokia Corporation.
5  *
6  * Contact: Luc Pionchon <luc.pionchon@nokia.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public License
10  * as published by the Free Software Foundation; either version 2.1 of
11  * the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21  * 02110-1301 USA
22  *
23  */
24
25 /* hildon-scroll-area.c
26  *
27  */
28
29 #include "hildon-scroll-area.h"
30 #include <gtk/gtkscrolledwindow.h>
31 #include <gtk/gtkfixed.h>
32 #include <gtk/gtkadjustment.h>
33 #include <gtk/gtkwidget.h>
34 #include <string.h>
35
36 typedef struct
37   {
38     GtkWidget *fixed;
39     GtkWidget *swouter;
40     GtkWidget *swinner;
41     GtkWidget *child;
42
43     GtkAdjustment *outadj;
44     GtkAdjustment *inadj;
45
46   } HildonScrollArea;
47
48
49 static void hildon_scroll_area_outer_value_changed (GtkAdjustment *adjustment,
50                                                     HildonScrollArea *sc);
51 static void hildon_scroll_area_inner_value_changed (GtkAdjustment *adjustment,
52                                                     HildonScrollArea *sc);
53 static void hildon_scroll_area_size_allocate (GtkWidget *widget,
54                                               GtkAllocation *allocation,
55                                               HildonScrollArea *sc);
56 static void hildon_scroll_area_child_requisition (GtkWidget *widget,
57                                                   GtkRequisition *req,
58                                                   HildonScrollArea *sc);
59 static void hildon_scroll_area_fixed_allocate (GtkWidget *widget,
60                                                GtkAllocation *allocation,
61                                                HildonScrollArea *sc);
62
63 /**
64  * hildon_scroll_area_new:
65  * @sw: #GtkWidget - #GtkScrolledWindow
66  * @child: #GtkWidget - Child to be place inside the sw
67  *
68  * This is not a widget. It's a helper function to provide
69  * hildon specified scrolling for applications.
70  * Puts and connects the @child to the @sw.
71  * A common situation where the scroll area should be used
72  * might be following.  A view containing @GtkTreeView based widget,
73  * (or any similar widget which has built-in @GtkScrolledWindow support)
74  * and eg. couple buttons.  Normaly @GtkScrolledWindow can not handle
75  * the situation so that the @GtkTreeView built-in support
76  * would work.  The scroll area is connecting this built-in system to
77  * the scrolled window and also noticing the buttons.  To use, one should
78  * create a box to which pack the buttons and the scroll area.
79  * The scroll area then contains the problematic widget eg. the @GtkTreeView.
80  * Then the box should be placed in the @GtkScrolledWindow.
81  * The function is currently assuming that the newly created scroll area
82  * hierarchy is not modified in anyway.  Or if it is, it may lead to
83  * unwanted problems.  Also assumed, that the @child will be packed
84  * to the @sw.
85  *
86  * Returns: a @GtkFixed
87  */
88 GtkWidget *hildon_scroll_area_new (GtkWidget *sw, GtkWidget *child)
89 {
90   GtkWidget *swi;
91   GtkWidget *fixed;
92   HildonScrollArea *sc;
93
94   g_return_val_if_fail (GTK_IS_SCROLLED_WINDOW (sw)
95                         && GTK_IS_WIDGET (child), NULL);
96
97   swi = gtk_scrolled_window_new (NULL, NULL);
98   fixed = gtk_fixed_new ();
99   sc = g_malloc (sizeof (HildonScrollArea));
100   memset (sc, 0, sizeof (HildonScrollArea));
101
102   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swi),
103                                   GTK_POLICY_NEVER, GTK_POLICY_NEVER);
104
105   gtk_container_add (GTK_CONTAINER (swi), child);
106   gtk_fixed_put (GTK_FIXED (fixed), swi, 0, 0);
107
108   sc->fixed = fixed;
109   sc->swouter = sw;
110   sc->swinner = swi;
111   sc->child = child;
112   sc->outadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (sw));
113   sc->inadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (swi));
114
115   g_signal_connect_after (G_OBJECT (child), "size-request",
116                           G_CALLBACK (hildon_scroll_area_child_requisition), sc);
117
118   g_signal_connect_after (G_OBJECT (sc->outadj), "value_changed",
119                           G_CALLBACK (hildon_scroll_area_outer_value_changed), sc);
120   g_signal_connect_after (G_OBJECT (sc->inadj), "value_changed",
121                           G_CALLBACK (hildon_scroll_area_inner_value_changed), sc);
122
123   g_signal_connect_after (G_OBJECT (sw), "size-allocate",
124                           G_CALLBACK (hildon_scroll_area_size_allocate), sc);
125   g_signal_connect (G_OBJECT (sc->fixed), "size-allocate",
126                     G_CALLBACK (hildon_scroll_area_fixed_allocate), sc);
127   g_signal_connect_swapped (G_OBJECT (sw), "destroy",
128                     G_CALLBACK (g_free), sc);
129
130   gtk_widget_show_all (sw);
131   return fixed;
132 }
133
134 static void hildon_scroll_area_fixed_allocate (GtkWidget *widget,
135                                                GtkAllocation *allocation,
136                                                HildonScrollArea *sc)
137 {
138   gtk_widget_set_size_request (sc->swinner, -1,
139                                MIN (sc->outadj->page_size, allocation->height));
140 }
141
142 static void hildon_scroll_area_child_requisition (GtkWidget *widget,
143                                                   GtkRequisition *req,
144                                                   HildonScrollArea *sc)
145 {
146   gint new_req = MAX (req->height, sc->fixed->allocation.height);
147   gtk_widget_set_size_request (sc->fixed, -1, req->height);
148   gtk_widget_set_size_request (sc->swinner, -1,
149                                MIN (sc->outadj->page_size, new_req));
150 }
151
152 static void hildon_scroll_area_outer_value_changed (GtkAdjustment *adjustment,
153                                                     HildonScrollArea *sc)
154 {
155   GtkRequisition req;
156   gtk_widget_size_request (sc->child, &req);
157
158   if ((sc->outadj->value + sc->outadj->page_size) > sc->fixed->allocation.y
159       && sc->outadj->value < (sc->fixed->allocation.y + req.height))
160     {
161       gdouble new_pos = 0;
162
163       new_pos = MAX (sc->outadj->value - sc->fixed->allocation.y, 0);
164       new_pos = MIN (new_pos, req.height - sc->inadj->page_size);
165       new_pos = MAX (new_pos, 0);
166
167       gtk_fixed_move (GTK_FIXED (sc->fixed), sc->swinner, 0, new_pos);
168       gtk_adjustment_set_value (sc->inadj, new_pos);
169     }
170 }
171
172 static void hildon_scroll_area_inner_value_changed (GtkAdjustment *adjustment,
173                                                     HildonScrollArea *sc)
174 {
175   if (sc->outadj->value != sc->fixed->allocation.y + adjustment->value)
176     gtk_adjustment_set_value (sc->outadj,
177                               sc->fixed->allocation.y + adjustment->value);
178 }
179
180 __inline__ static gint calculate_width (HildonScrollArea *sc)
181 {
182   GtkScrolledWindow *scwin = GTK_SCROLLED_WINDOW (sc->swouter);
183   return (scwin->hscrollbar_visible * scwin->hscrollbar->allocation.width);
184 }
185
186 static void hildon_scroll_area_size_allocate (GtkWidget *widget,
187                                               GtkAllocation *allocation,
188                                               HildonScrollArea *sc)
189 {
190   gtk_widget_set_size_request (sc->fixed, calculate_width (sc), sc->fixed->allocation.height);
191   gtk_widget_set_size_request (sc->child, sc->fixed->allocation.width, -1);
192 }