]> Pileus Git - ~andy/gtk/blob - gtk/gtkscrolledwindow.c
Enforce the widget/child realization/mapping invariants.
[~andy/gtk] / gtk / gtkscrolledwindow.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /*
21  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
22  * file for a list of people on the GTK+ Team.  See the ChangeLog
23  * files for a list of changes.  These files are distributed with
24  * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
25  */
26
27 #include "gtkscrolledwindow.h"
28 #include "gtksignal.h"
29
30
31 /* scrolled window policy and size requisition handling:
32  *
33  * gtk size requisition works as follows:
34  *   a widget upon size-request reports the width and height that it finds
35  *   to be best suited to display its contents, including children.
36  *   the width and/or height reported from a widget upon size requisition
37  *   may be overidden by the user by specifying a width and/or height
38  *   other than 0 through gtk_widget_set_usize().
39  *
40  * a scrolled window needs (for imlementing all three policy types) to
41  * request its width and height based on two different rationales.
42  * 1)   the user wants the scrolled window to just fit into the space
43  *      that it gets allocated for a specifc dimension.
44  * 1.1) this does not apply if the user specified a concrete value
45  *      value for that specific dimension by either specifying usize for the
46  *      scrolled window or for its child.
47  * 2)   the user wants the scrolled window to take as much space up as
48  *      is desired by the child for a specifc dimension (i.e. POLICY_NEVER).
49  *
50  * also, kinda obvious:
51  * 3)   a user would certainly not have choosen a scrolled window as a container
52  *      for the child, if the resulting allocation takes up more space than the
53  *      child would have allocated without the scrolled window.
54  *
55  * conclusions:
56  * A) from 1) follows: the scrolled window shouldn't request more space for a
57  *    specifc dimension than is required at minimum.
58  * B) from 1.1) follows: the requisition may be overidden by usize of the scrolled
59  *    window (done automatically) or by usize of the child (needs to be checked).
60  * C) from 2) follows: for POLICY_NEVER, the scrolled window simply reports the
61  *    child's dimension.
62  * D) from 3) follows: the scrolled window child's minimum width and minimum height
63  *    under A) at least correspond to the space taken up by its scrollbars.
64  */
65
66 #define SCROLLBAR_SPACING(w) (GTK_SCROLLED_WINDOW_GET_CLASS (w)->scrollbar_spacing)
67
68 #define DEFAULT_SCROLLBAR_SPACING  3
69
70 enum {
71   ARG_0,
72   ARG_HADJUSTMENT,
73   ARG_VADJUSTMENT,
74   ARG_HSCROLLBAR_POLICY,
75   ARG_VSCROLLBAR_POLICY,
76   ARG_WINDOW_PLACEMENT,
77   ARG_SHADOW
78 };
79
80
81 static void gtk_scrolled_window_class_init         (GtkScrolledWindowClass *klass);
82 static void gtk_scrolled_window_init               (GtkScrolledWindow      *scrolled_window);
83 static void gtk_scrolled_window_set_arg            (GtkObject              *object,
84                                                     GtkArg                 *arg,
85                                                     guint                   arg_id);
86 static void gtk_scrolled_window_get_arg            (GtkObject              *object,
87                                                     GtkArg                 *arg,
88                                                     guint                   arg_id);
89 static void gtk_scrolled_window_destroy            (GtkObject              *object);
90 static void gtk_scrolled_window_finalize           (GObject                *object);
91 static gint gtk_scrolled_window_expose             (GtkWidget              *widget,
92                                                     GdkEventExpose         *event);
93 static void gtk_scrolled_window_size_request       (GtkWidget              *widget,
94                                                     GtkRequisition         *requisition);
95 static void gtk_scrolled_window_size_allocate      (GtkWidget              *widget,
96                                                     GtkAllocation          *allocation);
97 static gint gtk_scrolled_window_scroll_event       (GtkWidget              *widget,
98                                                     GdkEventScroll         *event);
99 static void gtk_scrolled_window_add                (GtkContainer           *container,
100                                                     GtkWidget              *widget);
101 static void gtk_scrolled_window_remove             (GtkContainer           *container,
102                                                     GtkWidget              *widget);
103 static void gtk_scrolled_window_forall             (GtkContainer           *container,
104                                                     gboolean                include_internals,
105                                                     GtkCallback             callback,
106                                                     gpointer                callback_data);
107 static void gtk_scrolled_window_relative_allocation(GtkWidget              *widget,
108                                                     GtkAllocation          *allocation);
109 static void gtk_scrolled_window_adjustment_changed (GtkAdjustment          *adjustment,
110                                                     gpointer                data);
111
112 static GtkContainerClass *parent_class = NULL;
113
114
115 GtkType
116 gtk_scrolled_window_get_type (void)
117 {
118   static GtkType scrolled_window_type = 0;
119
120   if (!scrolled_window_type)
121     {
122       static const GtkTypeInfo scrolled_window_info =
123       {
124         "GtkScrolledWindow",
125         sizeof (GtkScrolledWindow),
126         sizeof (GtkScrolledWindowClass),
127         (GtkClassInitFunc) gtk_scrolled_window_class_init,
128         (GtkObjectInitFunc) gtk_scrolled_window_init,
129         /* reserved_1 */ NULL,
130         /* reserved_2 */ NULL,
131         (GtkClassInitFunc) NULL,
132       };
133
134       scrolled_window_type = gtk_type_unique (GTK_TYPE_BIN, &scrolled_window_info);
135     }
136
137   return scrolled_window_type;
138 }
139
140 static void
141 gtk_scrolled_window_class_init (GtkScrolledWindowClass *class)
142 {
143   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
144   GtkObjectClass *object_class;
145   GtkWidgetClass *widget_class;
146   GtkContainerClass *container_class;
147
148   object_class = (GtkObjectClass*) class;
149   widget_class = (GtkWidgetClass*) class;
150   container_class = (GtkContainerClass*) class;
151   parent_class = gtk_type_class (GTK_TYPE_BIN);
152
153   gobject_class->finalize = gtk_scrolled_window_finalize;
154
155   object_class->set_arg = gtk_scrolled_window_set_arg;
156   object_class->get_arg = gtk_scrolled_window_get_arg;
157   object_class->destroy = gtk_scrolled_window_destroy;
158
159   widget_class->expose_event = gtk_scrolled_window_expose;
160   widget_class->size_request = gtk_scrolled_window_size_request;
161   widget_class->size_allocate = gtk_scrolled_window_size_allocate;
162   widget_class->scroll_event = gtk_scrolled_window_scroll_event;
163
164   container_class->add = gtk_scrolled_window_add;
165   container_class->remove = gtk_scrolled_window_remove;
166   container_class->forall = gtk_scrolled_window_forall;
167
168   class->scrollbar_spacing = DEFAULT_SCROLLBAR_SPACING;
169
170   gtk_object_add_arg_type ("GtkScrolledWindow::hadjustment",
171                            GTK_TYPE_ADJUSTMENT,
172                            GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT,
173                            ARG_HADJUSTMENT);
174   gtk_object_add_arg_type ("GtkScrolledWindow::vadjustment",
175                            GTK_TYPE_ADJUSTMENT,
176                            GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT,
177                            ARG_VADJUSTMENT);
178   gtk_object_add_arg_type ("GtkScrolledWindow::hscrollbar_policy",
179                            GTK_TYPE_POLICY_TYPE,
180                            GTK_ARG_READWRITE,
181                            ARG_HSCROLLBAR_POLICY);
182   gtk_object_add_arg_type ("GtkScrolledWindow::vscrollbar_policy",
183                            GTK_TYPE_POLICY_TYPE,
184                            GTK_ARG_READWRITE,
185                            ARG_VSCROLLBAR_POLICY);
186   gtk_object_add_arg_type ("GtkScrolledWindow::window_placement",
187                            GTK_TYPE_CORNER_TYPE,
188                            GTK_ARG_READWRITE,
189                            ARG_WINDOW_PLACEMENT);
190   gtk_object_add_arg_type ("GtkScrolledWindow::shadow",
191                            GTK_TYPE_SHADOW_TYPE,
192                            GTK_ARG_READWRITE,
193                            ARG_SHADOW);
194 }
195
196 static void
197 gtk_scrolled_window_set_arg (GtkObject        *object,
198                              GtkArg           *arg,
199                              guint             arg_id)
200 {
201   GtkScrolledWindow *scrolled_window;
202
203   scrolled_window = GTK_SCROLLED_WINDOW (object);
204
205   switch (arg_id)
206     {
207     case ARG_HADJUSTMENT:
208       gtk_scrolled_window_set_hadjustment (scrolled_window, GTK_VALUE_POINTER (*arg));
209       break;
210     case ARG_VADJUSTMENT:
211       gtk_scrolled_window_set_vadjustment (scrolled_window, GTK_VALUE_POINTER (*arg));
212       break;
213     case ARG_HSCROLLBAR_POLICY:
214       gtk_scrolled_window_set_policy (scrolled_window,
215                                       GTK_VALUE_ENUM (*arg),
216                                       scrolled_window->vscrollbar_policy);
217       break;
218     case ARG_VSCROLLBAR_POLICY:
219       gtk_scrolled_window_set_policy (scrolled_window,
220                                       scrolled_window->hscrollbar_policy,
221                                       GTK_VALUE_ENUM (*arg));
222       break;
223     case ARG_WINDOW_PLACEMENT:
224       gtk_scrolled_window_set_placement (scrolled_window,
225                                          GTK_VALUE_ENUM (*arg));
226       break;
227     case ARG_SHADOW:
228       gtk_scrolled_window_set_shadow_type (scrolled_window,
229                                            GTK_VALUE_ENUM (*arg));
230       break;
231     default:
232       break;
233     }
234 }
235
236 static void
237 gtk_scrolled_window_get_arg (GtkObject        *object,
238                              GtkArg           *arg,
239                              guint             arg_id)
240 {
241   GtkScrolledWindow *scrolled_window;
242
243   scrolled_window = GTK_SCROLLED_WINDOW (object);
244
245   switch (arg_id)
246     {
247     case ARG_HADJUSTMENT:
248       GTK_VALUE_POINTER (*arg) = gtk_scrolled_window_get_hadjustment (scrolled_window);
249       break;
250     case ARG_VADJUSTMENT:
251       GTK_VALUE_POINTER (*arg) = gtk_scrolled_window_get_vadjustment (scrolled_window);
252       break;
253     case ARG_HSCROLLBAR_POLICY:
254       GTK_VALUE_ENUM (*arg) = scrolled_window->hscrollbar_policy;
255       break;
256     case ARG_VSCROLLBAR_POLICY:
257       GTK_VALUE_ENUM (*arg) = scrolled_window->vscrollbar_policy;
258       break;
259     case ARG_WINDOW_PLACEMENT:
260       GTK_VALUE_ENUM (*arg) = scrolled_window->window_placement;
261       break;
262     case ARG_SHADOW:
263       GTK_VALUE_ENUM (*arg) = scrolled_window->shadow_type;
264       break;
265     default:
266       arg->type = GTK_TYPE_INVALID;
267       break;
268     }
269 }
270
271 static void
272 gtk_scrolled_window_init (GtkScrolledWindow *scrolled_window)
273 {
274   GTK_WIDGET_SET_FLAGS (scrolled_window, GTK_NO_WINDOW);
275
276   gtk_container_set_resize_mode (GTK_CONTAINER (scrolled_window), GTK_RESIZE_QUEUE);
277
278   scrolled_window->hscrollbar = NULL;
279   scrolled_window->vscrollbar = NULL;
280   scrolled_window->hscrollbar_policy = GTK_POLICY_ALWAYS;
281   scrolled_window->vscrollbar_policy = GTK_POLICY_ALWAYS;
282   scrolled_window->hscrollbar_visible = FALSE;
283   scrolled_window->vscrollbar_visible = FALSE;
284   scrolled_window->window_placement = GTK_CORNER_TOP_LEFT;
285   
286 }
287
288 GtkWidget*
289 gtk_scrolled_window_new (GtkAdjustment *hadjustment,
290                          GtkAdjustment *vadjustment)
291 {
292   GtkWidget *scrolled_window;
293
294   if (hadjustment)
295     g_return_val_if_fail (GTK_IS_ADJUSTMENT (hadjustment), NULL);
296
297   if (vadjustment)
298     g_return_val_if_fail (GTK_IS_ADJUSTMENT (vadjustment), NULL);
299
300   scrolled_window = gtk_widget_new (GTK_TYPE_SCROLLED_WINDOW,
301                                     "hadjustment", hadjustment,
302                                     "vadjustment", vadjustment,
303                                     NULL);
304
305   return scrolled_window;
306 }
307
308 void
309 gtk_scrolled_window_set_hadjustment (GtkScrolledWindow *scrolled_window,
310                                      GtkAdjustment     *hadjustment)
311 {
312   GtkBin *bin;
313
314   g_return_if_fail (GTK_IS_SCROLLED_WINDOW (scrolled_window));
315   if (hadjustment)
316     g_return_if_fail (GTK_IS_ADJUSTMENT (hadjustment));
317   else
318     hadjustment = (GtkAdjustment*) gtk_object_new (GTK_TYPE_ADJUSTMENT, NULL);
319
320   bin = GTK_BIN (scrolled_window);
321
322   if (!scrolled_window->hscrollbar)
323     {
324       gtk_widget_push_composite_child ();
325       scrolled_window->hscrollbar = gtk_hscrollbar_new (hadjustment);
326       gtk_widget_set_composite_name (scrolled_window->hscrollbar, "hscrollbar");
327       gtk_widget_pop_composite_child ();
328
329       gtk_widget_set_parent (scrolled_window->hscrollbar, GTK_WIDGET (scrolled_window));
330       gtk_widget_ref (scrolled_window->hscrollbar);
331       gtk_widget_show (scrolled_window->hscrollbar);
332     }
333   else
334     {
335       GtkAdjustment *old_adjustment;
336       
337       old_adjustment = gtk_range_get_adjustment (GTK_RANGE (scrolled_window->hscrollbar));
338       if (old_adjustment == hadjustment)
339         return;
340
341       gtk_signal_disconnect_by_func (GTK_OBJECT (old_adjustment),
342                                      GTK_SIGNAL_FUNC (gtk_scrolled_window_adjustment_changed),
343                                      scrolled_window);
344       gtk_range_set_adjustment (GTK_RANGE (scrolled_window->hscrollbar),
345                                 hadjustment);
346     }
347   hadjustment = gtk_range_get_adjustment (GTK_RANGE (scrolled_window->hscrollbar));
348   gtk_signal_connect (GTK_OBJECT (hadjustment),
349                       "changed",
350                       GTK_SIGNAL_FUNC (gtk_scrolled_window_adjustment_changed),
351                       scrolled_window);
352   gtk_scrolled_window_adjustment_changed (hadjustment, scrolled_window);
353   
354   if (bin->child)
355     gtk_widget_set_scroll_adjustments (bin->child,
356                                        gtk_range_get_adjustment (GTK_RANGE (scrolled_window->hscrollbar)),
357                                        gtk_range_get_adjustment (GTK_RANGE (scrolled_window->vscrollbar)));
358 }
359
360 void
361 gtk_scrolled_window_set_vadjustment (GtkScrolledWindow *scrolled_window,
362                                      GtkAdjustment     *vadjustment)
363 {
364   GtkBin *bin;
365
366   g_return_if_fail (GTK_IS_SCROLLED_WINDOW (scrolled_window));
367   if (vadjustment)
368     g_return_if_fail (GTK_IS_ADJUSTMENT (vadjustment));
369   else
370     vadjustment = (GtkAdjustment*) gtk_object_new (GTK_TYPE_ADJUSTMENT, NULL);
371
372   bin = GTK_BIN (scrolled_window);
373
374   if (!scrolled_window->vscrollbar)
375     {
376       gtk_widget_push_composite_child ();
377       scrolled_window->vscrollbar = gtk_vscrollbar_new (vadjustment);
378       gtk_widget_set_composite_name (scrolled_window->vscrollbar, "vscrollbar");
379       gtk_widget_pop_composite_child ();
380
381       gtk_widget_set_parent (scrolled_window->vscrollbar, GTK_WIDGET (scrolled_window));
382       gtk_widget_ref (scrolled_window->vscrollbar);
383       gtk_widget_show (scrolled_window->vscrollbar);
384     }
385   else
386     {
387       GtkAdjustment *old_adjustment;
388       
389       old_adjustment = gtk_range_get_adjustment (GTK_RANGE (scrolled_window->vscrollbar));
390       if (old_adjustment == vadjustment)
391         return;
392
393       gtk_signal_disconnect_by_func (GTK_OBJECT (old_adjustment),
394                                      GTK_SIGNAL_FUNC (gtk_scrolled_window_adjustment_changed),
395                                      scrolled_window);
396       gtk_range_set_adjustment (GTK_RANGE (scrolled_window->vscrollbar),
397                                 vadjustment);
398     }
399   vadjustment = gtk_range_get_adjustment (GTK_RANGE (scrolled_window->vscrollbar));
400   gtk_signal_connect (GTK_OBJECT (vadjustment),
401                       "changed",
402                       GTK_SIGNAL_FUNC (gtk_scrolled_window_adjustment_changed),
403                       scrolled_window);
404   gtk_scrolled_window_adjustment_changed (vadjustment, scrolled_window);
405
406   if (bin->child)
407     gtk_widget_set_scroll_adjustments (bin->child,
408                                        gtk_range_get_adjustment (GTK_RANGE (scrolled_window->hscrollbar)),
409                                        gtk_range_get_adjustment (GTK_RANGE (scrolled_window->vscrollbar)));
410 }
411
412 GtkAdjustment*
413 gtk_scrolled_window_get_hadjustment (GtkScrolledWindow *scrolled_window)
414 {
415   g_return_val_if_fail (GTK_IS_SCROLLED_WINDOW (scrolled_window), NULL);
416
417   return (scrolled_window->hscrollbar ?
418           gtk_range_get_adjustment (GTK_RANGE (scrolled_window->hscrollbar)) :
419           NULL);
420 }
421
422 GtkAdjustment*
423 gtk_scrolled_window_get_vadjustment (GtkScrolledWindow *scrolled_window)
424 {
425   g_return_val_if_fail (GTK_IS_SCROLLED_WINDOW (scrolled_window), NULL);
426
427   return (scrolled_window->vscrollbar ?
428           gtk_range_get_adjustment (GTK_RANGE (scrolled_window->vscrollbar)) :
429           NULL);
430 }
431
432 void
433 gtk_scrolled_window_set_policy (GtkScrolledWindow *scrolled_window,
434                                 GtkPolicyType      hscrollbar_policy,
435                                 GtkPolicyType      vscrollbar_policy)
436 {
437   g_return_if_fail (GTK_IS_SCROLLED_WINDOW (scrolled_window));
438
439   if ((scrolled_window->hscrollbar_policy != hscrollbar_policy) ||
440       (scrolled_window->vscrollbar_policy != vscrollbar_policy))
441     {
442       scrolled_window->hscrollbar_policy = hscrollbar_policy;
443       scrolled_window->vscrollbar_policy = vscrollbar_policy;
444
445       gtk_widget_queue_resize (GTK_WIDGET (scrolled_window));
446     }
447 }
448
449 /**
450  * gtk_scrolled_window_get_policy:
451  * @scrolled_window: a #GtkScrolledWindow
452  * @hscrollbar_policy: location to store the policy for the horizontal scrollbar, or %NULL.
453  * @vscrollbar_policy: location to store the policy for the horizontal scrollbar, or %NULL.
454  * 
455  * Retrieves the current policy values for the horizontal and vertical
456  * scrollbars. See gtk_scrolled_window_set_policy().
457  **/
458 void
459 gtk_scrolled_window_get_policy (GtkScrolledWindow *scrolled_window,
460                                 GtkPolicyType     *hscrollbar_policy,
461                                 GtkPolicyType     *vscrollbar_policy)
462 {
463   g_return_if_fail (GTK_IS_SCROLLED_WINDOW (scrolled_window));
464
465   if (hscrollbar_policy)
466     *hscrollbar_policy = scrolled_window->hscrollbar_policy;
467   if (vscrollbar_policy)
468     *vscrollbar_policy = scrolled_window->vscrollbar_policy;
469 }
470
471 void
472 gtk_scrolled_window_set_placement (GtkScrolledWindow *scrolled_window,
473                                    GtkCornerType      window_placement)
474 {
475   g_return_if_fail (GTK_IS_SCROLLED_WINDOW (scrolled_window));
476
477   if (scrolled_window->window_placement != window_placement)
478     {
479       scrolled_window->window_placement = window_placement;
480
481       gtk_widget_queue_resize (GTK_WIDGET (scrolled_window));
482     }
483 }
484
485 /**
486  * gtk_scrolled_window_get_placement:
487  * @scrolled_window: a #GtkScrolledWindow
488  *
489  * Gets the placement of the scrollbars for the scrolled window. See 
490  * gtk_scrolled_window_set_placement().
491  *
492  * Return value: the current placement value.
493  **/
494 GtkCornerType
495 gtk_scrolled_window_get_placement (GtkScrolledWindow *scrolled_window)
496 {
497   g_return_val_if_fail (GTK_IS_SCROLLED_WINDOW (scrolled_window), GTK_CORNER_TOP_LEFT);
498
499   return scrolled_window->window_placement;
500 }
501
502 /**
503  * gtk_scrolled_window_set_shadow_type:
504  * @scrolled_window: a #GtkScrolledWindow
505  * @type: kind of shadow to draw around scrolled window contents
506  *
507  * Changes the type of shadow drawn around the contents of
508  * @scrolled_window.
509  * 
510  **/
511 void
512 gtk_scrolled_window_set_shadow_type (GtkScrolledWindow *scrolled_window,
513                                      GtkShadowType      type)
514 {
515   g_return_if_fail (GTK_IS_SCROLLED_WINDOW (scrolled_window));
516   g_return_if_fail (type >= GTK_SHADOW_NONE && type <= GTK_SHADOW_ETCHED_OUT);
517   
518   if (scrolled_window->shadow_type != type)
519     {
520       scrolled_window->shadow_type = type;
521
522       if (GTK_WIDGET_DRAWABLE (scrolled_window))
523         gtk_widget_queue_clear (GTK_WIDGET (scrolled_window));
524
525       gtk_widget_queue_resize (GTK_WIDGET (scrolled_window));
526     }
527 }
528
529 /**
530  * gtk_scrolled_window_get_shadow_type:
531  * @scrolled_window: a #GtkScrolledWindow
532  *
533  * Gets the shadow type of the scrolled window. See 
534  * gtk_scrolled_window_set_shadow_type().
535  *
536  * Return value: the current shadow type
537  **/
538 GtkShadowType
539 gtk_scrolled_window_get_shadow_type (GtkScrolledWindow *scrolled_window)
540 {
541   g_return_val_if_fail (GTK_IS_SCROLLED_WINDOW (scrolled_window), GTK_SHADOW_NONE);
542
543   return scrolled_window->shadow_type;
544 }
545
546 static void
547 gtk_scrolled_window_destroy (GtkObject *object)
548 {
549   GtkScrolledWindow *scrolled_window;
550
551   g_return_if_fail (GTK_IS_SCROLLED_WINDOW (object));
552
553   scrolled_window = GTK_SCROLLED_WINDOW (object);
554
555   gtk_widget_unparent (scrolled_window->hscrollbar);
556   gtk_widget_unparent (scrolled_window->vscrollbar);
557   gtk_widget_destroy (scrolled_window->hscrollbar);
558   gtk_widget_destroy (scrolled_window->vscrollbar);
559
560   GTK_OBJECT_CLASS (parent_class)->destroy (object);
561 }
562
563 static void
564 gtk_scrolled_window_finalize (GObject *object)
565 {
566   GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW (object);
567
568   gtk_widget_unref (scrolled_window->hscrollbar);
569   gtk_widget_unref (scrolled_window->vscrollbar);
570
571   G_OBJECT_CLASS (parent_class)->finalize (object);
572 }
573
574 static void
575 gtk_scrolled_window_paint (GtkWidget    *widget,
576                            GdkRectangle *area)
577 {
578   GtkAllocation relative_allocation;
579   GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW (widget);
580
581   if (scrolled_window->shadow_type != GTK_SHADOW_NONE)
582     {
583       gtk_scrolled_window_relative_allocation (widget, &relative_allocation);
584       
585       relative_allocation.x -= widget->style->xthickness;
586       relative_allocation.y -= widget->style->ythickness;
587       relative_allocation.width += 2 * widget->style->xthickness;
588       relative_allocation.height += 2 * widget->style->ythickness;
589       
590       gtk_paint_shadow (widget->style, widget->window,
591                         GTK_STATE_NORMAL, scrolled_window->shadow_type,
592                         area, widget, "scrolled_window",
593                         widget->allocation.x + relative_allocation.x,
594                         widget->allocation.y + relative_allocation.y,
595                         relative_allocation.width,
596                         relative_allocation.height);
597     }
598 }
599
600 static gint
601 gtk_scrolled_window_expose (GtkWidget      *widget,
602                             GdkEventExpose *event)
603 {
604   if (GTK_WIDGET_DRAWABLE (widget))
605     {
606       gtk_scrolled_window_paint (widget, &event->area);
607
608       (* GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event);
609     }
610
611   return FALSE;
612 }
613
614 static void
615 gtk_scrolled_window_forall (GtkContainer *container,
616                             gboolean      include_internals,
617                             GtkCallback   callback,
618                             gpointer      callback_data)
619 {
620   g_return_if_fail (GTK_IS_SCROLLED_WINDOW (container));
621   g_return_if_fail (callback != NULL);
622
623   GTK_CONTAINER_CLASS (parent_class)->forall (container,
624                                               include_internals,
625                                               callback,
626                                               callback_data);
627   if (include_internals)
628     {
629       GtkScrolledWindow *scrolled_window;
630
631       scrolled_window = GTK_SCROLLED_WINDOW (container);
632       
633       if (scrolled_window->vscrollbar)
634         callback (scrolled_window->vscrollbar, callback_data);
635       if (scrolled_window->hscrollbar)
636         callback (scrolled_window->hscrollbar, callback_data);
637     }
638 }
639
640 static void
641 gtk_scrolled_window_size_request (GtkWidget      *widget,
642                                   GtkRequisition *requisition)
643 {
644   GtkScrolledWindow *scrolled_window;
645   GtkBin *bin;
646   gint extra_width;
647   gint extra_height;
648   GtkRequisition hscrollbar_requisition;
649   GtkRequisition vscrollbar_requisition;
650   GtkRequisition child_requisition;
651
652   g_return_if_fail (GTK_IS_SCROLLED_WINDOW (widget));
653   g_return_if_fail (requisition != NULL);
654
655   scrolled_window = GTK_SCROLLED_WINDOW (widget);
656   bin = GTK_BIN (scrolled_window);
657
658   extra_width = 0;
659   extra_height = 0;
660   requisition->width = 0;
661   requisition->height = 0;
662   
663   gtk_widget_size_request (scrolled_window->hscrollbar,
664                            &hscrollbar_requisition);
665   gtk_widget_size_request (scrolled_window->vscrollbar,
666                            &vscrollbar_requisition);
667   
668   if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
669     {
670       gtk_widget_size_request (bin->child, &child_requisition);
671
672       if (scrolled_window->hscrollbar_policy == GTK_POLICY_NEVER)
673         requisition->width += child_requisition.width;
674       else
675         {
676           GtkWidgetAuxInfo *aux_info = _gtk_widget_get_aux_info (bin->child, FALSE);
677
678           if (aux_info && aux_info->width > 0)
679             {
680               requisition->width += aux_info->width;
681               extra_width = -1;
682             }
683           else
684             requisition->width += vscrollbar_requisition.width;
685         }
686
687       if (scrolled_window->vscrollbar_policy == GTK_POLICY_NEVER)
688         requisition->height += child_requisition.height;
689       else
690         {
691           GtkWidgetAuxInfo *aux_info = _gtk_widget_get_aux_info (bin->child, FALSE);
692
693           if (aux_info && aux_info->height > 0)
694             {
695               requisition->height += aux_info->height;
696               extra_height = -1;
697             }
698           else
699             requisition->height += hscrollbar_requisition.height;
700         }
701     }
702
703   if (scrolled_window->hscrollbar_policy == GTK_POLICY_AUTOMATIC ||
704       scrolled_window->hscrollbar_policy == GTK_POLICY_ALWAYS)
705     {
706       requisition->width = MAX (requisition->width, hscrollbar_requisition.width);
707       if (!extra_height || scrolled_window->hscrollbar_policy == GTK_POLICY_ALWAYS)
708         extra_height = SCROLLBAR_SPACING (scrolled_window) + hscrollbar_requisition.height;
709     }
710
711   if (scrolled_window->vscrollbar_policy == GTK_POLICY_AUTOMATIC ||
712       scrolled_window->vscrollbar_policy == GTK_POLICY_ALWAYS)
713     {
714       requisition->height = MAX (requisition->height, vscrollbar_requisition.height);
715       if (!extra_height || scrolled_window->vscrollbar_policy == GTK_POLICY_ALWAYS)
716         extra_width = SCROLLBAR_SPACING (scrolled_window) + vscrollbar_requisition.width;
717     }
718
719   requisition->width += GTK_CONTAINER (widget)->border_width * 2 + MAX (0, extra_width);
720   requisition->height += GTK_CONTAINER (widget)->border_width * 2 + MAX (0, extra_height);
721
722   if (scrolled_window->shadow_type != GTK_SHADOW_NONE)
723     {
724       requisition->width += 2 * widget->style->xthickness;
725       requisition->height += 2 * widget->style->ythickness;
726     }
727 }
728
729 static void
730 gtk_scrolled_window_relative_allocation (GtkWidget     *widget,
731                                          GtkAllocation *allocation)
732 {
733   GtkScrolledWindow *scrolled_window;
734
735   g_return_if_fail (widget != NULL);
736   g_return_if_fail (allocation != NULL);
737
738   scrolled_window = GTK_SCROLLED_WINDOW (widget);
739
740   allocation->x = GTK_CONTAINER (widget)->border_width;
741   allocation->y = GTK_CONTAINER (widget)->border_width;
742
743   if (scrolled_window->shadow_type != GTK_SHADOW_NONE)
744     {
745       allocation->x += widget->style->xthickness;
746       allocation->y += widget->style->ythickness;
747     }
748   
749   allocation->width = MAX (1, (gint)widget->allocation.width - allocation->x * 2);
750   allocation->height = MAX (1, (gint)widget->allocation.height - allocation->y * 2);
751
752   if (scrolled_window->vscrollbar_visible)
753     {
754       GtkRequisition vscrollbar_requisition;
755       gtk_widget_get_child_requisition (scrolled_window->vscrollbar,
756                                         &vscrollbar_requisition);
757   
758       if (scrolled_window->window_placement == GTK_CORNER_TOP_RIGHT ||
759           scrolled_window->window_placement == GTK_CORNER_BOTTOM_RIGHT)
760         allocation->x += (vscrollbar_requisition.width +
761                           SCROLLBAR_SPACING (scrolled_window));
762
763       allocation->width = MAX (1, (gint)allocation->width -
764                                ((gint)vscrollbar_requisition.width +
765                                 (gint)SCROLLBAR_SPACING (scrolled_window)));
766     }
767   if (scrolled_window->hscrollbar_visible)
768     {
769       GtkRequisition hscrollbar_requisition;
770       gtk_widget_get_child_requisition (scrolled_window->hscrollbar,
771                                         &hscrollbar_requisition);
772   
773       if (scrolled_window->window_placement == GTK_CORNER_BOTTOM_LEFT ||
774           scrolled_window->window_placement == GTK_CORNER_BOTTOM_RIGHT)
775         allocation->y += (hscrollbar_requisition.height +
776                           SCROLLBAR_SPACING (scrolled_window));
777
778       allocation->height = MAX (1, (gint)allocation->height -
779                                 ((gint)hscrollbar_requisition.height +
780                                  (gint)SCROLLBAR_SPACING (scrolled_window)));
781     }
782 }
783
784 static void
785 gtk_scrolled_window_size_allocate (GtkWidget     *widget,
786                                    GtkAllocation *allocation)
787 {
788   GtkScrolledWindow *scrolled_window;
789   GtkBin *bin;
790   GtkAllocation relative_allocation;
791   GtkAllocation child_allocation;
792   
793   g_return_if_fail (GTK_IS_SCROLLED_WINDOW (widget));
794   g_return_if_fail (allocation != NULL);
795
796   scrolled_window = GTK_SCROLLED_WINDOW (widget);
797   bin = GTK_BIN (scrolled_window);
798
799   widget->allocation = *allocation;
800
801   if (scrolled_window->hscrollbar_policy == GTK_POLICY_ALWAYS)
802     scrolled_window->hscrollbar_visible = TRUE;
803   else if (scrolled_window->hscrollbar_policy == GTK_POLICY_NEVER)
804     scrolled_window->hscrollbar_visible = FALSE;
805   if (scrolled_window->vscrollbar_policy == GTK_POLICY_ALWAYS)
806     scrolled_window->vscrollbar_visible = TRUE;
807   else if (scrolled_window->vscrollbar_policy == GTK_POLICY_NEVER)
808     scrolled_window->vscrollbar_visible = FALSE;
809
810   if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
811     {
812       gboolean previous_hvis;
813       gboolean previous_vvis;
814       guint count = 0;
815       
816       do
817         {
818           gtk_scrolled_window_relative_allocation (widget, &relative_allocation);
819           
820           child_allocation.x = relative_allocation.x + allocation->x;
821           child_allocation.y = relative_allocation.y + allocation->y;
822           child_allocation.width = relative_allocation.width;
823           child_allocation.height = relative_allocation.height;
824           
825           previous_hvis = scrolled_window->hscrollbar_visible;
826           previous_vvis = scrolled_window->vscrollbar_visible;
827           
828           gtk_widget_size_allocate (bin->child, &child_allocation);
829
830           /* If, after the first iteration, the hscrollbar and the
831            * vscrollbar flip visiblity, then we need both.
832            */
833           if (count &&
834               previous_hvis != scrolled_window->hscrollbar_visible &&
835               previous_vvis != scrolled_window->vscrollbar_visible)
836             {
837               scrolled_window->hscrollbar_visible = TRUE;
838               scrolled_window->vscrollbar_visible = TRUE;
839
840               /* a new resize is already queued at this point,
841                * so we will immediatedly get reinvoked
842                */
843               return;
844             }
845           
846           count++;
847         }
848       while (previous_hvis != scrolled_window->hscrollbar_visible ||
849              previous_vvis != scrolled_window->vscrollbar_visible);
850     }
851   else
852     gtk_scrolled_window_relative_allocation (widget, &relative_allocation);
853   
854   if (scrolled_window->hscrollbar_visible)
855     {
856       GtkRequisition hscrollbar_requisition;
857       gtk_widget_get_child_requisition (scrolled_window->hscrollbar,
858                                         &hscrollbar_requisition);
859   
860       if (!GTK_WIDGET_VISIBLE (scrolled_window->hscrollbar))
861         gtk_widget_show (scrolled_window->hscrollbar);
862
863       child_allocation.x = relative_allocation.x;
864       if (scrolled_window->window_placement == GTK_CORNER_TOP_LEFT ||
865           scrolled_window->window_placement == GTK_CORNER_TOP_RIGHT)
866         child_allocation.y = (relative_allocation.y +
867                               relative_allocation.height +
868                               SCROLLBAR_SPACING (scrolled_window) +
869                               (scrolled_window->shadow_type == GTK_SHADOW_NONE ?
870                                0 : widget->style->ythickness));
871       else
872         child_allocation.y = GTK_CONTAINER (scrolled_window)->border_width;
873
874       child_allocation.width = relative_allocation.width;
875       child_allocation.height = hscrollbar_requisition.height;
876       child_allocation.x += allocation->x;
877       child_allocation.y += allocation->y;
878
879       if (scrolled_window->shadow_type != GTK_SHADOW_NONE)
880         {
881           child_allocation.x -= widget->style->xthickness;
882           child_allocation.width += 2 * widget->style->xthickness;
883         }
884
885       gtk_widget_size_allocate (scrolled_window->hscrollbar, &child_allocation);
886     }
887   else if (GTK_WIDGET_VISIBLE (scrolled_window->hscrollbar))
888     gtk_widget_hide (scrolled_window->hscrollbar);
889
890   if (scrolled_window->vscrollbar_visible)
891     {
892       GtkRequisition vscrollbar_requisition;
893       if (!GTK_WIDGET_VISIBLE (scrolled_window->vscrollbar))
894         gtk_widget_show (scrolled_window->vscrollbar);
895
896       gtk_widget_get_child_requisition (scrolled_window->vscrollbar,
897                                         &vscrollbar_requisition);
898
899       if (scrolled_window->window_placement == GTK_CORNER_TOP_LEFT ||
900           scrolled_window->window_placement == GTK_CORNER_BOTTOM_LEFT)
901         child_allocation.x = (relative_allocation.x +
902                               relative_allocation.width +
903                               SCROLLBAR_SPACING (scrolled_window) +
904                               (scrolled_window->shadow_type == GTK_SHADOW_NONE ?
905                                0 : widget->style->xthickness));
906       else
907         child_allocation.x = GTK_CONTAINER (scrolled_window)->border_width;
908
909       child_allocation.y = relative_allocation.y;
910       child_allocation.width = vscrollbar_requisition.width;
911       child_allocation.height = relative_allocation.height;
912       child_allocation.x += allocation->x;
913       child_allocation.y += allocation->y;
914
915       if (scrolled_window->shadow_type != GTK_SHADOW_NONE)
916         {
917           child_allocation.y -= widget->style->ythickness;
918           child_allocation.height += 2 * widget->style->ythickness;
919         }
920
921       gtk_widget_size_allocate (scrolled_window->vscrollbar, &child_allocation);
922     }
923   else if (GTK_WIDGET_VISIBLE (scrolled_window->vscrollbar))
924     gtk_widget_hide (scrolled_window->vscrollbar);
925 }
926
927 static gint
928 gtk_scrolled_window_scroll_event (GtkWidget *widget,
929                                   GdkEventScroll *event)
930 {
931   GtkWidget *range;
932
933   g_return_val_if_fail (GTK_IS_SCROLLED_WINDOW (widget), FALSE);
934   g_return_val_if_fail (event != NULL, FALSE);  
935
936   if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_DOWN)
937     range = GTK_SCROLLED_WINDOW (widget)->vscrollbar;
938   else
939     range = GTK_SCROLLED_WINDOW (widget)->hscrollbar;
940
941   if (range && GTK_WIDGET_VISIBLE (range))
942     {
943       GtkAdjustment *adj = GTK_RANGE (range)->adjustment;
944       gdouble new_value;
945
946       if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_LEFT)
947         new_value = adj->value - adj->page_increment / 2;
948       else
949         new_value = adj->value + adj->page_increment / 2;
950
951       new_value = CLAMP (new_value, adj->lower, adj->upper - adj->page_size);
952       gtk_adjustment_set_value (adj, new_value);
953
954       return TRUE;
955     }
956
957   return FALSE;
958 }
959
960 static void
961 gtk_scrolled_window_adjustment_changed (GtkAdjustment *adjustment,
962                                         gpointer       data)
963 {
964   GtkScrolledWindow *scrolled_win;
965
966   g_return_if_fail (adjustment != NULL);
967   g_return_if_fail (data != NULL);
968
969   scrolled_win = GTK_SCROLLED_WINDOW (data);
970
971   if (scrolled_win->hscrollbar &&
972       adjustment == gtk_range_get_adjustment (GTK_RANGE (scrolled_win->hscrollbar)))
973     {
974       if (scrolled_win->hscrollbar_policy == GTK_POLICY_AUTOMATIC)
975         {
976           gboolean visible;
977           
978           visible = scrolled_win->hscrollbar_visible;
979           scrolled_win->hscrollbar_visible = (adjustment->upper - adjustment->lower >
980                                               adjustment->page_size);
981           if (scrolled_win->hscrollbar_visible != visible)
982             gtk_widget_queue_resize (GTK_WIDGET (scrolled_win));
983         }
984     }
985   else if (scrolled_win->vscrollbar &&
986            adjustment == gtk_range_get_adjustment (GTK_RANGE (scrolled_win->vscrollbar)))
987     {
988       if (scrolled_win->vscrollbar_policy == GTK_POLICY_AUTOMATIC)
989         {
990           gboolean visible;
991
992           visible = scrolled_win->vscrollbar_visible;
993           scrolled_win->vscrollbar_visible = (adjustment->upper - adjustment->lower >
994                                               adjustment->page_size);
995           if (scrolled_win->vscrollbar_visible != visible)
996             gtk_widget_queue_resize (GTK_WIDGET (scrolled_win));
997         }
998     }
999 }
1000
1001 static void
1002 gtk_scrolled_window_add (GtkContainer *container,
1003                          GtkWidget    *child)
1004 {
1005   GtkScrolledWindow *scrolled_window;
1006   GtkBin *bin;
1007
1008   bin = GTK_BIN (container);
1009   g_return_if_fail (bin->child == NULL);
1010
1011   scrolled_window = GTK_SCROLLED_WINDOW (container);
1012
1013   bin->child = child;
1014   gtk_widget_set_parent (child, GTK_WIDGET (bin));
1015
1016   /* this is a temporary message */
1017   if (!gtk_widget_set_scroll_adjustments (child,
1018                                           gtk_range_get_adjustment (GTK_RANGE (scrolled_window->hscrollbar)),
1019                                           gtk_range_get_adjustment (GTK_RANGE (scrolled_window->vscrollbar))))
1020     g_warning ("gtk_scrolled_window_add(): cannot add non scrollable widget "
1021                "use gtk_scrolled_window_add_with_viewport() instead");
1022 }
1023
1024 static void
1025 gtk_scrolled_window_remove (GtkContainer *container,
1026                             GtkWidget    *child)
1027 {
1028   g_return_if_fail (GTK_IS_SCROLLED_WINDOW (container));
1029   g_return_if_fail (child != NULL);
1030   g_return_if_fail (GTK_BIN (container)->child == child);
1031   
1032   gtk_widget_set_scroll_adjustments (child, NULL, NULL);
1033
1034   /* chain parent class handler to remove child */
1035   GTK_CONTAINER_CLASS (parent_class)->remove (container, child);
1036 }
1037
1038 void
1039 gtk_scrolled_window_add_with_viewport (GtkScrolledWindow *scrolled_window,
1040                                        GtkWidget         *child)
1041 {
1042   GtkBin *bin;
1043   GtkWidget *viewport;
1044
1045   g_return_if_fail (GTK_IS_SCROLLED_WINDOW (scrolled_window));
1046   g_return_if_fail (GTK_IS_WIDGET (child));
1047   g_return_if_fail (child->parent == NULL);
1048
1049   bin = GTK_BIN (scrolled_window);
1050
1051   if (bin->child != NULL)
1052     {
1053       g_return_if_fail (GTK_IS_VIEWPORT (bin->child));
1054       g_return_if_fail (GTK_BIN (bin->child)->child == NULL);
1055
1056       viewport = bin->child;
1057     }
1058   else
1059     {
1060       viewport =
1061         gtk_viewport_new (gtk_scrolled_window_get_hadjustment (scrolled_window),
1062                           gtk_scrolled_window_get_vadjustment (scrolled_window));
1063       gtk_container_add (GTK_CONTAINER (scrolled_window), viewport);
1064     }
1065
1066   gtk_widget_show (viewport);
1067   gtk_container_add (GTK_CONTAINER (viewport), child);
1068 }