Modified webpage: now tinymail repository is in gitorious.
[modest] / src / widgets / modest-scroll-area.c
1 /* Copyright (c) 2007, Nokia Corporation
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  *   notice, this list of conditions and the following disclaimer in the
12  *   documentation and/or other materials provided with the distribution.
13  * * Neither the name of the Nokia Corporation nor the names of its
14  *   contributors may be used to endorse or promote products derived from
15  *   this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30
31 /**
32  * SECTION:modest-scroll-area
33  * @short_description: A helper to create Maemo specific views,
34  * which are using scrollable area
35  *
36  * #GtkScrollArea combines a large widget that needs scrolling (like a
37  * text editor or a tree view) and other widgets that wouldn't fit one
38  * the screen normally without scrolling (like entries, toolbars etc.)
39  * into one scrollable area.
40  */
41
42 #include "modest-scroll-area.h"
43 #include <gtk/gtk.h>
44 #include <string.h>
45
46 typedef struct
47 {
48         GtkWidget *fixed;
49
50         /* Scrolled windows */
51         GtkWidget *swouter;
52         GtkWidget *swinner;
53
54         /* Widget that's being contained */
55         GtkWidget *child;
56
57         /* Vertical adjustment for scrolled windows */
58         GtkAdjustment *outadj;
59         GtkAdjustment *inadj;
60
61 } ModestScrollArea;
62
63
64 static void modest_scroll_area_outer_value_changed (GtkAdjustment *adjustment,
65                                                     ModestScrollArea *sc);
66 static void modest_scroll_area_inner_value_changed (GtkAdjustment *adjustment,
67                                                     ModestScrollArea *sc);
68 static void modest_scroll_area_size_allocate (GtkWidget *widget,
69                                               GtkAllocation *allocation,
70                                               ModestScrollArea *sc);
71 static void modest_scroll_area_child_requisition (GtkWidget *widget,
72                                                   GtkRequisition *req,
73                                                   ModestScrollArea *sc);
74 static void modest_scroll_area_fixed_allocate (GtkWidget *widget,
75                                                GtkAllocation *allocation,
76                                                ModestScrollArea *sc);
77
78 static int calculate_size (GtkWidget *widget);
79
80 /**
81  * modest_scroll_area_new:
82  * @sw: #GtkWidget - #GtkScrolledWindow
83  * @child: #GtkWidget - child to be place inside the sw
84  *
85  * This is not a widget. It's a helper function to create
86  * hildon-specific scrolling methods.
87  * A common situation where the scroll area should be used
88  * might be following.  A view containing @GtkTreeView based widget,
89  * (or any similar widget which has built-in @GtkScrolledWindow support)
90  * and eg. couple buttons.  Normaly @GtkScrolledWindow can not handle
91  * the situation so that the @GtkTreeView built-in support
92  * would work.  The scroll area is connecting this built-in system to
93  * the scrolled window and also noticing the buttons.  To use, one should
94  * create a box to which pack the buttons and the scroll area.
95  * The scroll area then contains the problematic widget eg. the @GtkTreeView.
96  * Then the box should be placed in the @GtkScrolledWindow.
97  * The function is currently assuming that the newly created scroll area
98  * hierarchy is not modified in anyway.  Or if it is, it may lead to
99  * unwanted problems.  Also assumed, that the @child will be packed
100  * to the @sw.
101  *
102  * Returns: a @GtkFixed
103  */
104 GtkWidget *
105 modest_scroll_area_new (GtkWidget *sw, GtkWidget *child)
106 {
107         GtkWidget *swi;
108         GtkWidget *fixed;
109         ModestScrollArea *sc;
110
111         g_return_val_if_fail (GTK_IS_SCROLLED_WINDOW (sw)
112                               && GTK_IS_WIDGET (child), NULL);
113
114         swi = gtk_scrolled_window_new (NULL, NULL);
115         fixed = gtk_fixed_new ();
116         sc = g_malloc (sizeof (ModestScrollArea));
117         memset (sc, 0, sizeof (ModestScrollArea));
118
119         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swi),
120                                         GTK_POLICY_NEVER, GTK_POLICY_NEVER);
121
122         gtk_container_add (GTK_CONTAINER (swi), child);
123         gtk_fixed_put (GTK_FIXED (fixed), swi, 0, 0);
124
125         sc->fixed = fixed;
126         sc->swouter = sw;
127         sc->swinner = swi;
128         sc->child = child;
129         sc->outadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (sw));
130         sc->inadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (swi));
131
132         g_signal_connect_after (G_OBJECT (child), "size-request",
133                                 G_CALLBACK (modest_scroll_area_child_requisition), sc);
134
135         g_signal_connect_after (G_OBJECT (sc->outadj), "value_changed",
136                                 G_CALLBACK (modest_scroll_area_outer_value_changed), sc);
137         g_signal_connect_after (G_OBJECT (sc->inadj), "value_changed",
138                                 G_CALLBACK (modest_scroll_area_inner_value_changed), sc);
139
140         g_signal_connect_after (G_OBJECT (sw), "size-allocate",
141                                 G_CALLBACK (modest_scroll_area_size_allocate), sc);
142         g_signal_connect (G_OBJECT (sc->fixed), "size-allocate",
143                           G_CALLBACK (modest_scroll_area_fixed_allocate), sc);
144         g_signal_connect_swapped (G_OBJECT (sw), "destroy",
145                                   G_CALLBACK (g_free), sc);
146
147         gtk_widget_show_all (sw);
148
149         gtk_widget_set_redraw_on_allocate (GTK_WIDGET(sc->fixed), FALSE);
150         gtk_widget_set_redraw_on_allocate (GTK_WIDGET(sw), FALSE);
151         gtk_widget_set_redraw_on_allocate (GTK_WIDGET(child), FALSE);
152         return fixed;
153 }
154
155 static void 
156 modest_scroll_area_fixed_allocate (GtkWidget *widget,
157                                    GtkAllocation *allocation,
158                                    ModestScrollArea *sc)
159 {
160         gtk_widget_set_size_request (sc->swinner, -1,
161                                      MIN (sc->outadj->page_size, allocation->height));
162 }
163
164
165 static int 
166 calculate_size (GtkWidget *widget)
167 {
168         int size = 0;
169         
170         if (GTK_IS_TEXT_VIEW (widget))
171                 return 0;
172         
173         if (GTK_IS_CONTAINER (widget)) {
174                 GList *tmp = NULL;
175                 GList *children = gtk_container_get_children (GTK_CONTAINER (widget));
176                 for (tmp = children; tmp != NULL; tmp = g_list_next (tmp)) {
177                         GtkWidget *wid = GTK_WIDGET (tmp->data);
178                         gint sz = calculate_size (wid);
179                         if ((GTK_WIDGET_VISIBLE (wid))) {
180                                 size += sz;
181                         }
182                 }
183                 g_list_free (children);
184         } else { 
185                 size = widget->allocation.height;
186         }
187         
188         return size;
189 }
190
191 static void 
192 modest_scroll_area_child_requisition (GtkWidget *widget,
193                                       GtkRequisition *req,
194                                       ModestScrollArea *sc)
195 {
196         /* Limit height to fixed height */
197         gint new_req = MAX (req->height, sc->fixed->allocation.height);
198         gint adjust_factor = calculate_size (sc->swouter) * 0.7;
199   
200         adjust_factor = MAX (0, adjust_factor - sc->outadj->value);
201         new_req = MIN (sc->outadj->page_size - adjust_factor, new_req);
202         
203         /* FIXME: hack, to provent gtk criticals */
204         if (new_req < -1)
205                 new_req = -1;
206                 
207         gtk_widget_set_size_request (sc->fixed, -1, req->height);
208         /* Request inner scrolled window at most page size */
209         gtk_widget_set_size_request (sc->swinner, -1, new_req);
210 }
211
212 static void 
213 modest_scroll_area_outer_value_changed (GtkAdjustment *adjustment,
214                                         ModestScrollArea *sc)
215 {
216         GtkRequisition req;
217
218         gtk_widget_size_request (sc->child, &req);
219
220         /* Update inner adjustment position based on outer one, update fixed position */
221         if ((sc->outadj->value + sc->outadj->page_size) > sc->fixed->allocation.y
222             && sc->outadj->value < (sc->fixed->allocation.y + req.height))
223         {
224                 gdouble new_pos = 0;
225
226                 new_pos = MAX (sc->outadj->value - sc->fixed->allocation.y, 0);
227                 new_pos = MIN (new_pos, req.height - sc->inadj->page_size);
228                 new_pos = MAX (new_pos, 0);
229
230                 gtk_fixed_move (GTK_FIXED (sc->fixed), sc->swinner, 0, new_pos);
231                 gtk_adjustment_set_value (sc->inadj, new_pos);
232         }
233 }
234
235 static void 
236 modest_scroll_area_inner_value_changed (GtkAdjustment *adjustment,
237                                                     ModestScrollArea *sc)
238 {
239         /* Update outer adjustment based on inner adjustment position */
240         if (sc->outadj->value != sc->fixed->allocation.y + adjustment->value)
241                 gtk_adjustment_set_value (sc->outadj,
242                                           sc->fixed->allocation.y + adjustment->value);
243 }
244
245 __inline__ static gint 
246 calculate_width (ModestScrollArea *sc)
247 {
248         GtkScrolledWindow *scwin = GTK_SCROLLED_WINDOW (sc->swouter);
249         return (scwin->hscrollbar_visible * scwin->hscrollbar->allocation.width);
250 }
251
252 static void 
253 modest_scroll_area_size_allocate (GtkWidget *widget,
254                                   GtkAllocation *allocation,
255                                   ModestScrollArea *sc)
256 {
257         g_return_if_fail (widget);
258         g_return_if_fail (allocation);
259         g_return_if_fail (sc);
260
261         gtk_widget_set_size_request (sc->fixed, calculate_width (sc), 
262                                      sc->fixed->allocation.height);
263         gtk_widget_set_size_request (sc->child, 
264                                      sc->fixed->allocation.width, -1);
265 }