2011-02-08 Dirk Pranke <dpranke@chromium.org>
[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 is_initialized;
64
65 void
66 moz_gtk_use_theme_parts(GtkThemeParts* parts)
67 {
68     gParts = parts;
69 }
70
71 /* Because we have such an unconventional way of drawing widgets, signal to the GTK theme engine
72    that they are drawing for Mozilla instead of a conventional GTK app so they can do any specific
73    things they may want to do. */
74 static void
75 moz_gtk_set_widget_name(GtkWidget* widget)
76 {
77     gtk_widget_set_name(widget, "MozillaGtkWidget");
78 }
79
80 gint
81 moz_gtk_enable_style_props(style_prop_t styleGetProp)
82 {
83     style_prop_func = styleGetProp;
84     return MOZ_GTK_SUCCESS;
85 }
86
87 static gint
88 ensure_window_widget()
89 {
90     if (!gParts->protoWindow) {
91         gParts->protoWindow = gtk_window_new(GTK_WINDOW_POPUP);
92
93         if (gParts->colormap)
94             gtk_widget_set_colormap(gParts->protoWindow, gParts->colormap);
95
96         gtk_widget_realize(gParts->protoWindow);
97         moz_gtk_set_widget_name(gParts->protoWindow);
98     }
99     return MOZ_GTK_SUCCESS;
100 }
101
102 static gint
103 setup_widget_prototype(GtkWidget* widget)
104 {
105     ensure_window_widget();
106     if (!gParts->protoLayout) {
107         gParts->protoLayout = gtk_fixed_new();
108         gtk_container_add(GTK_CONTAINER(gParts->protoWindow), gParts->protoLayout);
109     }
110
111     gtk_container_add(GTK_CONTAINER(gParts->protoLayout), widget);
112     gtk_widget_realize(widget);
113     g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE));
114     return MOZ_GTK_SUCCESS;
115 }
116
117 static gint
118 ensure_scrollbar_widget()
119 {
120     if (!gParts->vertScrollbarWidget) {
121         gParts->vertScrollbarWidget = gtk_vscrollbar_new(NULL);
122         setup_widget_prototype(gParts->vertScrollbarWidget);
123     }
124     if (!gParts->horizScrollbarWidget) {
125         gParts->horizScrollbarWidget = gtk_hscrollbar_new(NULL);
126         setup_widget_prototype(gParts->horizScrollbarWidget);
127     }
128     return MOZ_GTK_SUCCESS;
129 }
130
131 static gint
132 ensure_scrolled_window_widget()
133 {
134     if (!gParts->scrolledWindowWidget) {
135         gParts->scrolledWindowWidget = gtk_scrolled_window_new(NULL, NULL);
136         setup_widget_prototype(gParts->scrolledWindowWidget);
137     }
138     return MOZ_GTK_SUCCESS;
139 }
140
141 static GtkStateType
142 ConvertGtkState(GtkWidgetState* state)
143 {
144     if (state->disabled)
145         return GTK_STATE_INSENSITIVE;
146     else if (state->depressed)
147         return (state->inHover ? GTK_STATE_PRELIGHT : GTK_STATE_ACTIVE);
148     else if (state->inHover)
149         return (state->active ? GTK_STATE_ACTIVE : GTK_STATE_PRELIGHT);
150     else
151         return GTK_STATE_NORMAL;
152 }
153
154 static gint
155 TSOffsetStyleGCArray(GdkGC** gcs, gint xorigin, gint yorigin)
156 {
157     int i;
158     /* there are 5 gc's in each array, for each of the widget states */
159     for (i = 0; i < 5; ++i)
160         gdk_gc_set_ts_origin(gcs[i], xorigin, yorigin);
161     return MOZ_GTK_SUCCESS;
162 }
163
164 static gint
165 TSOffsetStyleGCs(GtkStyle* style, gint xorigin, gint yorigin)
166 {
167     TSOffsetStyleGCArray(style->fg_gc, xorigin, yorigin);
168     TSOffsetStyleGCArray(style->bg_gc, xorigin, yorigin);
169     TSOffsetStyleGCArray(style->light_gc, xorigin, yorigin);
170     TSOffsetStyleGCArray(style->dark_gc, xorigin, yorigin);
171     TSOffsetStyleGCArray(style->mid_gc, xorigin, yorigin);
172     TSOffsetStyleGCArray(style->text_gc, xorigin, yorigin);
173     TSOffsetStyleGCArray(style->base_gc, xorigin, yorigin);
174     gdk_gc_set_ts_origin(style->black_gc, xorigin, yorigin);
175     gdk_gc_set_ts_origin(style->white_gc, xorigin, yorigin);
176     return MOZ_GTK_SUCCESS;
177 }
178
179 gint
180 moz_gtk_init()
181 {
182     GtkWidgetClass *entry_class;
183
184     is_initialized = TRUE;
185
186     /* Add style property to GtkEntry.
187      * Adding the style property to the normal GtkEntry class means that it
188      * will work without issues inside GtkComboBox and for Spinbuttons. */
189     entry_class = g_type_class_ref(GTK_TYPE_ENTRY);
190     gtk_widget_class_install_style_property(entry_class,
191         g_param_spec_boolean("honors-transparent-bg-hint",
192                              "Transparent BG enabling flag",
193                              "If TRUE, the theme is able to draw the GtkEntry on non-prefilled background.",
194                              FALSE,
195                              G_PARAM_READWRITE));
196
197     return MOZ_GTK_SUCCESS;
198 }
199
200 static gint
201 moz_gtk_scrolled_window_paint(GdkDrawable* drawable, GdkRectangle* rect,
202                               GdkRectangle* cliprect, GtkWidgetState* state)
203 {
204     GtkStyle* style;
205     GtkAllocation allocation;
206     GtkWidget* widget;
207
208     ensure_scrolled_window_widget();
209     widget = gParts->scrolledWindowWidget;
210
211     gtk_widget_get_allocation(widget, &allocation);
212     allocation.x = rect->x;
213     allocation.y = rect->y;
214     allocation.width = rect->width;
215     allocation.height = rect->height;
216     gtk_widget_set_allocation(widget, &allocation);
217
218     style = gtk_widget_get_style(widget);
219     TSOffsetStyleGCs(style, rect->x - 1, rect->y - 1);
220     gtk_paint_shadow(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_IN,
221                      cliprect, gParts->scrolledWindowWidget, "scrolled_window",
222                      rect->x, rect->y, rect->width, rect->height);
223     return MOZ_GTK_SUCCESS;
224 }
225
226 static gint
227 moz_gtk_scrollbar_button_paint(GdkDrawable* drawable, GdkRectangle* rect,
228                                GdkRectangle* cliprect, GtkWidgetState* state,
229                                GtkScrollbarButtonFlags flags,
230                                GtkTextDirection direction)
231 {
232     GtkStateType state_type = ConvertGtkState(state);
233     GtkShadowType shadow_type = (state->active) ?
234         GTK_SHADOW_IN : GTK_SHADOW_OUT;
235     GdkRectangle arrow_rect;
236     GtkStyle* style;
237     GtkWidget *scrollbar;
238     GtkAllocation allocation;
239     GtkArrowType arrow_type;
240     gint arrow_displacement_x, arrow_displacement_y;
241     const char* detail = (flags & MOZ_GTK_STEPPER_VERTICAL) ?
242                            "vscrollbar" : "hscrollbar";
243
244     ensure_scrollbar_widget();
245
246     if (flags & MOZ_GTK_STEPPER_VERTICAL)
247         scrollbar = gParts->vertScrollbarWidget;
248     else
249         scrollbar = gParts->horizScrollbarWidget;
250
251     gtk_widget_set_direction(scrollbar, direction);
252
253     /* Some theme engines (i.e., ClearLooks) check the scrollbar's allocation
254        to determine where it should paint rounded corners on the buttons.
255        We need to trick them into drawing the buttons the way we want them. */
256
257     gtk_widget_get_allocation(scrollbar, &allocation);
258     allocation.x = rect->x;
259     allocation.y = rect->y;
260     allocation.width = rect->width;
261     allocation.height = rect->height;
262
263     if (flags & MOZ_GTK_STEPPER_VERTICAL) {
264         allocation.height *= 5;
265         if (flags & MOZ_GTK_STEPPER_DOWN) {
266             arrow_type = GTK_ARROW_DOWN;
267             if (flags & MOZ_GTK_STEPPER_BOTTOM)
268                 allocation.y -= 4 * rect->height;
269             else
270                 allocation.y -= rect->height;
271
272         } else {
273             arrow_type = GTK_ARROW_UP;
274             if (flags & MOZ_GTK_STEPPER_BOTTOM)
275                 allocation.y -= 3 * rect->height;
276         }
277     } else {
278         allocation.width *= 5;
279         if (flags & MOZ_GTK_STEPPER_DOWN) {
280             arrow_type = GTK_ARROW_RIGHT;
281             if (flags & MOZ_GTK_STEPPER_BOTTOM)
282                 allocation.x -= 4 * rect->width;
283             else
284                 allocation.x -= rect->width;
285         } else {
286             arrow_type = GTK_ARROW_LEFT;
287             if (flags & MOZ_GTK_STEPPER_BOTTOM)
288                 allocation.x -= 3 * rect->width;
289         }
290     }
291
292     gtk_widget_set_allocation(scrollbar, &allocation);
293     style = gtk_widget_get_style(scrollbar);
294
295     TSOffsetStyleGCs(style, rect->x, rect->y);
296
297     gtk_paint_box(style, drawable, state_type, shadow_type, cliprect,
298                   scrollbar, detail, rect->x, rect->y,
299                   rect->width, rect->height);
300
301     arrow_rect.width = rect->width / 2;
302     arrow_rect.height = rect->height / 2;
303     arrow_rect.x = rect->x + (rect->width - arrow_rect.width) / 2;
304     arrow_rect.y = rect->y + (rect->height - arrow_rect.height) / 2;
305
306     if (state_type == GTK_STATE_ACTIVE) {
307         gtk_widget_style_get(scrollbar,
308                              "arrow-displacement-x", &arrow_displacement_x,
309                              "arrow-displacement-y", &arrow_displacement_y,
310                              NULL);
311
312         arrow_rect.x += arrow_displacement_x;
313         arrow_rect.y += arrow_displacement_y;
314     }
315
316     gtk_paint_arrow(style, drawable, state_type, shadow_type, cliprect,
317                     scrollbar, detail, arrow_type, TRUE, arrow_rect.x,
318                     arrow_rect.y, arrow_rect.width, arrow_rect.height);
319
320     return MOZ_GTK_SUCCESS;
321 }
322
323 static gint
324 moz_gtk_scrollbar_trough_paint(GtkThemeWidgetType widget,
325                                GdkDrawable* drawable, GdkRectangle* rect,
326                                GdkRectangle* cliprect, GtkWidgetState* state,
327                                GtkTextDirection direction)
328 {
329     GtkStyle* style;
330     GtkScrollbar *scrollbar;
331
332     ensure_scrollbar_widget();
333
334     if (widget ==  MOZ_GTK_SCROLLBAR_TRACK_HORIZONTAL)
335         scrollbar = GTK_SCROLLBAR(gParts->horizScrollbarWidget);
336     else
337         scrollbar = GTK_SCROLLBAR(gParts->vertScrollbarWidget);
338
339     gtk_widget_set_direction(GTK_WIDGET(scrollbar), direction);
340
341     style = gtk_widget_get_style(GTK_WIDGET(scrollbar));
342
343     TSOffsetStyleGCs(style, rect->x, rect->y);
344     gtk_paint_box(style, drawable, GTK_STATE_ACTIVE, GTK_SHADOW_IN, cliprect,
345                   GTK_WIDGET(scrollbar), "trough", rect->x, rect->y,
346                   rect->width, rect->height);
347
348     if (state->focused) {
349         gtk_paint_focus(style, drawable, GTK_STATE_ACTIVE, cliprect,
350                         GTK_WIDGET(scrollbar), "trough",
351                         rect->x, rect->y, rect->width, rect->height);
352     }
353
354     return MOZ_GTK_SUCCESS;
355 }
356
357 static gint
358 moz_gtk_scrollbar_thumb_paint(GtkThemeWidgetType widget,
359                               GdkDrawable* drawable, GdkRectangle* rect,
360                               GdkRectangle* cliprect, GtkWidgetState* state,
361                               GtkTextDirection direction)
362 {
363     GtkStateType state_type = (state->inHover || state->active) ?
364         GTK_STATE_PRELIGHT : GTK_STATE_NORMAL;
365     GtkShadowType shadow_type = GTK_SHADOW_OUT;
366     GtkStyle* style;
367     GtkScrollbar *scrollbar;
368     GtkAdjustment *adj;
369     gboolean activate_slider;
370
371     ensure_scrollbar_widget();
372
373     if (widget == MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL)
374         scrollbar = GTK_SCROLLBAR(gParts->horizScrollbarWidget);
375     else
376         scrollbar = GTK_SCROLLBAR(gParts->vertScrollbarWidget);
377
378     gtk_widget_set_direction(GTK_WIDGET(scrollbar), direction);
379
380     /* Make sure to set the scrollbar range before painting so that
381        everything is drawn properly.  At least the bluecurve (and
382        maybe other) themes don't draw the top or bottom black line
383        surrounding the scrollbar if the theme thinks that it's butted
384        up against the scrollbar arrows.  Note the increases of the
385        clip rect below. */
386     /* Changing the cliprect is pretty bogus. This lets themes draw
387        outside the frame, which means we don't invalidate them
388        correctly. See bug 297508. But some themes do seem to need
389        it. So we modify the frame's overflow area to account for what
390        we're doing here; see nsNativeThemeGTK::GetWidgetOverflow. */
391     adj = gtk_range_get_adjustment(GTK_RANGE(scrollbar));
392
393     if (widget == MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL) {
394         cliprect->x -= 1;
395         cliprect->width += 2;
396         gtk_adjustment_set_page_size(adj, rect->width);
397     }
398     else {
399         cliprect->y -= 1;
400         cliprect->height += 2;
401         gtk_adjustment_set_page_size(adj, rect->height);
402     }
403
404 #if GTK_CHECK_VERSION(2, 14, 0)
405     gtk_adjustment_configure(adj,
406                              state->curpos,
407                              0,
408                              state->maxpos,
409                              gtk_adjustment_get_step_increment(adj),
410                              gtk_adjustment_get_page_increment(adj),
411                              gtk_adjustment_get_page_size(adj));
412 #else
413     adj->lower = 0;
414     adj->value = state->curpos;
415     adj->upper = state->maxpos;
416     gtk_adjustment_changed(adj);
417 #endif
418
419     style = gtk_widget_get_style(GTK_WIDGET(scrollbar));
420     
421     gtk_widget_style_get(GTK_WIDGET(scrollbar), "activate-slider",
422                          &activate_slider, NULL);
423     
424     if (activate_slider && state->active) {
425         shadow_type = GTK_SHADOW_IN;
426         state_type = GTK_STATE_ACTIVE;
427     }
428
429     TSOffsetStyleGCs(style, rect->x, rect->y);
430
431     gtk_paint_slider(style, drawable, state_type, shadow_type, cliprect,
432                      GTK_WIDGET(scrollbar), "slider", rect->x, rect->y,
433                      rect->width,  rect->height,
434                      (widget == MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL) ?
435                      GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL);
436
437     return MOZ_GTK_SUCCESS;
438 }
439
440 gint
441 moz_gtk_get_widget_border(GtkThemeWidgetType widget, gint* left, gint* top,
442                           gint* right, gint* bottom, GtkTextDirection direction,
443                           gboolean inhtml)
444 {
445     GtkWidget* w;
446     GtkStyle *style;
447
448     switch (widget) {
449     /* These widgets have no borders, since they are not containers. */
450     case MOZ_GTK_SCROLLBAR_BUTTON:
451     case MOZ_GTK_SCROLLBAR_TRACK_HORIZONTAL:
452     case MOZ_GTK_SCROLLBAR_TRACK_VERTICAL:
453     case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
454     case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
455         *left = *top = *right = *bottom = 0;
456         return MOZ_GTK_SUCCESS;
457     default:
458         g_warning("Unsupported widget type: %d", widget);
459         return MOZ_GTK_UNKNOWN_WIDGET;
460     }
461
462     style = gtk_widget_get_style(w);
463     *right = *left = XTHICKNESS(style);
464     *bottom = *top = YTHICKNESS(style);
465
466     return MOZ_GTK_SUCCESS;
467 }
468
469 gint
470 moz_gtk_get_scrollbar_metrics(MozGtkScrollbarMetrics *metrics)
471 {
472     ensure_scrollbar_widget();
473
474     gtk_widget_style_get (gParts->horizScrollbarWidget,
475                           "slider_width", &metrics->slider_width,
476                           "trough_border", &metrics->trough_border,
477                           "stepper_size", &metrics->stepper_size,
478                           "stepper_spacing", &metrics->stepper_spacing,
479                           "trough_under_steppers", &metrics->trough_under_steppers,
480                           "has_secondary_forward_stepper", &metrics->has_secondary_forward_stepper,
481                           "has_secondary_backward_stepper", &metrics->has_secondary_backward_stepper,
482                           NULL);
483
484     metrics->min_slider_size = gtk_range_get_min_slider_size(GTK_RANGE(gParts->horizScrollbarWidget));
485
486     return MOZ_GTK_SUCCESS;
487 }
488
489 gint
490 moz_gtk_widget_paint(GtkThemeWidgetType widget, GdkDrawable* drawable,
491                      GdkRectangle* rect, GdkRectangle* cliprect,
492                      GtkWidgetState* state, gint flags,
493                      GtkTextDirection direction)
494 {
495     switch (widget) {
496     case MOZ_GTK_SCROLLBAR_BUTTON:
497         return moz_gtk_scrollbar_button_paint(drawable, rect, cliprect, state,
498                                               (GtkScrollbarButtonFlags) flags,
499                                               direction);
500         break;
501     case MOZ_GTK_SCROLLBAR_TRACK_HORIZONTAL:
502     case MOZ_GTK_SCROLLBAR_TRACK_VERTICAL:
503         return moz_gtk_scrollbar_trough_paint(widget, drawable, rect,
504                                               cliprect, state, direction);
505         break;
506     case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
507     case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
508         return moz_gtk_scrollbar_thumb_paint(widget, drawable, rect,
509                                              cliprect, state, direction);
510         break;
511     case MOZ_GTK_SCROLLED_WINDOW:
512         return moz_gtk_scrolled_window_paint(drawable, rect, cliprect, state);
513         break;
514     default:
515         g_warning("Unknown widget type: %d", widget);
516     }
517
518     return MOZ_GTK_UNKNOWN_WIDGET;
519 }
520
521 GtkWidget* moz_gtk_get_scrollbar_widget(void)
522 {
523     if (!is_initialized)
524         return NULL;
525     ensure_scrollbar_widget();
526     return gParts->horizScrollbarWidget;
527 }
528
529 gint
530 moz_gtk_shutdown()
531 {
532     GtkWidgetClass *entry_class;
533     entry_class = g_type_class_peek(GTK_TYPE_ENTRY);
534     g_type_class_unref(entry_class);
535
536     is_initialized = FALSE;
537
538     return MOZ_GTK_SUCCESS;
539 }
540
541 void moz_gtk_destroy_theme_parts_widgets(GtkThemeParts* parts)
542 {
543     if (!parts)
544         return;
545
546     if (parts->protoWindow) {
547         gtk_widget_destroy(parts->protoWindow);
548         parts->protoWindow = NULL;
549     }
550 }
551
552 #endif // GTK_API_VERSION_2