37751b5f3ce69f40bf5d86ce818f9d761592a234
[hildon] / hildon-widgets / hildon-scroll-area.c
1 /*
2  * This file is part of hildon-libs
3  *
4  * Copyright (C) 2005, 2006 Nokia Corporation, all rights reserved.
5  *
6  * Contact: Michael Dominic Kostrzewa <michael.kostrzewa@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; version 2.1 of
11  * the License.
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 /**
26  * SECTION:hildon-scroll-area
27  * @short_description: A helper to create Maemo specific views,
28  * which are using scrollable area
29  *
30  * #GtkScrollArea combines a large widget that needs scrolling (like a
31  * text editor or a tree view) and other widgets that wouldn't fit one
32  * the screen normally without scrolling (like entries, toolbars etc.)
33  * into one scrollable area.
34  */
35
36 #include "hildon-scroll-area.h"
37 #include <gtk/gtkscrolledwindow.h>
38 #include <gtk/gtkfixed.h>
39 #include <gtk/gtkadjustment.h>
40 #include <gtk/gtkwidget.h>
41 #include <string.h>
42
43 typedef struct
44   {
45     GtkWidget *fixed;
46
47     /* Scrolled windows */
48     GtkWidget *swouter;
49     GtkWidget *swinner;
50
51     /* Widget that's being contained */
52     GtkWidget *child;
53
54     /* Vertical adjustment for scrolled windows */
55     GtkAdjustment *outadj;
56     GtkAdjustment *inadj;
57
58   } HildonScrollArea;
59
60
61 static void hildon_scroll_area_outer_value_changed (GtkAdjustment *adjustment,
62                                                     HildonScrollArea *sc);
63 static void hildon_scroll_area_inner_value_changed (GtkAdjustment *adjustment,
64                                                     HildonScrollArea *sc);
65 static void hildon_scroll_area_size_allocate (GtkWidget *widget,
66                                               GtkAllocation *allocation,
67                                               HildonScrollArea *sc);
68 static void hildon_scroll_area_child_requisition (GtkWidget *widget,
69                                                   GtkRequisition *req,
70                                                   HildonScrollArea *sc);
71 static void hildon_scroll_area_fixed_allocate (GtkWidget *widget,
72                                                GtkAllocation *allocation,
73                                                HildonScrollArea *sc);
74
75 static int calculate_size (GtkWidget *widget);
76
77 /**
78  * hildon_scroll_area_new:
79  * @sw: #GtkWidget - #GtkScrolledWindow
80  * @child: #GtkWidget - child to be place inside the sw
81  *
82  * This is not a widget. It's a helper function to create
83  * hildon-specific scrolling methods.
84  * A common situation where the scroll area should be used
85  * might be following.  A view containing @GtkTreeView based widget,
86  * (or any similar widget which has built-in @GtkScrolledWindow support)
87  * and eg. couple buttons.  Normaly @GtkScrolledWindow can not handle
88  * the situation so that the @GtkTreeView built-in support
89  * would work.  The scroll area is connecting this built-in system to
90  * the scrolled window and also noticing the buttons.  To use, one should
91  * create a box to which pack the buttons and the scroll area.
92  * The scroll area then contains the problematic widget eg. the @GtkTreeView.
93  * Then the box should be placed in the @GtkScrolledWindow.
94  * The function is currently assuming that the newly created scroll area
95  * hierarchy is not modified in anyway.  Or if it is, it may lead to
96  * unwanted problems.  Also assumed, that the @child will be packed
97  * to the @sw.
98  *
99  * Returns: a @GtkFixed
100  */
101 GtkWidget *hildon_scroll_area_new (GtkWidget *sw, GtkWidget *child)
102 {
103   GtkWidget *swi;
104   GtkWidget *fixed;
105   HildonScrollArea *sc;
106
107   g_return_val_if_fail (GTK_IS_SCROLLED_WINDOW (sw)
108                         && GTK_IS_WIDGET (child), NULL);
109
110   swi = gtk_scrolled_window_new (NULL, NULL);
111   fixed = gtk_fixed_new ();
112   sc = g_malloc (sizeof (HildonScrollArea));
113   memset (sc, 0, sizeof (HildonScrollArea));
114
115   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swi),
116                                   GTK_POLICY_NEVER, GTK_POLICY_NEVER);
117
118   gtk_container_add (GTK_CONTAINER (swi), child);
119   gtk_fixed_put (GTK_FIXED (fixed), swi, 0, 0);
120
121   sc->fixed = fixed;
122   sc->swouter = sw;
123   sc->swinner = swi;
124   sc->child = child;
125   sc->outadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (sw));
126   sc->inadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (swi));
127
128   g_signal_connect_after (G_OBJECT (child), "size-request",
129                           G_CALLBACK (hildon_scroll_area_child_requisition), sc);
130
131   g_signal_connect_after (G_OBJECT (sc->outadj), "value_changed",
132                           G_CALLBACK (hildon_scroll_area_outer_value_changed), sc);
133   g_signal_connect_after (G_OBJECT (sc->inadj), "value_changed",
134                           G_CALLBACK (hildon_scroll_area_inner_value_changed), sc);
135
136   g_signal_connect_after (G_OBJECT (sw), "size-allocate",
137                           G_CALLBACK (hildon_scroll_area_size_allocate), sc);
138   g_signal_connect (G_OBJECT (sc->fixed), "size-allocate",
139                     G_CALLBACK (hildon_scroll_area_fixed_allocate), sc);
140   g_signal_connect_swapped (G_OBJECT (sw), "destroy",
141                     G_CALLBACK (g_free), sc);
142
143   gtk_widget_show_all (sw);
144   return fixed;
145 }
146
147 static void hildon_scroll_area_fixed_allocate (GtkWidget *widget,
148                                                GtkAllocation *allocation,
149                                                HildonScrollArea *sc)
150 {
151   gtk_widget_set_size_request (sc->swinner, -1,
152                                MIN (sc->outadj->page_size, allocation->height));
153 }
154
155
156 static int calculate_size (GtkWidget *widget)
157 {
158   int size = 0;
159
160   if (GTK_IS_TEXT_VIEW (widget))
161     return 0;
162
163   if (GTK_IS_CONTAINER (widget)) {
164     GList *children = gtk_container_get_children (GTK_CONTAINER (widget));
165     while (children != NULL) {
166       GtkWidget *wid = GTK_WIDGET (children->data);
167       gint sz = calculate_size (wid);
168       if ((GTK_WIDGET_VISIBLE (wid))) {
169         size += sz;
170       }
171
172       children = g_list_next (children);
173     }
174   } else { 
175     size = widget->allocation.height;
176   }
177
178   return size;
179 }
180
181 static void hildon_scroll_area_child_requisition (GtkWidget *widget,
182                                                   GtkRequisition *req,
183                                                   HildonScrollArea *sc)
184 {
185   /* Limit height to fixed height */
186   gint new_req = MAX (req->height, sc->fixed->allocation.height);
187   new_req = MIN (sc->outadj->page_size - adjust_factor, new_req);
188   gint adjust_factor = calculate_size (sc->swouter) * 0.7;
189   adjust_factor = MAX (0, adjust_factor - sc->outadj->value);
190
191   gtk_widget_set_size_request (sc->fixed, -1, req->height);
192   /* Request inner scrolled window at most page size */
193   gtk_widget_set_size_request (sc->swinner, -1, new_req);
194 }
195
196 static void hildon_scroll_area_outer_value_changed (GtkAdjustment *adjustment,
197                                                     HildonScrollArea *sc)
198 {
199   GtkRequisition req;
200   gtk_widget_size_request (sc->child, &req);
201
202   /* Update inner adjustment position based on outer one, update fixed position */
203   if ((sc->outadj->value + sc->outadj->page_size) > sc->fixed->allocation.y
204       && sc->outadj->value < (sc->fixed->allocation.y + req.height))
205     {
206       gdouble new_pos = 0;
207
208       new_pos = MAX (sc->outadj->value - sc->fixed->allocation.y, 0);
209       new_pos = MIN (new_pos, req.height - sc->inadj->page_size);
210       new_pos = MAX (new_pos, 0);
211
212       gtk_fixed_move (GTK_FIXED (sc->fixed), sc->swinner, 0, new_pos);
213       gtk_adjustment_set_value (sc->inadj, new_pos);
214     }
215 }
216
217 static void hildon_scroll_area_inner_value_changed (GtkAdjustment *adjustment,
218                                                     HildonScrollArea *sc)
219 {
220   /* Update outer adjustment based on inner adjustment position */
221   if (sc->outadj->value != sc->fixed->allocation.y + adjustment->value)
222     gtk_adjustment_set_value (sc->outadj,
223                               sc->fixed->allocation.y + adjustment->value);
224 }
225
226 __inline__ static gint calculate_width (HildonScrollArea *sc)
227 {
228   GtkScrolledWindow *scwin = GTK_SCROLLED_WINDOW (sc->swouter);
229   return (scwin->hscrollbar_visible * scwin->hscrollbar->allocation.width);
230 }
231
232 static void hildon_scroll_area_size_allocate (GtkWidget *widget,
233                                               GtkAllocation *allocation,
234                                               HildonScrollArea *sc)
235 {
236   gtk_widget_set_size_request (sc->fixed, calculate_width (sc), sc->fixed->allocation.height);
237   gtk_widget_set_size_request (sc->child, sc->fixed->allocation.width, -1);
238 }