From 3650a89de79dfbd48bcd45a44910e0db7cae48d5 Mon Sep 17 00:00:00 2001 From: "roc+%cs.cmu.edu" Date: Thu, 23 Feb 2006 01:01:29 +0000 Subject: [PATCH] Bug 327878. Add cairo_draw_with_xlib API, and use it to render GTK2 native themes (including for HTML content) --- gfx/thebes/public/Makefile.in | 2 +- gfx/thebes/public/gfxRect.h | 6 + gfx/thebes/public/gfxXlibNativeRenderer.h | 110 ++++ gfx/thebes/src/Makefile.in | 6 +- gfx/thebes/src/cairo-xlib-utils.c | 594 ++++++++++++++++++++++ gfx/thebes/src/cairo-xlib-utils.h | 151 ++++++ gfx/thebes/src/gfxXlibNativeRenderer.cpp | 121 +++++ widget/src/gtk2/nsNativeThemeGTK.cpp | 299 ++++++----- widget/src/gtk2/nsWidgetFactory.cpp | 5 - 9 files changed, 1152 insertions(+), 142 deletions(-) create mode 100644 gfx/thebes/public/gfxXlibNativeRenderer.h create mode 100644 gfx/thebes/src/cairo-xlib-utils.c create mode 100644 gfx/thebes/src/cairo-xlib-utils.h create mode 100644 gfx/thebes/src/gfxXlibNativeRenderer.cpp diff --git a/gfx/thebes/public/Makefile.in b/gfx/thebes/public/Makefile.in index dba8f32f9695..71d9fcf93e63 100644 --- a/gfx/thebes/public/Makefile.in +++ b/gfx/thebes/public/Makefile.in @@ -41,7 +41,7 @@ endif endif ifeq ($(MOZ_GFX_TOOLKIT),gtk2) -EXPORTS += gfxXlibSurface.h gfxPlatformGtk.h +EXPORTS += gfxXlibSurface.h gfxPlatformGtk.h gfxXlibNativeRenderer.h EXPORTS += gfxPangoFonts.h EXPORTS += gfxPDFSurface.h gfxPSSurface.h diff --git a/gfx/thebes/public/gfxRect.h b/gfx/thebes/public/gfxRect.h index 48f327ab172d..8fff75d2fa3c 100644 --- a/gfx/thebes/public/gfxRect.h +++ b/gfx/thebes/public/gfxRect.h @@ -59,6 +59,12 @@ struct NS_EXPORT gfxRect { return (pos != s.pos) || (size != s.size); } + const gfxPoint& TopLeft() { return pos; } + gfxFloat Width() { return size.width; } + gfxFloat Height() { return size.height; } + gfxFloat X() { return pos.x; } + gfxFloat Y() { return pos.y; } + // XXX figure out what methods (intersect, union, etc) we use and add them. }; diff --git a/gfx/thebes/public/gfxXlibNativeRenderer.h b/gfx/thebes/public/gfxXlibNativeRenderer.h new file mode 100644 index 000000000000..13ce721daa23 --- /dev/null +++ b/gfx/thebes/public/gfxXlibNativeRenderer.h @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Novell code. + * + * The Initial Developer of the Original Code is Novell. + * Portions created by the Initial Developer are Copyright (C) 2006 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * rocallahan@novell.com + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef GFXXLIBNATIVERENDER_H_ +#define GFXXLIBNATIVERENDER_H_ + +#include "gfxColor.h" +#include + +class gfxASurface; +class gfxContext; + +/** + * This class lets us take code that draws into an X drawable and lets us + * use it to draw into any Thebes context. The user should subclass this class, + * override NativeDraw, and then call Draw(). The drawing will be subjected + * to all Thebes transformations, clipping etc. + */ +class NS_EXPORT gfxXlibNativeRenderer { +public: + /** + * Perform the native drawing. + * @param offsetX draw at this offset into the given drawable + * @param offsetY draw at this offset into the given drawable + * @param clipRects an array of rects; clip to the union + * @param numClipRects the number of rects in the array, or zero if + * no clipping is required + */ + virtual nsresult NativeDraw(Display* dpy, Drawable drawable, Visual* visual, + short offsetX, short offsetY, + XRectangle* clipRects, PRUint32 numClipRects) = 0; + + enum { + // If set, then Draw() is opaque, i.e., every pixel in the intersection + // of the clipRect and (offset.x,offset.y,bounds.width,bounds.height) + // will be set and there is no dependence on what the existing pixels + // in the drawable are set to. + DRAW_IS_OPAQUE = 0x01, + // If set, then offset may be non-zero; if not set, then Draw() can + // only be called with offset==(0,0) + DRAW_SUPPORTS_OFFSET = 0x02, + // If set, then numClipRects can be zero or one + DRAW_SUPPORTS_CLIP_RECT = 0x04, + // If set, then numClipRects can be any value. If neither this + // nor CLIP_RECT are set, then numClipRects will be zero + DRAW_SUPPORTS_CLIP_LIST = 0x08, + // If set, then the visual passed in can be any visual, otherwise the + // visual passed in must be the default visual for dpy's default screen + DRAW_SUPPORTS_NONDEFAULT_VISUAL = 0x08, + // If set, then the display 'dpy' in the callback can be different from + // the display passed to 'Draw' + DRAW_SUPPORTS_ALTERNATE_DISPLAY = 0x10 + }; + + struct DrawOutput { + gfxASurface* mSurface; + PRPackedBool mUniformAlpha; + PRPackedBool mUniformColor; + gfxRGBA mColor; + }; + + /** + * @param flags see above + * @param bounds Draw()'s drawing is guaranteed to be restricted to + * the rectangle (offset.x,offset.y,bounds.width,bounds.height) + * @param dpy a display to use for the drawing if ctx doesn't have one + * @param resultSurface if non-null, we will try to capture a copy of the + * rendered image into a surface similar to the surface of ctx; if + * successful, a pointer to the new gfxASurface is stored in *resultSurface, + * otherwise *resultSurface is set to nsnull. + */ + nsresult Draw(Display* dpy, gfxContext* ctx, int width, int height, + PRUint32 flags, DrawOutput* output); +}; + +#endif /*GFXXLIBNATIVERENDER_H_*/ diff --git a/gfx/thebes/src/Makefile.in b/gfx/thebes/src/Makefile.in index 8a6dfb4c7fff..acb3546bf0e0 100644 --- a/gfx/thebes/src/Makefile.in +++ b/gfx/thebes/src/Makefile.in @@ -26,8 +26,7 @@ CPPSRCS = \ gfxFont.cpp \ gfxPlatform.cpp \ $(NULL) - - + SHARED_LIBRARY_LIBS += \ $(DIST)/lib/$(LIB_PREFIX)mozcairo.$(LIB_SUFFIX) \ $(DIST)/lib/$(LIB_PREFIX)mozlibpixman.$(LIB_SUFFIX) \ @@ -50,9 +49,10 @@ OS_LIBS += $(call EXPAND_LIBNAME,$(_OS_LIBS)) endif ifeq ($(MOZ_GFX_TOOLKIT),gtk2) -CPPSRCS += gfxXlibSurface.cpp gfxPlatformGtk.cpp +CPPSRCS += gfxXlibSurface.cpp gfxPlatformGtk.cpp gfxXlibNativeRenderer.cpp CPPSRCS += gfxPangoFonts.cpp CPPSRCS += gfxPDFSurface.cpp gfxPSSurface.cpp +CSRCS = cairo-xlib-utils.c EXTRA_DSO_LDOPTS += $(MOZ_PANGO_LIBS) $(ZLIB_LIBS) endif diff --git a/gfx/thebes/src/cairo-xlib-utils.c b/gfx/thebes/src/cairo-xlib-utils.c new file mode 100644 index 000000000000..7720fd11a5fc --- /dev/null +++ b/gfx/thebes/src/cairo-xlib-utils.c @@ -0,0 +1,594 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Novell code. + * + * The Initial Developer of the Original Code is Novell. + * Portions created by the Initial Developer are Copyright (C) 2006 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * rocallahan@novell.com + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "cairo-xlib-utils.h" + +#include "cairo-xlib.h" +#include +#include + +#include +#define CAIRO_XLIB_DRAWING_NOTE(m) fprintf(stderr, m) +/* #define CAIRO_XLIB_DRAWING_NOTE(m) do {} while (0) */ + +static cairo_surface_t *_get_current_target (cairo_t *cr) +{ + cairo_surface_t *target = cairo_get_group_target (cr); + if (target == NULL) { + target = cairo_get_target (cr); + } + return target; +} + +static cairo_user_data_key_t pixmap_free_key; + +typedef struct { + Display *dpy; + Pixmap pixmap; +} pixmap_free_struct; + +static void pixmap_free_func (void *data) +{ + pixmap_free_struct *pfs = (pixmap_free_struct *) data; + XFreePixmap (pfs->dpy, pfs->pixmap); + free (pfs); +} + +/* We have three basic strategies available: + 1) 'direct': cr targets an xlib surface, and other conditions are met: we can + pass the underlying drawable directly to the callback + 2) 'opaque': the image is opaque: we can create a temporary cairo xlib surface, + pass its underlying drawable to the callback, and paint the result + using cairo + 3) 'default': create a temporary cairo xlib surface, fill with black, pass its + underlying drawable to the callback, copy the results to a cairo + image surface, repeat with a white background, update the on-black + image alpha values by comparing the two images, then paint the on-black + image using cairo + Sure would be nice to have an X extension to do 3 for us on the server... +*/ + +static cairo_bool_t +_convert_coord_to_short (double coord, short *v) +{ + *v = (short)coord; + /* XXX allow some tolerance here? */ + return *v == coord; +} + +static cairo_bool_t +_convert_coord_to_unsigned_short (double coord, unsigned short *v) +{ + *v = (unsigned short)coord; + /* XXX allow some tolerance here? */ + return *v == coord; +} + +static cairo_bool_t +_intersect_interval (double a_begin, double a_end, double b_begin, double b_end, + double *out_begin, double *out_end) +{ + *out_begin = a_begin; + if (*out_begin < b_begin) { + *out_begin = b_begin; + } + *out_end = a_end; + if (*out_end > b_end) { + *out_end = b_end; + } + return *out_begin < *out_end; +} + +#define MAX_STATIC_CLIP_RECTANGLES 50 +static cairo_bool_t +_get_rectangular_clip (cairo_t *cr, + double device_offset_x, double device_offset_y, + int bounds_x, int bounds_y, + int bounds_width, int bounds_height, + cairo_bool_t *need_clip, + XRectangle *rectangles, int max_rectangles, + int *num_rectangles) +{ + cairo_clip_rect_t clips[MAX_STATIC_CLIP_RECTANGLES]; + int count; + int i; + double b_x = bounds_x; + double b_y = bounds_y; + double b_x_most = bounds_x + bounds_width; + double b_y_most = bounds_y + bounds_height; + int rect_count = 0; + + if (!cairo_has_clip (cr)) { + *need_clip = False; + return True; + } + + if (!cairo_extract_clip_rectangles (cr, MAX_STATIC_CLIP_RECTANGLES, clips, &count)) + return False; + + for (i = 0; i < count; ++i) { + double intersect_x, intersect_y, intersect_x_most, intersect_y_most; + + /* get the clip rect into surface coordinates */ + clips[i].x += device_offset_x; + clips[i].y += device_offset_y; + + if (b_x >= clips[i].x && b_x_most <= clips[i].x + clips[i].width && + b_y >= clips[i].y && b_y_most <= clips[i].y + clips[i].height) { + /* the bounds are entirely inside the clip region so we don't need to clip. */ + *need_clip = False; + return True; + } + + if (_intersect_interval (b_x, b_x_most, clips[i].x, clips[i].x + clips[i].width, + &intersect_x, &intersect_x_most) && + _intersect_interval (b_y, b_y_most, clips[i].y, clips[i].y + clips[i].height, + &intersect_y, &intersect_y_most)) { + XRectangle *rect = &rectangles[rect_count]; + + if (rect_count >= max_rectangles) + return False; + + if (!_convert_coord_to_short (intersect_x, &rect->x) || + !_convert_coord_to_short (intersect_y, &rect->y) || + !_convert_coord_to_unsigned_short (intersect_x_most - intersect_x, &rect->width) || + !_convert_coord_to_unsigned_short (intersect_y_most - intersect_y, &rect->height)) + return False; + + ++rect_count; + } + } + + *need_clip = True; + *num_rectangles = rect_count; + return True; +} + +/** + * Try the direct path. + * @param status the status returned by the callback, if we took the direct path + * @return True if we took the direct path + */ +static cairo_bool_t +_draw_with_xlib_direct (cairo_t *cr, + Display *default_display, + cairo_xlib_drawing_callback callback, + void *closure, + int bounds_width, int bounds_height, + cairo_xlib_drawing_support_t capabilities) +{ + cairo_surface_t *target = _get_current_target (cr); + Drawable d = cairo_xlib_surface_get_drawable (target); + cairo_matrix_t matrix; + short offset_x, offset_y; + cairo_bool_t needs_clip; + XRectangle rectangles[MAX_STATIC_CLIP_RECTANGLES]; + int rect_count; + double device_offset_x, device_offset_y; + int max_rectangles; + Display *dpy; + Visual *visual; + + cairo_get_matrix (cr, &matrix); + cairo_surface_get_device_offset (target, &device_offset_x, &device_offset_y); + + /* Check that the matrix is a pure translation */ + /* XXX test some approximation to == 1.0 here? */ + if (matrix.xx != 1.0 || matrix.yy != 1.0 || matrix.xy != 0.0 || matrix.yx != 0.0) { + CAIRO_XLIB_DRAWING_NOTE("TAKING SLOW PATH: matrix not a pure translation\n"); + return False; + } + /* Check that the matrix translation offsets (adjusted for + device offset) are integers */ + if (!_convert_coord_to_short (matrix.x0 + device_offset_x, &offset_x) || + !_convert_coord_to_short (matrix.y0 + device_offset_y, &offset_y)) { + CAIRO_XLIB_DRAWING_NOTE("TAKING SLOW PATH: non-integer offset\n"); + return False; + } + + max_rectangles = 0; + if (capabilities & CAIRO_XLIB_DRAWING_SUPPORTS_CLIP_RECT) { + max_rectangles = 1; + } + if (capabilities & CAIRO_XLIB_DRAWING_SUPPORTS_CLIP_LIST) { + max_rectangles = MAX_STATIC_CLIP_RECTANGLES; + } + + /* Check that the clip is rectangular and aligned on unit boundaries */ + if (!_get_rectangular_clip (cr, device_offset_x, device_offset_y, + offset_x, offset_y, bounds_width, bounds_height, + &needs_clip, + rectangles, max_rectangles, &rect_count)) { + CAIRO_XLIB_DRAWING_NOTE("TAKING SLOW PATH: unsupported clip\n"); + return False; + } + + /* Stop now if everything is clipped out */ + if (needs_clip && rect_count == 0) { + CAIRO_XLIB_DRAWING_NOTE("TAKING FAST PATH: all clipped\n"); + return True; + } + + /* Check that the operator is OVER */ + if (cairo_get_operator (cr) != CAIRO_OPERATOR_OVER) { + CAIRO_XLIB_DRAWING_NOTE("TAKING SLOW PATH: non-OVER operator\n"); + return False; + } + + /* Check that the offset is supported */ + if (!(capabilities & CAIRO_XLIB_DRAWING_SUPPORTS_OFFSET) && + (offset_x != 0 || offset_y != 0)) + return False; + + /* Check that the target surface is an xlib surface. Do this late because + we might complete early above when when the object to be drawn is + completely clipped out. */ + if (!d) { + CAIRO_XLIB_DRAWING_NOTE("TAKING SLOW PATH: non-X surface\n"); + return False; + } + + /* Check that the display is supported */ + dpy = cairo_xlib_surface_get_display (target); + if (!(capabilities & CAIRO_XLIB_DRAWING_SUPPORTS_ALTERNATE_DISPLAY) && + dpy != default_display) { + CAIRO_XLIB_DRAWING_NOTE("TAKING SLOW PATH: non-default display\n"); + return False; + } + + /* Check that the visual is supported */ + visual = cairo_xlib_surface_get_visual (target); + if (!(capabilities & CAIRO_XLIB_DRAWING_SUPPORTS_NONDEFAULT_VISUAL) && + DefaultVisual (dpy, DefaultScreen (dpy)) != visual) { + CAIRO_XLIB_DRAWING_NOTE("TAKING SLOW PATH: non-default visual\n"); + return False; + } + + /* we're good to go! */ + CAIRO_XLIB_DRAWING_NOTE("TAKING FAST PATH\n"); + cairo_surface_flush (target); + callback (closure, dpy, d, visual, offset_x, offset_y, rectangles, + needs_clip ? rect_count : 0); + cairo_surface_mark_dirty (target); + return True; +} + +static cairo_surface_t * +_create_temp_xlib_surface (cairo_t *cr, Display *dpy, int width, int height, + cairo_xlib_drawing_support_t capabilities) +{ + /* base the temp surface on the *screen* surface, not any intermediate + * group surface, because the screen surface is more likely to have + * characteristics that the xlib-using code is likely to be happy with */ + cairo_surface_t *target = cairo_get_target (cr); + Drawable target_drawable = cairo_xlib_surface_get_drawable (target); + + int screen_index = DefaultScreen (dpy); + Drawable drawable = RootWindow (dpy, screen_index); + Screen *screen = DefaultScreenOfDisplay (dpy); + Visual *visual = DefaultVisual (dpy, screen_index); + int depth = DefaultDepth (dpy, screen_index); + + Pixmap pixmap; + cairo_surface_t *result; + pixmap_free_struct *pfs; + + /* make the temporary surface match target_drawable to the extent supported + by the native rendering code */ + if (target_drawable) { + Display *target_display = cairo_xlib_surface_get_display (target); + Visual *target_visual = cairo_xlib_surface_get_visual (target); + if ((target_display == dpy || + (capabilities & CAIRO_XLIB_DRAWING_SUPPORTS_ALTERNATE_DISPLAY)) && + (target_visual == visual || + (capabilities & CAIRO_XLIB_DRAWING_SUPPORTS_NONDEFAULT_VISUAL))) { + drawable = target_drawable; + dpy = target_display; + visual = target_visual; + screen = cairo_xlib_surface_get_screen (target); + depth = cairo_xlib_surface_get_depth (target); + } + } + + pfs = malloc (sizeof(pixmap_free_struct)); + if (pfs == NULL) + return NULL; + + pixmap = XCreatePixmap (dpy, drawable, width, height, depth); + if (!pixmap) { + free (pfs); + return NULL; + } + pfs->dpy = dpy; + pfs->pixmap = pixmap; + + result = cairo_xlib_surface_create (dpy, pixmap, visual, width, height); + if (cairo_surface_status (result) != CAIRO_STATUS_SUCCESS) { + pixmap_free_func (pfs); + return NULL; + } + + cairo_surface_set_user_data (result, &pixmap_free_key, pfs, pixmap_free_func); + return result; +} + +static cairo_bool_t +_draw_onto_temp_xlib_surface (cairo_surface_t *temp_xlib_surface, + cairo_xlib_drawing_callback callback, + void *closure, + double background_gray_value) +{ + Drawable d = cairo_xlib_surface_get_drawable (temp_xlib_surface); + Display *dpy = cairo_xlib_surface_get_display (temp_xlib_surface); + Visual *visual = cairo_xlib_surface_get_visual (temp_xlib_surface); + cairo_bool_t result; + cairo_t *cr = cairo_create (temp_xlib_surface); + cairo_set_source_rgb (cr, background_gray_value, background_gray_value, + background_gray_value); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_paint (cr); + cairo_destroy (cr); + + cairo_surface_flush (temp_xlib_surface); + /* no clipping is needed because the callback can't draw outside the native + surface anyway */ + result = callback (closure, dpy, d, visual, 0, 0, NULL, 0); + cairo_surface_mark_dirty (temp_xlib_surface); + return result; +} + +static cairo_surface_t * +_copy_xlib_surface_to_image (cairo_surface_t *temp_xlib_surface, + cairo_format_t format, + int width, int height, + unsigned char **data_out) +{ + unsigned char *data; + cairo_surface_t *result; + cairo_t *cr; + + *data_out = data = (unsigned char*)malloc (width*height*4); + if (!data) + return NULL; + + result = cairo_image_surface_create_for_data (data, format, width, height, width*4); + cr = cairo_create (result); + cairo_set_source_surface (cr, temp_xlib_surface, 0, 0); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_paint (cr); + cairo_destroy (cr); + return result; +} + +#define SET_ALPHA(v, a) (((v) & ~(0xFF << 24)) | ((a) << 24)) +#define GREEN_OF(v) (((v) >> 8) & 0xFF) + +/** + * Given the RGB data for two image surfaces, one a source image composited + * with OVER onto a black background, and one a source image composited with + * OVER onto a white background, reconstruct the original image data into + * black_data. + * + * Consider a single color channel and a given pixel. Suppose the original + * premultiplied color value was C and the alpha value was A. Let the final + * on-black color be B and the final on-white color be W. All values range + * over 0-255. + * Then B=C and W=(255*(255 - A) + C*255)/255. Solving for A, we get + * A=255 - (W - C). Therefore it suffices to leave the black_data color + * data alone and set the alpha values using that simple formula. It shouldn't + * matter what color channel we pick for the alpha computation, but we'll + * pick green because if we went through a color channel downsample the green + * bits are likely to be the most accurate. + */ +static void +_compute_alpha_values (uint32_t *black_data, + uint32_t *white_data, + int width, int height, + cairo_xlib_drawing_result_t *analysis) +{ + int num_pixels = width*height; + int i; + uint32_t first; + uint32_t deltas = 0; + unsigned char first_alpha; + + if (num_pixels == 0) { + if (analysis) { + analysis->uniform_alpha = True; + analysis->uniform_color = True; + /* whatever we put here will be true */ + analysis->alpha = 1.0; + analysis->r = analysis->g = analysis->b = 0.0; + } + return; + } + + first_alpha = 255 - (GREEN_OF(*white_data) - GREEN_OF(*black_data)); + /* set the alpha value of 'first' */ + first = SET_ALPHA(*black_data, first_alpha); + + for (i = 0; i < num_pixels; ++i) { + uint32_t black = *black_data; + uint32_t white = *white_data; + unsigned char pixel_alpha = 255 - (GREEN_OF(white) - GREEN_OF(black)); + + black = SET_ALPHA(black, pixel_alpha); + *black_data = black; + deltas |= (first ^ black); + + black_data++; + white_data++; + } + + if (analysis) { + analysis->uniform_alpha = (deltas >> 24) == 0; + if (analysis->uniform_alpha) { + analysis->alpha = first_alpha/255.0; + /* we only set uniform_color when the alpha is already uniform. + it's only useful in that case ... and if the alpha was nonuniform + then computing whether the color is uniform would require unpremultiplying + every pixel */ + analysis->uniform_color = (deltas & ~(0xFF << 24)) == 0; + if (analysis->uniform_color) { + if (first_alpha == 0) { + /* can't unpremultiply, this is OK */ + analysis->r = analysis->g = analysis->b = 0.0; + } else { + double d_first_alpha = first_alpha; + analysis->r = (first & 0xFF)/d_first_alpha; + analysis->g = ((first >> 8) & 0xFF)/d_first_alpha; + analysis->b = ((first >> 16) & 0xFF)/d_first_alpha; + } + } + } + } +} + +void +cairo_draw_with_xlib (cairo_t *cr, + cairo_xlib_drawing_callback callback, + void *closure, + Display *dpy, + unsigned int width, unsigned int height, + cairo_xlib_drawing_opacity_t is_opaque, + cairo_xlib_drawing_support_t capabilities, + cairo_xlib_drawing_result_t *result) +{ + cairo_surface_t *temp_xlib_surface; + cairo_surface_t *black_image_surface; + cairo_surface_t *white_image_surface; + unsigned char *black_data; + unsigned char *white_data; + + if (result) { + result->surface = NULL; + result->uniform_alpha = False; + result->uniform_color = False; + } + + /* exit early if there's no work to do. This is actually important + because we'll die with an X error if we try to create an empty temporary + pixmap */ + if (width == 0 || height == 0) + return; + + if (_draw_with_xlib_direct (cr, dpy, callback, closure, width, height, + capabilities)) + return; + + temp_xlib_surface = _create_temp_xlib_surface (cr, dpy, width, height, + capabilities); + if (temp_xlib_surface == NULL) + return; + /* update 'dpy' to refer to the display that actually got used. Might + be different now, if cr was referring to an xlib surface on a different display */ + dpy = cairo_xlib_surface_get_display (temp_xlib_surface); + + if (!_draw_onto_temp_xlib_surface (temp_xlib_surface, callback, closure, 0.0)) { + cairo_surface_destroy (temp_xlib_surface); + return; + } + + if (is_opaque == CAIRO_XLIB_DRAWING_OPAQUE) { + cairo_set_source_surface (cr, temp_xlib_surface, 0.0, 0.0); + cairo_paint (cr); + if (result) { + result->surface = temp_xlib_surface; + /* fill in the result with what we know, which is really just what our + assumption was */ + result->uniform_alpha = True; + result->alpha = 1.0; + } else { + cairo_surface_destroy (temp_xlib_surface); + } + return; + } + + black_image_surface = + _copy_xlib_surface_to_image (temp_xlib_surface, CAIRO_FORMAT_ARGB32, + width, height, &black_data); + + _draw_onto_temp_xlib_surface (temp_xlib_surface, callback, closure, 1.0); + white_image_surface = + _copy_xlib_surface_to_image (temp_xlib_surface, CAIRO_FORMAT_RGB24, + width, height, &white_data); + + cairo_surface_destroy (temp_xlib_surface); + + if (black_image_surface && white_image_surface && + cairo_surface_status (black_image_surface) == CAIRO_STATUS_SUCCESS && + cairo_surface_status (white_image_surface) == CAIRO_STATUS_SUCCESS && + black_data != NULL && white_data != NULL) { + cairo_surface_flush (black_image_surface); + cairo_surface_flush (white_image_surface); + _compute_alpha_values ((uint32_t*)black_data, (uint32_t*)white_data, width, height, result); + cairo_surface_mark_dirty (black_image_surface); + + cairo_set_source_surface (cr, black_image_surface, 0.0, 0.0); + /* if the caller wants to retrieve the rendered image, put it into + a 'similar' surface, and use that as the source for the drawing right + now. This means we always return a surface similar to the surface + used for 'cr', which is ideal if it's going to be cached and reused. + We do not return an image if the result has uniform color and alpha. */ + if (result && (!result->uniform_alpha || !result->uniform_color)) { + cairo_surface_t *target = _get_current_target (cr); + cairo_surface_t *similar_surface = + cairo_surface_create_similar (target, CAIRO_CONTENT_COLOR_ALPHA, + width, height); + cairo_t *copy_cr = cairo_create (similar_surface); + cairo_set_source_surface (copy_cr, black_image_surface, 0.0, 0.0); + cairo_set_operator (copy_cr, CAIRO_OPERATOR_SOURCE); + cairo_paint (copy_cr); + cairo_destroy (copy_cr); + + cairo_set_source_surface (cr, similar_surface, 0.0, 0.0); + + result->surface = similar_surface; + } + + cairo_paint (cr); + } + + if (black_image_surface) { + cairo_surface_destroy (black_image_surface); + } + if (white_image_surface) { + cairo_surface_destroy (white_image_surface); + } + free (black_data); + free (white_data); +} diff --git a/gfx/thebes/src/cairo-xlib-utils.h b/gfx/thebes/src/cairo-xlib-utils.h new file mode 100644 index 000000000000..b072225e46bd --- /dev/null +++ b/gfx/thebes/src/cairo-xlib-utils.h @@ -0,0 +1,151 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Novell code. + * + * The Initial Developer of the Original Code is Novell. + * Portions created by the Initial Developer are Copyright (C) 2006 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * rocallahan@novell.com + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef CAIROXLIBUTILS_H_ +#define CAIROXLIBUTILS_H_ + +#include "cairo.h" +#include + +CAIRO_BEGIN_DECLS + +/** + * This callback encapsulates Xlib-based rendering. We assume that the + * execution of the callback is equivalent to compositing some RGBA image of + * size (bounds_width, bounds_height) onto the drawable at offset (offset_x, + * offset_y), clipped to the union of the clip_rects if num_rects is greater + * than zero. This includes the assumption that the same RGBA image + * is composited if you call the callback multiple times with the same closure, + * display and visual during a single cairo_draw_with_xlib call. + * + * @return True on success, False on non-recoverable error + */ +typedef cairo_bool_t (* cairo_xlib_drawing_callback) + (void *closure, + Display *dpy, + Drawable drawable, + Visual *visual, + short offset_x, short offset_y, + XRectangle* clip_rects, unsigned int num_rects); + +/** + * This structure captures the result of the native drawing, in case the + * caller may wish to reapply the drawing efficiently later. + */ +typedef struct { + cairo_surface_t *surface; + cairo_bool_t uniform_alpha; + cairo_bool_t uniform_color; + double alpha; /* valid only if uniform_alpha is TRUE */ + double r, g, b; /* valid only if uniform_color is TRUE */ +} cairo_xlib_drawing_result_t; + +/** + * This type specifies whether the native drawing callback draws all pixels + * in its bounds opaquely, independent of the contents of the target drawable, + * or whether it leaves pixels transparent/translucent or depends on the + * existing contents of the target drawable in some way. + */ +typedef enum _cairo_xlib_drawing_opacity { + CAIRO_XLIB_DRAWING_OPAQUE, + CAIRO_XLIB_DRAWING_TRANSPARENT +} cairo_xlib_drawing_opacity_t; + +/** + * This type encodes the capabilities of the native drawing callback. + * + * If CAIRO_XLIB_DRAWING_SUPPORTS_OFFSET is set, 'offset_x' and 'offset_y' + * can be nonzero in the call to the callback; otherwise they will be zero. + * + * If CAIRO_XLIB_DRAWING_SUPPORTS_CLIP_RECT is set, then 'num_rects' can be + * zero or one in the call to the callback. If + * CAIRO_XLIB_DRAWING_SUPPORTS_CLIP_LIST is set, then 'num_rects' can be + * anything in the call to the callback. Otherwise 'num_rects' will be zero. + * Do not set both of these values. + * + * If CAIRO_XLIB_DRAWING_SUPPORTS_ALTERNATE_DISPLAY is set, then 'dpy' can be + * any display, otherwise it will be the display passed into + * cairo_draw_with_xlib. + * + * If CAIRO_XLIB_DRAWING_SUPPORTS_NONDEFAULT_VISUAL is set, then 'visual' can be + * any visual, otherwise it will be equal to + * DefaultVisual (dpy, DefaultScreen (dpy)). + */ +typedef enum { + CAIRO_XLIB_DRAWING_SUPPORTS_OFFSET = 0x01, + CAIRO_XLIB_DRAWING_SUPPORTS_CLIP_RECT = 0x02, + CAIRO_XLIB_DRAWING_SUPPORTS_CLIP_LIST = 0x04, + CAIRO_XLIB_DRAWING_SUPPORTS_ALTERNATE_DISPLAY = 0x08, + CAIRO_XLIB_DRAWING_SUPPORTS_NONDEFAULT_VISUAL = 0x10 +} cairo_xlib_drawing_support_t; + +/** + * Draw Xlib output into any cairo context. All cairo transforms and effects + * are honored, including the current operator. This is equivalent to a + * cairo_set_source_surface and then cairo_paint. + * @param cr the context to draw into + * @param callback the code to perform Xlib rendering + * @param closure associated data + * @param dpy an X display to use in case the cairo context has no associated X display + * @param width the width of the putative image drawn by the callback + * @param height the height of the putative image drawn by the callback + * @param is_opaque set to CAIRO_XLIB_DRAWING_IS_OPAQUE to indicate + * that all alpha values of the putative image will be 1.0; the pixels drawn into + * the Drawable must not depend on the prior contents of the Drawable + * in any way + * @param capabilities the capabilities of the callback as described above. + * @param result if non-NULL, we *may* fill in the struct with information about + * the rendered image. 'surface' may be filled in with a surface representing + * the image, similar to the target of 'cr'. If 'uniform_alpha' is True then + * every pixel of the image has the same alpha value 'alpha'. If + * 'uniform_color' is True then every pixel of the image has the same RGB + * color (r, g, b). If the image has uniform color and alpha (or alpha is zero, + * in which case the color is always uniform) then we won't bother returning + * a surface for it. + */ +void cairo_draw_with_xlib (cairo_t *cr, + cairo_xlib_drawing_callback callback, + void *closure, + Display *dpy, + unsigned int width, unsigned int height, + cairo_xlib_drawing_opacity_t is_opaque, + cairo_xlib_drawing_support_t capabilities, + cairo_xlib_drawing_result_t *result); + +CAIRO_END_DECLS + +#endif /*CAIROXLIBUTILS_H_*/ diff --git a/gfx/thebes/src/gfxXlibNativeRenderer.cpp b/gfx/thebes/src/gfxXlibNativeRenderer.cpp new file mode 100644 index 000000000000..9c4cdc4d2e65 --- /dev/null +++ b/gfx/thebes/src/gfxXlibNativeRenderer.cpp @@ -0,0 +1,121 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Novell code. + * + * The Initial Developer of the Original Code is Novell. + * Portions created by the Initial Developer are Copyright (C) 2006 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * rocallahan@novell.com + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "gfxXlibNativeRenderer.h" +#include "gfxContext.h" + +#include "cairo-xlib-utils.h" + +typedef struct { + gfxXlibNativeRenderer* mRenderer; + nsresult mRV; +} NativeRenderingClosure; + +static cairo_bool_t +NativeRendering(void *closure, + Display *dpy, + Drawable drawable, + Visual *visual, + short offset_x, short offset_y, + XRectangle* rectangles, unsigned int num_rects) +{ + NativeRenderingClosure* cl = (NativeRenderingClosure*)closure; + nsresult rv = cl->mRenderer-> + NativeDraw(dpy, drawable, visual, offset_x, offset_y, + rectangles, num_rects); + cl->mRV = rv; + return NS_SUCCEEDED(rv); +} + +nsresult +gfxXlibNativeRenderer::Draw(Display* dpy, gfxContext* ctx, int width, int height, + PRUint32 flags, DrawOutput* output) +{ + NativeRenderingClosure closure = { this, NS_OK }; + cairo_xlib_drawing_result_t result; + + if (output) { + output->mSurface = NULL; + output->mUniformAlpha = PR_FALSE; + output->mUniformColor = PR_FALSE; + } + + int cairoFlags = 0; + if (flags & DRAW_SUPPORTS_OFFSET) { + cairoFlags |= CAIRO_XLIB_DRAWING_SUPPORTS_OFFSET; + } + if (flags & DRAW_SUPPORTS_CLIP_RECT) { + cairoFlags |= CAIRO_XLIB_DRAWING_SUPPORTS_CLIP_RECT; + } + if (flags & DRAW_SUPPORTS_CLIP_LIST) { + cairoFlags |= CAIRO_XLIB_DRAWING_SUPPORTS_CLIP_LIST; + } + if (flags & DRAW_SUPPORTS_ALTERNATE_DISPLAY) { + cairoFlags |= CAIRO_XLIB_DRAWING_SUPPORTS_ALTERNATE_DISPLAY; + } + if (flags & DRAW_SUPPORTS_NONDEFAULT_VISUAL) { + cairoFlags |= CAIRO_XLIB_DRAWING_SUPPORTS_NONDEFAULT_VISUAL; + } + cairo_draw_with_xlib(ctx->GetCairo(), NativeRendering, &closure, dpy, + width, height, + (flags & DRAW_IS_OPAQUE) ? CAIRO_XLIB_DRAWING_OPAQUE + : CAIRO_XLIB_DRAWING_TRANSPARENT, + (cairo_xlib_drawing_support_t)cairoFlags, + output ? &result : NULL); + if (NS_FAILED(closure.mRV)) { + if (result.surface) { + cairo_surface_destroy (result.surface); + } + return closure.mRV; + } + + if (output) { + if (result.surface) { + output->mSurface = new gfxUnknownSurface(result.surface); + if (!output->mSurface) { + cairo_surface_destroy (result.surface); + return NS_ERROR_OUT_OF_MEMORY; + } + } + + output->mUniformAlpha = result.uniform_alpha; + output->mUniformColor = result.uniform_color; + output->mColor = gfxRGBA(result.r, result.g, result.b, result.alpha); + } + + return NS_OK; +} diff --git a/widget/src/gtk2/nsNativeThemeGTK.cpp b/widget/src/gtk2/nsNativeThemeGTK.cpp index c935f917cf22..e1b398c55abf 100644 --- a/widget/src/gtk2/nsNativeThemeGTK.cpp +++ b/widget/src/gtk2/nsNativeThemeGTK.cpp @@ -56,14 +56,15 @@ #include "nsIMenuFrame.h" #include "nsIMenuParent.h" #include "prlink.h" +#include "nsIDOMHTMLInputElement.h" #include - #include #ifdef MOZ_CAIRO_GFX #include "gfxContext.h" #include "gfxPlatformGtk.h" +#include "gfxXlibNativeRenderer.h" #endif NS_IMPL_ISUPPORTS2(nsNativeThemeGTK, nsITheme, nsIObserver) @@ -204,15 +205,21 @@ nsNativeThemeGTK::GetGtkWidgetAndState(PRUint8 aWidgetType, nsIFrame* aFrame, // widgets, so don't adjust stateFrame here. aFrame = aFrame->GetParent(); } - } else if (content->Tag() == mInputAtom) { - atom = mInputCheckedAtom; - } - - if (aWidgetFlags) { - if (!atom) { - atom = (aWidgetType == NS_THEME_CHECKBOX || aWidgetType == NS_THEME_CHECKBOX_LABEL) ? mCheckedAtom : mSelectedAtom; + if (aWidgetFlags) { + if (!atom) { + atom = (aWidgetType == NS_THEME_CHECKBOX || aWidgetType == NS_THEME_CHECKBOX_LABEL) ? mCheckedAtom : mSelectedAtom; + } + *aWidgetFlags = CheckBooleanAttr(aFrame, atom); + } + } else { + if (aWidgetFlags) { + nsCOMPtr inputElt(do_QueryInterface(content)); + if (inputElt) { + PRBool isHTMLChecked; + inputElt->GetChecked(&isHTMLChecked); + *aWidgetFlags = isHTMLChecked; + } } - *aWidgetFlags = CheckBooleanAttr(aFrame, atom); } } @@ -415,6 +422,105 @@ NativeThemeErrorHandler(Display* dpy, XErrorEvent* error) { return 0; } +#ifdef MOZ_CAIRO_GFX +class ThemeRenderer : public gfxXlibNativeRenderer { +public: + ThemeRenderer(GtkWidgetState aState, GtkThemeWidgetType aGTKWidgetType, + gint aFlags, const GdkRectangle& aGDKRect, + const GdkRectangle& aGDKClip) + : mState(aState), mGTKWidgetType(aGTKWidgetType), mFlags(aFlags), + mGDKRect(aGDKRect), mGDKClip(aGDKClip) {} + nsresult NativeDraw(Display* dpy, Drawable drawable, Visual* visual, + short offsetX, short offsetY, + XRectangle* clipRects, PRUint32 numClipRects); +private: + GtkWidgetState mState; + GtkThemeWidgetType mGTKWidgetType; + gint mFlags; + GdkWindow* mWindow; + const GdkRectangle& mGDKRect; + const GdkRectangle& mGDKClip; +}; + +nsresult +ThemeRenderer::NativeDraw(Display* dpy, Drawable drawable, Visual* visual, + short offsetX, short offsetY, + XRectangle* clipRects, PRUint32 numClipRects) +{ + GdkRectangle gdk_rect = mGDKRect; + gdk_rect.x += offsetX; + gdk_rect.y += offsetY; + + GdkRectangle gdk_clip = mGDKClip; + gdk_clip.x += offsetX; + gdk_clip.y += offsetY; + if (numClipRects > 0) { + NS_ASSERTION(numClipRects == 1, "gfxXlibNativeRenderer cheated on us"); + GdkRectangle extraClip = {clipRects->x, clipRects->y, + clipRects->width, clipRects->height}; + if (!gdk_rectangle_intersect(&gdk_clip, &extraClip, &gdk_clip)) + return NS_OK; + } + + GdkDisplay* gdkDpy = gdk_x11_lookup_xdisplay(dpy); + if (!gdkDpy) + return NS_ERROR_FAILURE; + GdkPixmap* gdkPixmap = gdk_pixmap_lookup_for_display(gdkDpy, drawable); + if (gdkPixmap) { + g_object_ref(G_OBJECT(gdkPixmap)); + } else { + gdkPixmap = gdk_pixmap_foreign_new_for_display(gdkDpy, drawable); + if (!gdkPixmap) + return NS_ERROR_FAILURE; + if (visual) { + GdkScreen* gdkScreen = gdk_display_get_default_screen(gdkDpy); + GdkVisual* gdkVisual = gdk_x11_screen_lookup_visual(gdkScreen, visual->visualid); + Colormap cmap = DefaultScreenOfDisplay(dpy)->cmap; + GdkColormap* colormap = + gdk_x11_colormap_foreign_new(gdkVisual, cmap); + + gdk_drawable_set_colormap(gdkPixmap, colormap); + } + } + + moz_gtk_widget_paint(mGTKWidgetType, gdkPixmap, &gdk_rect, &gdk_clip, &mState, + mFlags); + + g_object_unref(G_OBJECT(gdkPixmap)); + return NS_OK; +} +#endif + +static PRBool +GetExtraSizeForWidget(PRUint8 aWidgetType, nsIntMargin* aExtra) +{ + *aExtra = nsIntMargin(0,0,0,0); + // Allow an extra one pixel above and below the thumb for certain + // GTK2 themes (Ximian Industrial, Bluecurve, Misty, at least); + // see moz_gtk_scrollbar_thumb_paint in gtk2drawing.c + switch (aWidgetType) { + case NS_THEME_SCROLLBAR_THUMB_VERTICAL: + aExtra->top = aExtra->bottom = 1; + return PR_TRUE; + case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL: + aExtra->left = aExtra->right = 1; + return PR_TRUE; + default: + return PR_FALSE; + } +} + +static GdkRectangle +ConvertToGdkRect(const nsRect &aRect, float aT2P) +{ + GdkRectangle gdk_rect; + gdk_rect.x = NSToCoordRound(aRect.x * aT2P); + gdk_rect.y = NSToCoordRound(aRect.y * aT2P); + gdk_rect.width = NSToCoordRound(aRect.XMost() * aT2P) - gdk_rect.x; + gdk_rect.height = NSToCoordRound(aRect.YMost() * aT2P) - gdk_rect.y; + return gdk_rect; +} + NS_IMETHODIMP nsNativeThemeGTK::DrawWidgetBackground(nsIRenderingContext* aContext, nsIFrame* aFrame, @@ -428,7 +534,7 @@ nsNativeThemeGTK::DrawWidgetBackground(nsIRenderingContext* aContext, if (!GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, &state, &flags)) return NS_OK; - + #ifndef MOZ_CAIRO_GFX GdkWindow* window = NS_STATIC_CAST(GdkWindow*, aContext->GetNativeGraphicData(nsIRenderingContext::NATIVE_GDK_DRAWABLE)); @@ -478,97 +584,26 @@ nsNativeThemeGTK::DrawWidgetBackground(nsIRenderingContext* aContext, } } #else - // Thebes gfx rendering; if we can get a GdkWindow and if we don't have any transform - // other than a translation, then just render directly. Otherwise, we can't do - // native themes yet, until we figure out how to intercept gtk rendering - - nsRefPtr ctx = (gfxContext*) aContext->GetNativeGraphicData(nsIRenderingContext::NATIVE_THEBES_CONTEXT); - nsIDeviceContext *dctx = nsnull; aContext->GetDeviceContext(dctx); double t2p = dctx->AppUnitsToDevUnits(); + double p2t = dctx->DevUnitsToAppUnits(); - GdkRectangle gdk_rect = { aRect.x * t2p, - aRect.y * t2p, - aRect.width * t2p, - aRect.height * t2p }; - GdkRectangle gdk_clip = { aClipRect.x * t2p, - aClipRect.y * t2p, - aClipRect.width * t2p, - aClipRect.height * t2p }; + // This is the rectangle that will actually be drawn, in appunits + nsRect drawingRect(aClipRect); + nsIntMargin extraSize; + // remember whether the widget might draw outside its given clip rect + PRBool overDrawing = GetExtraSizeForWidget(aWidgetType, &extraSize); + // inflate drawing rect to account for the overdraw + nsMargin extraSizeInTwips(NSToCoordRound(extraSize.left*p2t), + NSToCoordRound(extraSize.top*p2t), + NSToCoordRound(extraSize.right*p2t), + NSToCoordRound(extraSize.bottom*p2t)); + drawingRect.Inflate(extraSizeInTwips); - - GdkWindow* gdkwin = (GdkWindow*) gfxPlatformGtk::GetPlatform()->GetSurfaceGdkDrawable(ctx->CurrentSurface()); - GdkDrawable *target_drawable = nsnull; - PRBool needs_thebes_composite = PR_FALSE; - - if (gdkwin && !ctx->CurrentMatrix().HasNonTranslation()) { - // we can draw straight to the gdkwin drawable; need to offset gdk_rect/gdk_clip - // by the current translation - target_drawable = gdkwin; - - gfxPoint t = ctx->CurrentMatrix().GetTranslation(); - gdk_rect.x += t.x; - gdk_rect.y += t.y; - gdk_clip.x += t.x; - gdk_clip.y += t.y; - - ctx->CurrentSurface()->Flush(); - } else { - // we can't draw directly to the gdkwin drawable because we're being transformed; - // so draw to our temporary pixmap and composite - - // .. except that we can't draw to the temporary pixmap just yet, so bail - SetWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType); - RefreshWidgetWindow(aFrame); - return NS_OK; - -#if 0 - PRUint32 maxsz = ((PRUint32)(aRect.width < aRect.height ? (aRect.height * t2p) : (aRect.width * t2p))) + 1; - - if (mGdkPixmapSize < maxsz) { - if (mGdkPixmap) { - mThebesSurface = nsnull; - g_object_unref(mGdkPixmap); - mGdkPixmap = nsnull; - } - - fprintf (stderr, "maxsz: %d\n", maxsz); - mGdkPixmap = gdk_pixmap_new (gdk_get_default_root_window(), - maxsz, - maxsz, - 32); - - mThebesSurface = new gfxXlibSurface (gdk_x11_get_default_xdisplay(), - GDK_PIXMAP_XID(mGdkPixmap), - gfxXlibSurface::FindRenderFormat(gdk_x11_get_default_xdisplay(), - gfxASurface::ImageFormatARGB32), - maxsz, maxsz); - - mGdkPixmapSize = maxsz; - } - - nsRefPtr gdkCtx = new gfxContext(mThebesSurface); - gdkCtx->SetOperator(gfxContext::OPERATOR_CLEAR); - gdkCtx->Paint(); - - target_drawable = mGdkPixmap; - needs_thebes_composite = PR_TRUE; -#endif - } - - if (gdk_clip.x < 0) { - gdk_clip.width += gdk_clip.x; - gdk_clip.x = 0; - } - - if (gdk_clip.y < 0) { - gdk_clip.height += gdk_clip.y; - gdk_clip.y = 0; - } - - if (gdk_clip.width < 0 || gdk_clip.height < 0) - return NS_OK; + // translate everything so (0,0) is the top left of the drawingRect + nsIRenderingContext::AutoPushTranslation + translation(aContext, drawingRect.x, drawingRect.y); NS_ASSERTION(!IsWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType), "Trying to render an unsafe widget!"); @@ -580,10 +615,37 @@ nsNativeThemeGTK::DrawWidgetBackground(nsIRenderingContext* aContext, oldHandler = XSetErrorHandler(NativeThemeErrorHandler); } + // We do not support clip lists (because we can't currently tweak the actual + // Gdk GC used), and we require the use of the default display and visual + // because I'm afraid that otherwise the GTK theme may explode. + PRUint32 rendererFlags = gfxXlibNativeRenderer::DRAW_SUPPORTS_OFFSET; + if (!overDrawing) { + rendererFlags |= gfxXlibNativeRenderer::DRAW_SUPPORTS_CLIP_RECT; + } + GdkRectangle gdk_rect = ConvertToGdkRect(aRect - drawingRect.TopLeft(), t2p); + GdkRectangle gdk_clip = ConvertToGdkRect(aClipRect - drawingRect.TopLeft(), t2p); + ThemeRenderer renderer(state, gtkWidgetType, flags, gdk_rect, gdk_clip); - moz_gtk_widget_paint(gtkWidgetType, target_drawable, &gdk_rect, &gdk_clip, &state, - flags); - + gfxContext* ctx = + (gfxContext*)aContext->GetNativeGraphicData(nsIRenderingContext::NATIVE_THEBES_CONTEXT); + gfxRect rect(0, 0, drawingRect.width*t2p, drawingRect.height*t2p); + // Don't snap if it's a non-unit scale factor. We're going to have to take + // slow paths then in any case. + gfxMatrix current = ctx->CurrentMatrix(); + PRBool snapXY = ctx->UserToDevicePixelSnapped(rect) && + !current.HasNonTranslation(); + if (snapXY) { + gfxMatrix translation; + translation.Translate(rect.TopLeft()); + ctx->SetMatrix(translation); + renderer.Draw(gdk_x11_get_default_xdisplay(), ctx, + NSToCoordRound(rect.Width()), NSToCoordRound(rect.Height()), + rendererFlags, nsnull); + ctx->SetMatrix(current); + } else { + renderer.Draw(gdk_x11_get_default_xdisplay(), ctx, + drawingRect.width, drawingRect.height, rendererFlags, nsnull); + } if (!safeState) { gdk_flush(); @@ -605,26 +667,7 @@ nsNativeThemeGTK::DrawWidgetBackground(nsIRenderingContext* aContext, SetWidgetStateSafe(mSafeWidgetStates, aWidgetType, &state); } } - - if (needs_thebes_composite) { -#if 0 - ctx->Save(); - ctx->NewPath(); - ctx->Translate(gfxPoint(aRect.x * t2p, - aRect.y * t2p)); - nsRefPtr pat = new gfxPattern(mThebesSurface); - - ctx->PixelSnappedRectangleAndSetPattern(gfxRect(0, 0, - aRect.width * t2p, - aRect.height * t2p), - pat); - ctx->Fill(); - ctx->Restore(); #endif - } else { - ctx->CurrentSurface()->MarkDirty(); - } -#endif /* MOZ_CAIRO_GFX */ return NS_OK; } @@ -692,21 +735,9 @@ nsNativeThemeGTK::GetWidgetOverflow(nsIDeviceContext* aContext, nsIFrame* aFrame, PRUint8 aWidgetType, nsRect* aResult) { - nsIntMargin extraSize(0,0,0,0); - // Allow an extra one pixel above and below the thumb for certain - // GTK2 themes (Ximian Industrial, Bluecurve, Misty, at least); - // see moz_gtk_scrollbar_thumb_paint in gtk2drawing.c - switch (aWidgetType) { - case NS_THEME_SCROLLBAR_THUMB_VERTICAL: - extraSize.top = extraSize.bottom = 1; - break; - case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL: - extraSize.left = extraSize.right = 1; - break; - default: + nsIntMargin extraSize; + if (!GetExtraSizeForWidget(aWidgetType, &extraSize)) return PR_FALSE; - } - float p2t = aContext->DevUnitsToAppUnits(); nsMargin m(NSIntPixelsToTwips(extraSize.left, p2t), NSIntPixelsToTwips(extraSize.top, p2t), @@ -874,11 +905,13 @@ nsNativeThemeGTK::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame, PRUint8 aWidgetType) { +#ifndef MOZ_CAIRO_GFX if (aFrame) { - // For now don't support HTML. + // don't support HTML in non-cairo builds if (aFrame->GetContent()->IsContentOfType(nsIContent::eHTML)) return PR_FALSE; } +#endif if (IsWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType)) return PR_FALSE; diff --git a/widget/src/gtk2/nsWidgetFactory.cpp b/widget/src/gtk2/nsWidgetFactory.cpp index 94d2a591f757..275c7b06fbfa 100644 --- a/widget/src/gtk2/nsWidgetFactory.cpp +++ b/widget/src/gtk2/nsWidgetFactory.cpp @@ -228,15 +228,10 @@ static const nsModuleComponentInfo components[] = "@mozilla.org/gfx/screenmanager;1", nsScreenManagerGtkConstructor }, #ifdef NATIVE_THEME_SUPPORT - /* XXX temporary! Disable native theme support on linux until roc - * lands new native theme stuff - */ -#ifndef MOZ_CAIRO_GFX { "Native Theme Renderer", NS_THEMERENDERER_CID, "@mozilla.org/chrome/chrome-native-theme;1", nsNativeThemeGTKConstructor }, -#endif #endif { "PrintSettings Service", NS_PRINTSETTINGSSERVICE_CID,