2011-01-12 Martin Robinson <mrobinson@igalia.com>
[WebKit-https.git] / Source / WebCore / platform / gtk / gtk2drawing.c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4  *
5  * The contents of this file are subject to the Mozilla Public License Version
6  * 1.1 (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  * http://www.mozilla.org/MPL/
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the
13  * License.
14  *
15  * The Original Code is mozilla.org code.
16  *
17  * The Initial Developer of the Original Code is
18  * Netscape Communications Corporation.
19  * Portions created by the Initial Developer are Copyright (C) 2002
20  * the Initial Developer. All Rights Reserved.
21  *
22  * Contributor(s):
23  *  Brian Ryner <bryner@brianryner.com>  (Original Author)
24  *  Pierre Chanial <p_ch@verizon.net>
25  *  Michael Ventnor <m.ventnor@gmail.com>
26  *  Alp Toker <alp@nuanti.com>
27  *
28  * Alternatively, the contents of this file may be used under the terms of
29  * either the GNU General Public License Version 2 or later (the "GPL"), or
30  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31  * in which case the provisions of the GPL or the LGPL are applicable instead
32  * of those above. If you wish to allow use of your version of this file only
33  * under the terms of either the GPL or the LGPL, and not to allow others to
34  * use your version of this file under the terms of the MPL, indicate your
35  * decision by deleting the provisions above and replace them with the notice
36  * and other provisions required by the GPL or the LGPL. If you do not delete
37  * the provisions above, a recipient may use your version of this file under
38  * the terms of any one of the MPL, the GPL or the LGPL.
39  *
40  * ***** END LICENSE BLOCK ***** */
41
42 /*
43  * This file contains painting functions for each of the gtk2 widgets.
44  * Adapted from the gtkdrawing.c, and gtk+2.0 source.
45  */
46
47 #ifdef GTK_API_VERSION_2
48
49 #undef GTK_DISABLE_DEPRECATED
50 #undef GDK_DISABLE_DEPRECATED
51
52 #include <gdk/gdkprivate.h>
53 #include "gtkdrawing.h"
54 #include "GtkVersioning.h"
55 #include <math.h>
56 #include <string.h>
57
58 #define XTHICKNESS(style) (style->xthickness)
59 #define YTHICKNESS(style) (style->ythickness)
60
61 static GtkThemeParts *gParts = NULL;
62 static style_prop_t style_prop_func;
63 static gboolean have_arrow_scaling;
64 static gboolean is_initialized;
65
66 void
67 moz_gtk_use_theme_parts(GtkThemeParts* parts)
68 {
69     gParts = parts;
70 }
71
72 /* Because we have such an unconventional way of drawing widgets, signal to the GTK theme engine
73    that they are drawing for Mozilla instead of a conventional GTK app so they can do any specific
74    things they may want to do. */
75 static void
76 moz_gtk_set_widget_name(GtkWidget* widget)
77 {
78     gtk_widget_set_name(widget, "MozillaGtkWidget");
79 }
80
81 gint
82 moz_gtk_enable_style_props(style_prop_t styleGetProp)
83 {
84     style_prop_func = styleGetProp;
85     return MOZ_GTK_SUCCESS;
86 }
87
88 static gint
89 ensure_window_widget()
90 {
91     if (!gParts->protoWindow) {
92         gParts->protoWindow = gtk_window_new(GTK_WINDOW_POPUP);
93
94         if (gParts->colormap)
95             gtk_widget_set_colormap(gParts->protoWindow, gParts->colormap);
96
97         gtk_widget_realize(gParts->protoWindow);
98         moz_gtk_set_widget_name(gParts->protoWindow);
99     }
100     return MOZ_GTK_SUCCESS;
101 }
102
103 static gint
104 setup_widget_prototype(GtkWidget* widget)
105 {
106     ensure_window_widget();
107     if (!gParts->protoLayout) {
108         gParts->protoLayout = gtk_fixed_new();
109         gtk_container_add(GTK_CONTAINER(gParts->protoWindow), gParts->protoLayout);
110     }
111
112     gtk_container_add(GTK_CONTAINER(gParts->protoLayout), widget);
113     gtk_widget_realize(widget);
114     g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE));
115     return MOZ_GTK_SUCCESS;
116 }
117
118 static gint
119 ensure_button_widget()
120 {
121     if (!gParts->buttonWidget) {
122         gParts->buttonWidget = gtk_button_new_with_label("M");
123         setup_widget_prototype(gParts->buttonWidget);
124     }
125     return MOZ_GTK_SUCCESS;
126 }
127
128 static gint
129 ensure_toggle_button_widget()
130 {
131     if (!gParts->toggleButtonWidget) {
132         gParts->toggleButtonWidget = gtk_toggle_button_new();
133         setup_widget_prototype(gParts->toggleButtonWidget);
134         /* toggle button must be set active to get the right style on hover. */
135         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gParts->toggleButtonWidget), TRUE);
136   }
137   return MOZ_GTK_SUCCESS;
138 }
139
140 static gint
141 ensure_button_arrow_widget()
142 {
143     if (!gParts->buttonArrowWidget) {
144         ensure_toggle_button_widget();
145
146         gParts->buttonArrowWidget = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT);
147         gtk_container_add(GTK_CONTAINER(gParts->toggleButtonWidget), gParts->buttonArrowWidget);
148         gtk_widget_realize(gParts->buttonArrowWidget);
149     }
150     return MOZ_GTK_SUCCESS;
151 }
152
153 static gint
154 ensure_scrollbar_widget()
155 {
156     if (!gParts->vertScrollbarWidget) {
157         gParts->vertScrollbarWidget = gtk_vscrollbar_new(NULL);
158         setup_widget_prototype(gParts->vertScrollbarWidget);
159     }
160     if (!gParts->horizScrollbarWidget) {
161         gParts->horizScrollbarWidget = gtk_hscrollbar_new(NULL);
162         setup_widget_prototype(gParts->horizScrollbarWidget);
163     }
164     return MOZ_GTK_SUCCESS;
165 }
166
167 /* We need to have pointers to the inner widgets (button, separator, arrow)
168  * of the ComboBox to get the correct rendering from theme engines which
169  * special cases their look. Since the inner layout can change, we ask GTK
170  * to NULL our pointers when they are about to become invalid because the
171  * corresponding widgets don't exist anymore. It's the role of
172  * g_object_add_weak_pointer().
173  * Note that if we don't find the inner widgets (which shouldn't happen), we
174  * fallback to use generic "non-inner" widgets, and they don't need that kind
175  * of weak pointer since they are explicit children of gParts->protoWindow and as
176  * such GTK holds a strong reference to them. */
177 static void
178 moz_gtk_get_combo_box_inner_button(GtkWidget *widget, gpointer client_data)
179 {
180     if (GTK_IS_TOGGLE_BUTTON(widget)) {
181         gParts->comboBoxButtonWidget = widget;
182         g_object_add_weak_pointer(G_OBJECT(widget),
183                                   (gpointer) &gParts->comboBoxButtonWidget);
184         gtk_widget_realize(widget);
185         g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE));
186     }
187 }
188
189 static void
190 moz_gtk_get_combo_box_button_inner_widgets(GtkWidget *widget,
191                                            gpointer client_data)
192 {
193     if (GTK_IS_SEPARATOR(widget)) {
194         gParts->comboBoxSeparatorWidget = widget;
195         g_object_add_weak_pointer(G_OBJECT(widget),
196                                   (gpointer) &gParts->comboBoxSeparatorWidget);
197     } else if (GTK_IS_ARROW(widget)) {
198         gParts->comboBoxArrowWidget = widget;
199         g_object_add_weak_pointer(G_OBJECT(widget),
200                                   (gpointer) &gParts->comboBoxArrowWidget);
201     } else
202         return;
203     gtk_widget_realize(widget);
204     g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE));
205 }
206
207 static gint
208 ensure_combo_box_widgets()
209 {
210     GtkWidget* buttonChild;
211
212     if (gParts->comboBoxButtonWidget && gParts->comboBoxArrowWidget)
213         return MOZ_GTK_SUCCESS;
214
215     /* Create a ComboBox if needed */
216     if (!gParts->comboBoxWidget) {
217         gParts->comboBoxWidget = gtk_combo_box_new();
218         setup_widget_prototype(gParts->comboBoxWidget);
219     }
220
221     /* Get its inner Button */
222     gtk_container_forall(GTK_CONTAINER(gParts->comboBoxWidget),
223                          moz_gtk_get_combo_box_inner_button,
224                          NULL);
225
226     if (gParts->comboBoxButtonWidget) {
227         /* Get the widgets inside the Button */
228         buttonChild = gtk_bin_get_child(GTK_BIN(gParts->comboBoxButtonWidget));
229         if (GTK_IS_HBOX(buttonChild)) {
230             /* appears-as-list = FALSE, cell-view = TRUE; the button
231              * contains an hbox. This hbox is there because the ComboBox
232              * needs to place a cell renderer, a separator, and an arrow in
233              * the button when appears-as-list is FALSE. */
234             gtk_container_forall(GTK_CONTAINER(buttonChild),
235                                  moz_gtk_get_combo_box_button_inner_widgets,
236                                  NULL);
237         } else if(GTK_IS_ARROW(buttonChild)) {
238             /* appears-as-list = TRUE, or cell-view = FALSE;
239              * the button only contains an arrow */
240             gParts->comboBoxArrowWidget = buttonChild;
241             g_object_add_weak_pointer(G_OBJECT(buttonChild), (gpointer)
242                                       &gParts->comboBoxArrowWidget);
243             gtk_widget_realize(gParts->comboBoxArrowWidget);
244             g_object_set_data(G_OBJECT(gParts->comboBoxArrowWidget),
245                               "transparent-bg-hint", GINT_TO_POINTER(TRUE));
246         }
247     } else {
248         /* Shouldn't be reached with current internal gtk implementation; we
249          * use a generic toggle button as last resort fallback to avoid
250          * crashing. */
251         ensure_toggle_button_widget();
252         gParts->comboBoxButtonWidget = gParts->toggleButtonWidget;
253     }
254
255     if (!gParts->comboBoxArrowWidget) {
256         /* Shouldn't be reached with current internal gtk implementation;
257          * we gParts->buttonArrowWidget as last resort fallback to avoid
258          * crashing. */
259         ensure_button_arrow_widget();
260         gParts->comboBoxArrowWidget = gParts->buttonArrowWidget;
261     }
262
263     /* We don't test the validity of gParts->comboBoxSeparatorWidget since there
264      * is none when "appears-as-list" = TRUE or "cell-view" = FALSE; if it
265      * is invalid we just won't paint it. */
266
267     return MOZ_GTK_SUCCESS;
268 }
269
270 static gint
271 ensure_progress_widget()
272 {
273     if (!gParts->progresWidget) {
274         gParts->progresWidget = gtk_progress_bar_new();
275         setup_widget_prototype(gParts->progresWidget);
276     }
277     return MOZ_GTK_SUCCESS;
278 }
279
280 static gint
281 ensure_scrolled_window_widget()
282 {
283     if (!gParts->scrolledWindowWidget) {
284         gParts->scrolledWindowWidget = gtk_scrolled_window_new(NULL, NULL);
285         setup_widget_prototype(gParts->scrolledWindowWidget);
286     }
287     return MOZ_GTK_SUCCESS;
288 }
289
290 static GtkStateType
291 ConvertGtkState(GtkWidgetState* state)
292 {
293     if (state->disabled)
294         return GTK_STATE_INSENSITIVE;
295     else if (state->depressed)
296         return (state->inHover ? GTK_STATE_PRELIGHT : GTK_STATE_ACTIVE);
297     else if (state->inHover)
298         return (state->active ? GTK_STATE_ACTIVE : GTK_STATE_PRELIGHT);
299     else
300         return GTK_STATE_NORMAL;
301 }
302
303 static gint
304 TSOffsetStyleGCArray(GdkGC** gcs, gint xorigin, gint yorigin)
305 {
306     int i;
307     /* there are 5 gc's in each array, for each of the widget states */
308     for (i = 0; i < 5; ++i)
309         gdk_gc_set_ts_origin(gcs[i], xorigin, yorigin);
310     return MOZ_GTK_SUCCESS;
311 }
312
313 static gint
314 TSOffsetStyleGCs(GtkStyle* style, gint xorigin, gint yorigin)
315 {
316     TSOffsetStyleGCArray(style->fg_gc, xorigin, yorigin);
317     TSOffsetStyleGCArray(style->bg_gc, xorigin, yorigin);
318     TSOffsetStyleGCArray(style->light_gc, xorigin, yorigin);
319     TSOffsetStyleGCArray(style->dark_gc, xorigin, yorigin);
320     TSOffsetStyleGCArray(style->mid_gc, xorigin, yorigin);
321     TSOffsetStyleGCArray(style->text_gc, xorigin, yorigin);
322     TSOffsetStyleGCArray(style->base_gc, xorigin, yorigin);
323     gdk_gc_set_ts_origin(style->black_gc, xorigin, yorigin);
324     gdk_gc_set_ts_origin(style->white_gc, xorigin, yorigin);
325     return MOZ_GTK_SUCCESS;
326 }
327
328 static gint
329 moz_gtk_button_paint(GdkDrawable* drawable, GdkRectangle* rect,
330                      GdkRectangle* cliprect, GtkWidgetState* state,
331                      GtkReliefStyle relief, GtkWidget* widget,
332                      GtkTextDirection direction)
333 {
334     GtkShadowType shadow_type;
335     GtkStyle* style = gtk_widget_get_style(widget);
336     GtkStateType button_state = ConvertGtkState(state);
337     gint x = rect->x, y=rect->y, width=rect->width, height=rect->height;
338
339     gboolean interior_focus;
340     gint focus_width, focus_pad;
341
342     moz_gtk_widget_get_focus(widget, &interior_focus, &focus_width, &focus_pad);
343
344     gtk_widget_set_state(widget, button_state);
345     gtk_widget_set_direction(widget, direction);
346
347     if (state->isDefault)
348         GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_DEFAULT);
349
350     gtk_button_set_relief(GTK_BUTTON(widget), relief);
351
352     /* Some theme engines love to cause us pain in that gtk_paint_focus is a
353        no-op on buttons and button-like widgets. They only listen to this flag. */
354     if (state->focused && !state->disabled)
355         GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
356
357     if (!interior_focus && state->focused) {
358         x += focus_width + focus_pad;
359         y += focus_width + focus_pad;
360         width -= 2 * (focus_width + focus_pad);
361         height -= 2 * (focus_width + focus_pad);
362     }
363
364     shadow_type = button_state == GTK_STATE_ACTIVE ||
365                       state->depressed ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
366  
367     if (state->isDefault && relief == GTK_RELIEF_NORMAL) {
368         gtk_paint_box(style, drawable, button_state, shadow_type, cliprect,
369                       widget, "buttondefault", x, y, width, height);                   
370     }
371  
372     if (relief != GTK_RELIEF_NONE || state->depressed ||
373            (button_state != GTK_STATE_NORMAL &&
374             button_state != GTK_STATE_INSENSITIVE)) {
375         TSOffsetStyleGCs(style, x, y);
376         /* the following line can trigger an assertion (Crux theme)
377            file ../../gdk/gdkwindow.c: line 1846 (gdk_window_clear_area):
378            assertion `GDK_IS_WINDOW (window)' failed */
379         gtk_paint_box(style, drawable, button_state, shadow_type, cliprect,
380                       widget, "button", x, y, width, height);
381     }
382
383     if (state->focused) {
384         if (interior_focus) {
385             GtkStyle* style = gtk_widget_get_style(widget);
386             x += style->xthickness + focus_pad;
387             y += style->ythickness + focus_pad;
388             width -= 2 * (style->xthickness + focus_pad);
389             height -= 2 * (style->ythickness + focus_pad);
390         } else {
391             x -= focus_width + focus_pad;
392             y -= focus_width + focus_pad;
393             width += 2 * (focus_width + focus_pad);
394             height += 2 * (focus_width + focus_pad);
395         }
396
397         TSOffsetStyleGCs(style, x, y);
398         gtk_paint_focus(style, drawable, button_state, cliprect,
399                         widget, "button", x, y, width, height);
400     }
401
402     GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_DEFAULT);
403     GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
404     return MOZ_GTK_SUCCESS;
405 }
406
407 gint
408 moz_gtk_init()
409 {
410     GtkWidgetClass *entry_class;
411
412     is_initialized = TRUE;
413     have_arrow_scaling = (gtk_major_version > 2 ||
414                           (gtk_major_version == 2 && gtk_minor_version >= 12));
415
416     /* Add style property to GtkEntry.
417      * Adding the style property to the normal GtkEntry class means that it
418      * will work without issues inside GtkComboBox and for Spinbuttons. */
419     entry_class = g_type_class_ref(GTK_TYPE_ENTRY);
420     gtk_widget_class_install_style_property(entry_class,
421         g_param_spec_boolean("honors-transparent-bg-hint",
422                              "Transparent BG enabling flag",
423                              "If TRUE, the theme is able to draw the GtkEntry on non-prefilled background.",
424                              FALSE,
425                              G_PARAM_READWRITE));
426
427     return MOZ_GTK_SUCCESS;
428 }
429
430 gint
431 moz_gtk_widget_get_focus(GtkWidget* widget, gboolean* interior_focus,
432                          gint* focus_width, gint* focus_pad) 
433 {
434     gtk_widget_style_get (widget,
435                           "interior-focus", interior_focus,
436                           "focus-line-width", focus_width,
437                           "focus-padding", focus_pad,
438                           NULL);
439
440     return MOZ_GTK_SUCCESS;
441 }
442
443 gint
444 moz_gtk_button_get_inner_border(GtkWidget* widget, GtkBorder* inner_border)
445 {
446     static const GtkBorder default_inner_border = { 1, 1, 1, 1 };
447     GtkBorder *tmp_border;
448
449     gtk_widget_style_get (widget, "inner-border", &tmp_border, NULL);
450
451     if (tmp_border) {
452         *inner_border = *tmp_border;
453         gtk_border_free(tmp_border);
454     }
455     else
456         *inner_border = default_inner_border;
457
458     return MOZ_GTK_SUCCESS;
459 }
460
461 static gint
462 calculate_button_inner_rect(GtkWidget* button, GdkRectangle* rect,
463                             GdkRectangle* inner_rect,
464                             GtkTextDirection direction,
465                             gboolean ignore_focus)
466 {
467     GtkBorder inner_border;
468     gboolean interior_focus;
469     gint focus_width, focus_pad;
470     GtkStyle* style;
471
472     style = gtk_widget_get_style(button);
473
474     /* This mirrors gtkbutton's child positioning */
475     moz_gtk_button_get_inner_border(button, &inner_border);
476     moz_gtk_widget_get_focus(button, &interior_focus,
477                              &focus_width, &focus_pad);
478
479     if (ignore_focus)
480         focus_width = focus_pad = 0;
481
482     inner_rect->x = rect->x + XTHICKNESS(style) + focus_width + focus_pad;
483     inner_rect->x += direction == GTK_TEXT_DIR_LTR ?
484                         inner_border.left : inner_border.right;
485     inner_rect->y = rect->y + inner_border.top + YTHICKNESS(style) +
486                     focus_width + focus_pad;
487     inner_rect->width = MAX(1, rect->width - inner_border.left -
488        inner_border.right - (XTHICKNESS(style) + focus_pad + focus_width) * 2);
489     inner_rect->height = MAX(1, rect->height - inner_border.top -
490        inner_border.bottom - (YTHICKNESS(style) + focus_pad + focus_width) * 2);
491
492     return MOZ_GTK_SUCCESS;
493 }
494
495
496 static gint
497 calculate_arrow_rect(GtkWidget* arrow, GdkRectangle* rect,
498                      GdkRectangle* arrow_rect, GtkTextDirection direction)
499 {
500     /* defined in gtkarrow.c */
501     gfloat arrow_scaling = 0.7;
502     gfloat xalign, xpad;
503     gint extent;
504     GtkMisc* misc = GTK_MISC(arrow);
505     gfloat misc_xalign, misc_yalign;
506     gint misc_xpad, misc_ypad;
507
508     if (have_arrow_scaling)
509         gtk_widget_style_get(arrow, "arrow_scaling", &arrow_scaling, NULL);
510
511     gtk_misc_get_padding(misc, &misc_xpad, &misc_ypad);
512     gtk_misc_get_alignment(misc, &misc_xalign, &misc_yalign);
513
514     extent = MIN((rect->width - misc_xpad * 2),
515                  (rect->height - misc_ypad * 2)) * arrow_scaling;
516
517     xalign = direction == GTK_TEXT_DIR_LTR ? misc_xalign : 1.0 - misc_xalign;
518     xpad = misc_xpad + (rect->width - extent) * xalign;
519
520     arrow_rect->x = direction == GTK_TEXT_DIR_LTR ?
521                         floor(rect->x + xpad) : ceil(rect->x + xpad);
522     arrow_rect->y = floor(rect->y + misc_ypad +
523                           ((rect->height - extent) * misc_yalign));
524
525     arrow_rect->width = arrow_rect->height = extent;
526
527     return MOZ_GTK_SUCCESS;
528 }
529
530 static gint
531 moz_gtk_scrolled_window_paint(GdkDrawable* drawable, GdkRectangle* rect,
532                               GdkRectangle* cliprect, GtkWidgetState* state)
533 {
534     GtkStyle* style;
535     GtkAllocation allocation;
536     GtkWidget* widget;
537
538     ensure_scrolled_window_widget();
539     widget = gParts->scrolledWindowWidget;
540
541     gtk_widget_get_allocation(widget, &allocation);
542     allocation.x = rect->x;
543     allocation.y = rect->y;
544     allocation.width = rect->width;
545     allocation.height = rect->height;
546     gtk_widget_set_allocation(widget, &allocation);
547
548     style = gtk_widget_get_style(widget);
549     TSOffsetStyleGCs(style, rect->x - 1, rect->y - 1);
550     gtk_paint_shadow(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_IN,
551                      cliprect, gParts->scrolledWindowWidget, "scrolled_window",
552                      rect->x, rect->y, rect->width, rect->height);
553     return MOZ_GTK_SUCCESS;
554 }
555
556 static gint
557 moz_gtk_scrollbar_button_paint(GdkDrawable* drawable, GdkRectangle* rect,
558                                GdkRectangle* cliprect, GtkWidgetState* state,
559                                GtkScrollbarButtonFlags flags,
560                                GtkTextDirection direction)
561 {
562     GtkStateType state_type = ConvertGtkState(state);
563     GtkShadowType shadow_type = (state->active) ?
564         GTK_SHADOW_IN : GTK_SHADOW_OUT;
565     GdkRectangle arrow_rect;
566     GtkStyle* style;
567     GtkWidget *scrollbar;
568     GtkAllocation allocation;
569     GtkArrowType arrow_type;
570     gint arrow_displacement_x, arrow_displacement_y;
571     const char* detail = (flags & MOZ_GTK_STEPPER_VERTICAL) ?
572                            "vscrollbar" : "hscrollbar";
573
574     ensure_scrollbar_widget();
575
576     if (flags & MOZ_GTK_STEPPER_VERTICAL)
577         scrollbar = gParts->vertScrollbarWidget;
578     else
579         scrollbar = gParts->horizScrollbarWidget;
580
581     gtk_widget_set_direction(scrollbar, direction);
582
583     /* Some theme engines (i.e., ClearLooks) check the scrollbar's allocation
584        to determine where it should paint rounded corners on the buttons.
585        We need to trick them into drawing the buttons the way we want them. */
586
587     gtk_widget_get_allocation(scrollbar, &allocation);
588     allocation.x = rect->x;
589     allocation.y = rect->y;
590     allocation.width = rect->width;
591     allocation.height = rect->height;
592
593     if (flags & MOZ_GTK_STEPPER_VERTICAL) {
594         allocation.height *= 5;
595         if (flags & MOZ_GTK_STEPPER_DOWN) {
596             arrow_type = GTK_ARROW_DOWN;
597             if (flags & MOZ_GTK_STEPPER_BOTTOM)
598                 allocation.y -= 4 * rect->height;
599             else
600                 allocation.y -= rect->height;
601
602         } else {
603             arrow_type = GTK_ARROW_UP;
604             if (flags & MOZ_GTK_STEPPER_BOTTOM)
605                 allocation.y -= 3 * rect->height;
606         }
607     } else {
608         allocation.width *= 5;
609         if (flags & MOZ_GTK_STEPPER_DOWN) {
610             arrow_type = GTK_ARROW_RIGHT;
611             if (flags & MOZ_GTK_STEPPER_BOTTOM)
612                 allocation.x -= 4 * rect->width;
613             else
614                 allocation.x -= rect->width;
615         } else {
616             arrow_type = GTK_ARROW_LEFT;
617             if (flags & MOZ_GTK_STEPPER_BOTTOM)
618                 allocation.x -= 3 * rect->width;
619         }
620     }
621
622     gtk_widget_set_allocation(scrollbar, &allocation);
623     style = gtk_widget_get_style(scrollbar);
624
625     TSOffsetStyleGCs(style, rect->x, rect->y);
626
627     gtk_paint_box(style, drawable, state_type, shadow_type, cliprect,
628                   scrollbar, detail, rect->x, rect->y,
629                   rect->width, rect->height);
630
631     arrow_rect.width = rect->width / 2;
632     arrow_rect.height = rect->height / 2;
633     arrow_rect.x = rect->x + (rect->width - arrow_rect.width) / 2;
634     arrow_rect.y = rect->y + (rect->height - arrow_rect.height) / 2;
635
636     if (state_type == GTK_STATE_ACTIVE) {
637         gtk_widget_style_get(scrollbar,
638                              "arrow-displacement-x", &arrow_displacement_x,
639                              "arrow-displacement-y", &arrow_displacement_y,
640                              NULL);
641
642         arrow_rect.x += arrow_displacement_x;
643         arrow_rect.y += arrow_displacement_y;
644     }
645
646     gtk_paint_arrow(style, drawable, state_type, shadow_type, cliprect,
647                     scrollbar, detail, arrow_type, TRUE, arrow_rect.x,
648                     arrow_rect.y, arrow_rect.width, arrow_rect.height);
649
650     return MOZ_GTK_SUCCESS;
651 }
652
653 static gint
654 moz_gtk_scrollbar_trough_paint(GtkThemeWidgetType widget,
655                                GdkDrawable* drawable, GdkRectangle* rect,
656                                GdkRectangle* cliprect, GtkWidgetState* state,
657                                GtkTextDirection direction)
658 {
659     GtkStyle* style;
660     GtkScrollbar *scrollbar;
661
662     ensure_scrollbar_widget();
663
664     if (widget ==  MOZ_GTK_SCROLLBAR_TRACK_HORIZONTAL)
665         scrollbar = GTK_SCROLLBAR(gParts->horizScrollbarWidget);
666     else
667         scrollbar = GTK_SCROLLBAR(gParts->vertScrollbarWidget);
668
669     gtk_widget_set_direction(GTK_WIDGET(scrollbar), direction);
670
671     style = gtk_widget_get_style(GTK_WIDGET(scrollbar));
672
673     TSOffsetStyleGCs(style, rect->x, rect->y);
674     gtk_paint_box(style, drawable, GTK_STATE_ACTIVE, GTK_SHADOW_IN, cliprect,
675                   GTK_WIDGET(scrollbar), "trough", rect->x, rect->y,
676                   rect->width, rect->height);
677
678     if (state->focused) {
679         gtk_paint_focus(style, drawable, GTK_STATE_ACTIVE, cliprect,
680                         GTK_WIDGET(scrollbar), "trough",
681                         rect->x, rect->y, rect->width, rect->height);
682     }
683
684     return MOZ_GTK_SUCCESS;
685 }
686
687 static gint
688 moz_gtk_scrollbar_thumb_paint(GtkThemeWidgetType widget,
689                               GdkDrawable* drawable, GdkRectangle* rect,
690                               GdkRectangle* cliprect, GtkWidgetState* state,
691                               GtkTextDirection direction)
692 {
693     GtkStateType state_type = (state->inHover || state->active) ?
694         GTK_STATE_PRELIGHT : GTK_STATE_NORMAL;
695     GtkShadowType shadow_type = GTK_SHADOW_OUT;
696     GtkStyle* style;
697     GtkScrollbar *scrollbar;
698     GtkAdjustment *adj;
699     gboolean activate_slider;
700
701     ensure_scrollbar_widget();
702
703     if (widget == MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL)
704         scrollbar = GTK_SCROLLBAR(gParts->horizScrollbarWidget);
705     else
706         scrollbar = GTK_SCROLLBAR(gParts->vertScrollbarWidget);
707
708     gtk_widget_set_direction(GTK_WIDGET(scrollbar), direction);
709
710     /* Make sure to set the scrollbar range before painting so that
711        everything is drawn properly.  At least the bluecurve (and
712        maybe other) themes don't draw the top or bottom black line
713        surrounding the scrollbar if the theme thinks that it's butted
714        up against the scrollbar arrows.  Note the increases of the
715        clip rect below. */
716     /* Changing the cliprect is pretty bogus. This lets themes draw
717        outside the frame, which means we don't invalidate them
718        correctly. See bug 297508. But some themes do seem to need
719        it. So we modify the frame's overflow area to account for what
720        we're doing here; see nsNativeThemeGTK::GetWidgetOverflow. */
721     adj = gtk_range_get_adjustment(GTK_RANGE(scrollbar));
722
723     if (widget == MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL) {
724         cliprect->x -= 1;
725         cliprect->width += 2;
726         gtk_adjustment_set_page_size(adj, rect->width);
727     }
728     else {
729         cliprect->y -= 1;
730         cliprect->height += 2;
731         gtk_adjustment_set_page_size(adj, rect->height);
732     }
733
734 #if GTK_CHECK_VERSION(2, 14, 0)
735     gtk_adjustment_configure(adj,
736                              state->curpos,
737                              0,
738                              state->maxpos,
739                              gtk_adjustment_get_step_increment(adj),
740                              gtk_adjustment_get_page_increment(adj),
741                              gtk_adjustment_get_page_size(adj));
742 #else
743     adj->lower = 0;
744     adj->value = state->curpos;
745     adj->upper = state->maxpos;
746     gtk_adjustment_changed(adj);
747 #endif
748
749     style = gtk_widget_get_style(GTK_WIDGET(scrollbar));
750     
751     gtk_widget_style_get(GTK_WIDGET(scrollbar), "activate-slider",
752                          &activate_slider, NULL);
753     
754     if (activate_slider && state->active) {
755         shadow_type = GTK_SHADOW_IN;
756         state_type = GTK_STATE_ACTIVE;
757     }
758
759     TSOffsetStyleGCs(style, rect->x, rect->y);
760
761     gtk_paint_slider(style, drawable, state_type, shadow_type, cliprect,
762                      GTK_WIDGET(scrollbar), "slider", rect->x, rect->y,
763                      rect->width,  rect->height,
764                      (widget == MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL) ?
765                      GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL);
766
767     return MOZ_GTK_SUCCESS;
768 }
769
770 static gint
771 moz_gtk_combo_box_paint(GdkDrawable* drawable, GdkRectangle* rect,
772                         GdkRectangle* cliprect, GtkWidgetState* state,
773                         gboolean ishtml, GtkTextDirection direction)
774 {
775     GdkRectangle arrow_rect, real_arrow_rect;
776     gint /* arrow_size, */ separator_width;
777     gboolean wide_separators;
778     GtkStateType state_type = ConvertGtkState(state);
779     GtkShadowType shadow_type = state->active ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
780     GtkStyle* style;
781     GtkRequisition arrow_req;
782
783     ensure_combo_box_widgets();
784
785     /* Also sets the direction on gParts->comboBoxButtonWidget, which is then
786      * inherited by the separator and arrow */
787     moz_gtk_button_paint(drawable, rect, cliprect, state, GTK_RELIEF_NORMAL,
788                          gParts->comboBoxButtonWidget, direction);
789
790     calculate_button_inner_rect(gParts->comboBoxButtonWidget,
791                                 rect, &arrow_rect, direction, ishtml);
792     /* Now arrow_rect contains the inner rect ; we want to correct the width
793      * to what the arrow needs (see gtk_combo_box_size_allocate) */
794     gtk_widget_size_request(gParts->comboBoxArrowWidget, &arrow_req);
795     if (direction == GTK_TEXT_DIR_LTR)
796         arrow_rect.x += arrow_rect.width - arrow_req.width;
797     arrow_rect.width = arrow_req.width;
798
799     calculate_arrow_rect(gParts->comboBoxArrowWidget,
800                          &arrow_rect, &real_arrow_rect, direction);
801
802     style = gtk_widget_get_style(gParts->comboBoxArrowWidget);
803     TSOffsetStyleGCs(style, rect->x, rect->y);
804
805     gtk_widget_size_allocate(gParts->comboBoxWidget, rect);
806
807     gtk_paint_arrow(style, drawable, state_type, shadow_type, cliprect,
808                     gParts->comboBoxArrowWidget, "arrow",  GTK_ARROW_DOWN, TRUE,
809                     real_arrow_rect.x, real_arrow_rect.y,
810                     real_arrow_rect.width, real_arrow_rect.height);
811
812
813     /* If there is no separator in the theme, there's nothing left to do. */
814     if (!gParts->comboBoxSeparatorWidget)
815         return MOZ_GTK_SUCCESS;
816
817     style = gtk_widget_get_style(gParts->comboBoxSeparatorWidget);
818     TSOffsetStyleGCs(style, rect->x, rect->y);
819
820     gtk_widget_style_get(gParts->comboBoxSeparatorWidget,
821                          "wide-separators", &wide_separators,
822                          "separator-width", &separator_width,
823                          NULL);
824
825     if (wide_separators) {
826         if (direction == GTK_TEXT_DIR_LTR)
827             arrow_rect.x -= separator_width;
828         else
829             arrow_rect.x += arrow_rect.width;
830
831         gtk_paint_box(style, drawable,
832                       GTK_STATE_NORMAL, GTK_SHADOW_ETCHED_OUT,
833                       cliprect, gParts->comboBoxSeparatorWidget, "vseparator",
834                       arrow_rect.x, arrow_rect.y,
835                       separator_width, arrow_rect.height);
836     } else {
837         if (direction == GTK_TEXT_DIR_LTR)
838             arrow_rect.x -= XTHICKNESS(style);
839         else
840             arrow_rect.x += arrow_rect.width;
841
842         gtk_paint_vline(style, drawable, GTK_STATE_NORMAL, cliprect,
843                         gParts->comboBoxSeparatorWidget, "vseparator",
844                         arrow_rect.y, arrow_rect.y + arrow_rect.height,
845                         arrow_rect.x);
846     }
847
848     return MOZ_GTK_SUCCESS;
849 }
850
851 static gint
852 moz_gtk_progressbar_paint(GdkDrawable* drawable, GdkRectangle* rect,
853                           GdkRectangle* cliprect, GtkTextDirection direction)
854 {
855     GtkStyle* style;
856
857     ensure_progress_widget();
858     gtk_widget_set_direction(gParts->progresWidget, direction);
859
860     style = gtk_widget_get_style(gParts->progresWidget);
861
862     TSOffsetStyleGCs(style, rect->x, rect->y);
863     gtk_paint_box(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_IN,
864                   cliprect, gParts->progresWidget, "trough", rect->x, rect->y,
865                   rect->width, rect->height);
866
867     return MOZ_GTK_SUCCESS;
868 }
869
870 static gint
871 moz_gtk_progress_chunk_paint(GdkDrawable* drawable, GdkRectangle* rect,
872                              GdkRectangle* cliprect, GtkTextDirection direction)
873 {
874     GtkStyle* style;
875
876     ensure_progress_widget();
877     gtk_widget_set_direction(gParts->progresWidget, direction);
878
879     style = gtk_widget_get_style(gParts->progresWidget);
880
881     TSOffsetStyleGCs(style, rect->x, rect->y);
882     gtk_paint_box(style, drawable, GTK_STATE_PRELIGHT, GTK_SHADOW_OUT,
883                   cliprect, gParts->progresWidget, "bar", rect->x, rect->y,
884                   rect->width, rect->height);
885
886     return MOZ_GTK_SUCCESS;
887 }
888
889 gint
890 moz_gtk_get_widget_border(GtkThemeWidgetType widget, gint* left, gint* top,
891                           gint* right, gint* bottom, GtkTextDirection direction,
892                           gboolean inhtml)
893 {
894     GtkWidget* w;
895     GtkStyle *style;
896
897     switch (widget) {
898     case MOZ_GTK_BUTTON:
899         {
900             GtkBorder inner_border;
901             gboolean interior_focus;
902             gint focus_width, focus_pad;
903             GtkStyle *style;
904
905             ensure_button_widget();
906             *left = *top = *right = *bottom = gtk_container_get_border_width(GTK_CONTAINER(gParts->buttonWidget));
907
908             /* Don't add this padding in HTML, otherwise the buttons will
909                become too big and stuff the layout. */
910             if (!inhtml) {
911                 moz_gtk_widget_get_focus(gParts->buttonWidget, &interior_focus, &focus_width, &focus_pad);
912                 moz_gtk_button_get_inner_border(gParts->buttonWidget, &inner_border);
913                 *left += focus_width + focus_pad + inner_border.left;
914                 *right += focus_width + focus_pad + inner_border.right;
915                 *top += focus_width + focus_pad + inner_border.top;
916                 *bottom += focus_width + focus_pad + inner_border.bottom;
917             }
918
919             style = gtk_widget_get_style(gParts->buttonWidget);
920             *left += style->xthickness;
921             *right += style->xthickness;
922             *top += style->ythickness;
923             *bottom += style->ythickness;
924             return MOZ_GTK_SUCCESS;
925         }
926     case MOZ_GTK_DROPDOWN:
927         {
928             /* We need to account for the arrow on the dropdown, so text
929              * doesn't come too close to the arrow, or in some cases spill
930              * into the arrow. */
931             gboolean ignored_interior_focus, wide_separators;
932             gint focus_width, focus_pad, separator_width;
933             GtkRequisition arrow_req;
934             GtkStyle* style;
935
936             ensure_combo_box_widgets();
937
938             *left = gtk_container_get_border_width(GTK_CONTAINER(gParts->comboBoxButtonWidget));
939
940             if (!inhtml) {
941                 moz_gtk_widget_get_focus(gParts->comboBoxButtonWidget,
942                                          &ignored_interior_focus,
943                                          &focus_width, &focus_pad);
944                 *left += focus_width + focus_pad;
945             }
946
947             style = gtk_widget_get_style(gParts->comboBoxButtonWidget);
948             *top = *left + style->ythickness;
949             *left += style->xthickness;
950
951             *right = *left; *bottom = *top;
952
953             /* If there is no separator, don't try to count its width. */
954             separator_width = 0;
955             if (gParts->comboBoxSeparatorWidget) {
956                 gtk_widget_style_get(gParts->comboBoxSeparatorWidget,
957                                      "wide-separators", &wide_separators,
958                                      "separator-width", &separator_width,
959                                      NULL);
960
961                 if (!wide_separators)
962                     separator_width =
963                         XTHICKNESS(style);
964             }
965
966             gtk_widget_size_request(gParts->comboBoxArrowWidget, &arrow_req);
967             if (direction == GTK_TEXT_DIR_RTL)
968                 *left += separator_width + arrow_req.width;
969             else
970                 *right += separator_width + arrow_req.width;
971
972             return MOZ_GTK_SUCCESS;
973         }
974     case MOZ_GTK_PROGRESSBAR:
975         ensure_progress_widget();
976         w = gParts->progresWidget;
977         break;
978     /* These widgets have no borders, since they are not containers. */
979     case MOZ_GTK_SCROLLBAR_BUTTON:
980     case MOZ_GTK_SCROLLBAR_TRACK_HORIZONTAL:
981     case MOZ_GTK_SCROLLBAR_TRACK_VERTICAL:
982     case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
983     case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
984     case MOZ_GTK_PROGRESS_CHUNK:
985         *left = *top = *right = *bottom = 0;
986         return MOZ_GTK_SUCCESS;
987     default:
988         g_warning("Unsupported widget type: %d", widget);
989         return MOZ_GTK_UNKNOWN_WIDGET;
990     }
991
992     style = gtk_widget_get_style(w);
993     *right = *left = XTHICKNESS(style);
994     *bottom = *top = YTHICKNESS(style);
995
996     return MOZ_GTK_SUCCESS;
997 }
998
999 gint
1000 moz_gtk_get_scrollbar_metrics(MozGtkScrollbarMetrics *metrics)
1001 {
1002     ensure_scrollbar_widget();
1003
1004     gtk_widget_style_get (gParts->horizScrollbarWidget,
1005                           "slider_width", &metrics->slider_width,
1006                           "trough_border", &metrics->trough_border,
1007                           "stepper_size", &metrics->stepper_size,
1008                           "stepper_spacing", &metrics->stepper_spacing,
1009                           "trough_under_steppers", &metrics->trough_under_steppers,
1010                           "has_secondary_forward_stepper", &metrics->has_secondary_forward_stepper,
1011                           "has_secondary_backward_stepper", &metrics->has_secondary_backward_stepper,
1012                           NULL);
1013
1014     metrics->min_slider_size = gtk_range_get_min_slider_size(GTK_RANGE(gParts->horizScrollbarWidget));
1015
1016     return MOZ_GTK_SUCCESS;
1017 }
1018
1019 gint
1020 moz_gtk_widget_paint(GtkThemeWidgetType widget, GdkDrawable* drawable,
1021                      GdkRectangle* rect, GdkRectangle* cliprect,
1022                      GtkWidgetState* state, gint flags,
1023                      GtkTextDirection direction)
1024 {
1025     switch (widget) {
1026     case MOZ_GTK_BUTTON:
1027         if (state->depressed) {
1028             ensure_toggle_button_widget();
1029             return moz_gtk_button_paint(drawable, rect, cliprect, state,
1030                                         (GtkReliefStyle) flags,
1031                                         gParts->toggleButtonWidget, direction);
1032         }
1033         ensure_button_widget();
1034         return moz_gtk_button_paint(drawable, rect, cliprect, state,
1035                                     (GtkReliefStyle) flags, gParts->buttonWidget,
1036                                     direction);
1037         break;
1038     case MOZ_GTK_SCROLLBAR_BUTTON:
1039         return moz_gtk_scrollbar_button_paint(drawable, rect, cliprect, state,
1040                                               (GtkScrollbarButtonFlags) flags,
1041                                               direction);
1042         break;
1043     case MOZ_GTK_SCROLLBAR_TRACK_HORIZONTAL:
1044     case MOZ_GTK_SCROLLBAR_TRACK_VERTICAL:
1045         return moz_gtk_scrollbar_trough_paint(widget, drawable, rect,
1046                                               cliprect, state, direction);
1047         break;
1048     case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
1049     case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
1050         return moz_gtk_scrollbar_thumb_paint(widget, drawable, rect,
1051                                              cliprect, state, direction);
1052         break;
1053     case MOZ_GTK_SCROLLED_WINDOW:
1054         return moz_gtk_scrolled_window_paint(drawable, rect, cliprect, state);
1055         break;
1056     case MOZ_GTK_DROPDOWN:
1057         return moz_gtk_combo_box_paint(drawable, rect, cliprect, state,
1058                                        (gboolean) flags, direction);
1059         break;
1060     case MOZ_GTK_PROGRESSBAR:
1061         return moz_gtk_progressbar_paint(drawable, rect, cliprect, direction);
1062         break;
1063     case MOZ_GTK_PROGRESS_CHUNK:
1064         return moz_gtk_progress_chunk_paint(drawable, rect, cliprect,
1065                                             direction);
1066         break;
1067     default:
1068         g_warning("Unknown widget type: %d", widget);
1069     }
1070
1071     return MOZ_GTK_UNKNOWN_WIDGET;
1072 }
1073
1074 GtkWidget* moz_gtk_get_scrollbar_widget(void)
1075 {
1076     if (!is_initialized)
1077         return NULL;
1078     ensure_scrollbar_widget();
1079     return gParts->horizScrollbarWidget;
1080 }
1081
1082 gint
1083 moz_gtk_shutdown()
1084 {
1085     GtkWidgetClass *entry_class;
1086     entry_class = g_type_class_peek(GTK_TYPE_ENTRY);
1087     g_type_class_unref(entry_class);
1088
1089     is_initialized = FALSE;
1090
1091     return MOZ_GTK_SUCCESS;
1092 }
1093
1094 void moz_gtk_destroy_theme_parts_widgets(GtkThemeParts* parts)
1095 {
1096     if (!parts)
1097         return;
1098
1099     if (parts->protoWindow) {
1100         gtk_widget_destroy(parts->protoWindow);
1101         parts->protoWindow = NULL;
1102     }
1103 }
1104
1105 GtkWidget* moz_gtk_get_progress_widget()
1106 {
1107     if (!is_initialized)
1108         return NULL;
1109     ensure_progress_widget();
1110     return gParts->progresWidget;
1111 }
1112
1113 #endif // GTK_API_VERSION_2