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