/*
* Copyright (C) 2001 Havoc Pennington
* Copyright (C) 2016 Alberts Muktupāvels
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "config.h"
#include
#include
#include "meta-draw-op-private.h"
#define GDK_COLOR_RGB(color) \
((guint32) (((int)((color).red * 255) << 16) | \
((int)((color).green * 255) << 8) | \
((int)((color).blue * 255))))
#define CLAMP_UCHAR(v) ((guchar) (CLAMP (((int)v), (int)0, (int)255)))
#define INTENSITY(r, g, b) ((r) * 0.30 + (g) * 0.59 + (b) * 0.11)
/**
* A list of MetaDrawOp objects. Maintains a reference count.
* Grows as necessary and allows the allocation of unused spaces
* to keep reallocations to a minimum.
*
* \bug Do we really win anything from not using the equivalent
* GLib structures?
*/
struct _MetaDrawOpList
{
int refcount;
MetaDrawOp **ops;
int n_ops;
int n_allocated;
};
static void
fill_env (MetaPositionExprEnv *env,
const MetaDrawInfo *info,
MetaRectangleDouble rect)
{
/* FIXME this stuff could be raised into draw_op_list_draw() probably
*/
env->rect = rect;
env->object_width = -1;
env->object_height = -1;
env->left_width = info->left_width;
env->right_width = info->right_width;
env->top_height = info->top_height;
env->bottom_height = info->bottom_height;
env->frame_x_center = info->width / 2 - rect.x;
env->frame_y_center = info->height / 2 - rect.y;
env->mini_icon_width = info->mini_icon ? gdk_pixbuf_get_width (info->mini_icon) : 0;
env->mini_icon_height = info->mini_icon ? gdk_pixbuf_get_height (info->mini_icon) : 0;
env->icon_width = info->icon ? gdk_pixbuf_get_width (info->icon) : 0;
env->icon_height = info->icon ? gdk_pixbuf_get_height (info->icon) : 0;
env->title_width = info->title_layout_width;
env->title_height = info->title_layout_height;
env->scale = info->scale;
}
static cairo_surface_t *
scale_surface (cairo_surface_t *surface,
gdouble old_width,
gdouble old_height,
gdouble new_width,
gdouble new_height,
gboolean vertical_stripes,
gboolean horizontal_stripes)
{
gdouble scale_x;
gdouble scale_y;
cairo_content_t content;
gint width;
gint height;
cairo_surface_t *scaled;
cairo_t *cr;
scale_x = new_width / old_width;
scale_y = new_height / old_height;
if (horizontal_stripes && !vertical_stripes)
{
new_width = old_width;
scale_x = 1.0;
}
else if (vertical_stripes && !horizontal_stripes)
{
new_height = old_height;
scale_y = 1.0;
}
content = CAIRO_CONTENT_COLOR_ALPHA;
width = ceil (new_width);
height = ceil (new_height);
scaled = cairo_surface_create_similar (surface, content, width, height);
cr = cairo_create (scaled);
cairo_scale (cr, scale_x, scale_y);
cairo_set_source_surface (cr, surface, 0, 0);
cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_PAD);
cairo_paint (cr);
cairo_destroy (cr);
return scaled;
}
static cairo_surface_t *
get_surface_from_pixbuf (GdkPixbuf *pixbuf,
MetaImageFillType fill_type,
gdouble width,
gdouble height,
gboolean vertical_stripes,
gboolean horizontal_stripes)
{
gdouble pixbuf_width;
gdouble pixbuf_height;
cairo_surface_t *surface;
cairo_content_t content;
cairo_surface_t *copy;
cairo_t *cr;
pixbuf_width = gdk_pixbuf_get_width (pixbuf);
pixbuf_height = gdk_pixbuf_get_height (pixbuf);
surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, 1, NULL);
if (pixbuf_width == width && pixbuf_height == height)
{
return surface;
}
if (fill_type != META_IMAGE_FILL_TILE)
{
cairo_surface_t *scaled;
scaled = scale_surface (surface, pixbuf_width, pixbuf_height,
width, height, vertical_stripes,
horizontal_stripes);
cairo_surface_destroy (surface);
surface = scaled;
}
content = CAIRO_CONTENT_COLOR_ALPHA;
width = ceil (width);
height = ceil (height);
copy = cairo_surface_create_similar (surface, content, width, height);
cr = cairo_create (copy);
cairo_set_source_surface (cr, surface, 0, 0);
if (fill_type == META_IMAGE_FILL_TILE ||
vertical_stripes || horizontal_stripes)
{
cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
}
cairo_paint (cr);
cairo_destroy (cr);
cairo_surface_destroy (surface);
return copy;
}
static GdkPixbuf *
colorize_pixbuf (GdkPixbuf *orig,
GdkRGBA *new_color)
{
GdkPixbuf *pixbuf;
double intensity;
int x, y;
const guchar *src;
guchar *dest;
int orig_rowstride;
int dest_rowstride;
int width, height;
gboolean has_alpha;
const guchar *src_pixels;
guchar *dest_pixels;
pixbuf = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (orig), gdk_pixbuf_get_has_alpha (orig),
gdk_pixbuf_get_bits_per_sample (orig),
gdk_pixbuf_get_width (orig), gdk_pixbuf_get_height (orig));
if (pixbuf == NULL)
return NULL;
orig_rowstride = gdk_pixbuf_get_rowstride (orig);
dest_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
width = gdk_pixbuf_get_width (pixbuf);
height = gdk_pixbuf_get_height (pixbuf);
has_alpha = gdk_pixbuf_get_has_alpha (orig);
src_pixels = gdk_pixbuf_get_pixels (orig);
dest_pixels = gdk_pixbuf_get_pixels (pixbuf);
for (y = 0; y < height; y++)
{
src = src_pixels + y * orig_rowstride;
dest = dest_pixels + y * dest_rowstride;
for (x = 0; x < width; x++)
{
double dr, dg, db;
intensity = INTENSITY (src[0], src[1], src[2]) / 255.0;
if (intensity <= 0.5)
{
/* Go from black at intensity = 0.0 to new_color at intensity = 0.5 */
dr = new_color->red * intensity * 2.0;
dg = new_color->green * intensity * 2.0;
db = new_color->blue * intensity * 2.0;
}
else
{
/* Go from new_color at intensity = 0.5 to white at intensity = 1.0 */
dr = new_color->red + (1.0 - new_color->red) * (intensity - 0.5) * 2.0;
dg = new_color->green + (1.0 - new_color->green) * (intensity - 0.5) * 2.0;
db = new_color->blue + (1.0 - new_color->blue) * (intensity - 0.5) * 2.0;
}
dest[0] = CLAMP_UCHAR (255 * dr);
dest[1] = CLAMP_UCHAR (255 * dg);
dest[2] = CLAMP_UCHAR (255 * db);
if (has_alpha)
{
dest[3] = src[3];
src += 4;
dest += 4;
}
else
{
src += 3;
dest += 3;
}
}
}
return pixbuf;
}
static cairo_surface_t *
draw_op_as_surface (const MetaDrawOp *op,
GtkStyleContext *context,
const MetaDrawInfo *info,
gdouble width,
gdouble height)
{
cairo_surface_t *surface;
surface = NULL;
switch (op->type)
{
case META_DRAW_IMAGE:
{
if (op->data.image.colorize_spec)
{
GdkRGBA color;
meta_color_spec_render (op->data.image.colorize_spec,
context, &color);
if (op->data.image.colorize_cache_pixbuf == NULL ||
op->data.image.colorize_cache_pixel != GDK_COLOR_RGB (color))
{
if (op->data.image.colorize_cache_pixbuf)
g_object_unref (G_OBJECT (op->data.image.colorize_cache_pixbuf));
/* const cast here */
((MetaDrawOp*)op)->data.image.colorize_cache_pixbuf =
colorize_pixbuf (op->data.image.pixbuf,
&color);
((MetaDrawOp*)op)->data.image.colorize_cache_pixel =
GDK_COLOR_RGB (color);
}
if (op->data.image.colorize_cache_pixbuf)
{
surface = get_surface_from_pixbuf (op->data.image.colorize_cache_pixbuf,
op->data.image.fill_type,
width, height,
op->data.image.vertical_stripes,
op->data.image.horizontal_stripes);
}
}
else
{
surface = get_surface_from_pixbuf (op->data.image.pixbuf,
op->data.image.fill_type,
width, height,
op->data.image.vertical_stripes,
op->data.image.horizontal_stripes);
}
break;
}
case META_DRAW_ICON:
if (info->mini_icon &&
width <= gdk_pixbuf_get_width (info->mini_icon) &&
height <= gdk_pixbuf_get_height (info->mini_icon))
surface = get_surface_from_pixbuf (info->mini_icon, op->data.icon.fill_type,
width, height, FALSE, FALSE);
else if (info->icon)
surface = get_surface_from_pixbuf (info->icon, op->data.icon.fill_type,
width, height, FALSE, FALSE);
break;
case META_DRAW_TINT:
case META_DRAW_LINE:
case META_DRAW_RECTANGLE:
case META_DRAW_ARC:
case META_DRAW_CLIP:
case META_DRAW_GRADIENT:
case META_DRAW_GTK_ARROW:
case META_DRAW_GTK_BOX:
case META_DRAW_GTK_VLINE:
case META_DRAW_TITLE:
case META_DRAW_OP_LIST:
case META_DRAW_TILE:
break;
default:
break;
}
return surface;
}
/* This code was originally rendering anti-aliased using X primitives, and
* now has been switched to draw anti-aliased using cairo. In general, the
* closest correspondence between X rendering and cairo rendering is given
* by offsetting the geometry by 0.5 pixels in both directions before rendering
* with cairo. This is because X samples at the upper left corner of the
* pixel while cairo averages over the entire pixel. However, in the cases
* where the X rendering was an exact rectangle with no "jaggies"
* we need to be a bit careful about applying the offset. We want to produce
* the exact same pixel-aligned rectangle, rather than a rectangle with
* fuzz around the edges.
*/
static void
draw_op_draw_with_env (const MetaDrawOp *op,
GtkStyleContext *context,
cairo_t *cr,
const MetaDrawInfo *info,
MetaPositionExprEnv *env)
{
GdkRGBA color;
cairo_save (cr);
cairo_set_line_width (cr, 1.0);
switch (op->type)
{
case META_DRAW_LINE:
{
gdouble x1, x2, y1, y2;
meta_color_spec_render (op->data.line.color_spec, context, &color);
gdk_cairo_set_source_rgba (cr, &color);
if (op->data.line.width > 0)
cairo_set_line_width (cr, op->data.line.width);
if (op->data.line.dash_on_length > 0 &&
op->data.line.dash_off_length > 0)
{
double dash_list[2];
dash_list[0] = op->data.line.dash_on_length;
dash_list[1] = op->data.line.dash_off_length;
cairo_set_dash (cr, dash_list, 2, 0);
}
x1 = meta_draw_spec_parse_x_position (op->data.line.x1, env);
y1 = meta_draw_spec_parse_y_position (op->data.line.y1, env);
if (!op->data.line.x2 && !op->data.line.y2 &&
op->data.line.width == 0)
{
cairo_rectangle (cr, x1, y1, 1, 1);
cairo_fill (cr);
}
else
{
if (op->data.line.x2)
x2 = meta_draw_spec_parse_x_position (op->data.line.x2, env);
else
x2 = x1;
if (op->data.line.y2)
y2 = meta_draw_spec_parse_y_position (op->data.line.y2, env);
else
y2 = y1;
/* This is one of the cases where we are matching the exact
* pixel aligned rectangle produced by X; for zero-width lines
* the generic algorithm produces the right result so we don't
* need to handle them here.
*/
if ((y1 == y2 || x1 == x2) && op->data.line.width != 0)
{
double offset = op->data.line.width % 2 ? .5 : 0;
if (y1 == y2)
{
cairo_move_to (cr, x1, y1 + offset);
cairo_line_to (cr, x2, y2 + offset);
}
else
{
cairo_move_to (cr, x1 + offset, y1);
cairo_line_to (cr, x2 + offset, y2);
}
}
else
{
/* zero-width lines include both end-points in X, unlike wide lines */
if (op->data.line.width == 0)
cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
cairo_move_to (cr, x1 + .5, y1 + .5);
cairo_line_to (cr, x2 + .5, y2 + .5);
}
cairo_stroke (cr);
}
}
break;
case META_DRAW_RECTANGLE:
{
gdouble rx, ry, rwidth, rheight;
meta_color_spec_render (op->data.rectangle.color_spec, context, &color);
gdk_cairo_set_source_rgba (cr, &color);
rx = meta_draw_spec_parse_x_position (op->data.rectangle.x, env);
ry = meta_draw_spec_parse_y_position (op->data.rectangle.y, env);
rwidth = meta_draw_spec_parse_size (op->data.rectangle.width, env);
rheight = meta_draw_spec_parse_size (op->data.rectangle.height, env);
/* Filled and stroked rectangles are the other cases
* we pixel-align to X rasterization
*/
if (op->data.rectangle.filled)
{
cairo_rectangle (cr, rx, ry, rwidth, rheight);
cairo_fill (cr);
}
else
{
cairo_rectangle (cr, rx + .5, ry + .5, rwidth, rheight);
cairo_stroke (cr);
}
}
break;
case META_DRAW_ARC:
{
gdouble rx, ry, rwidth, rheight;
double start_angle, end_angle;
double center_x, center_y;
meta_color_spec_render (op->data.arc.color_spec, context, &color);
gdk_cairo_set_source_rgba (cr, &color);
rx = meta_draw_spec_parse_x_position (op->data.arc.x, env);
ry = meta_draw_spec_parse_y_position (op->data.arc.y, env);
rwidth = meta_draw_spec_parse_size (op->data.arc.width, env);
rheight = meta_draw_spec_parse_size (op->data.arc.height, env);
start_angle = op->data.arc.start_angle * (M_PI / 180.)
- (.5 * M_PI); /* start at 12 instead of 3 oclock */
end_angle = start_angle + op->data.arc.extent_angle * (M_PI / 180.);
center_x = rx + (double)rwidth / 2. + .5;
center_y = ry + (double)rheight / 2. + .5;
cairo_save (cr);
cairo_translate (cr, center_x, center_y);
cairo_scale (cr, (double)rwidth / 2., (double)rheight / 2.);
if (op->data.arc.extent_angle >= 0)
cairo_arc (cr, 0, 0, 1, start_angle, end_angle);
else
cairo_arc_negative (cr, 0, 0, 1, start_angle, end_angle);
cairo_restore (cr);
if (op->data.arc.filled)
{
cairo_line_to (cr, center_x, center_y);
cairo_fill (cr);
}
else
cairo_stroke (cr);
}
break;
case META_DRAW_CLIP:
break;
case META_DRAW_TINT:
{
gdouble rx, ry, rwidth, rheight;
rx = meta_draw_spec_parse_x_position (op->data.tint.x, env);
ry = meta_draw_spec_parse_y_position (op->data.tint.y, env);
rwidth = meta_draw_spec_parse_size (op->data.tint.width, env);
rheight = meta_draw_spec_parse_size (op->data.tint.height, env);
meta_color_spec_render (op->data.tint.color_spec, context, &color);
meta_alpha_gradient_spec_render (op->data.tint.alpha_spec, color, cr,
rx, ry, rwidth, rheight);
}
break;
case META_DRAW_GRADIENT:
{
gdouble rx, ry, rwidth, rheight;
rx = meta_draw_spec_parse_x_position (op->data.gradient.x, env);
ry = meta_draw_spec_parse_y_position (op->data.gradient.y, env);
rwidth = meta_draw_spec_parse_size (op->data.gradient.width, env);
rheight = meta_draw_spec_parse_size (op->data.gradient.height, env);
meta_gradient_spec_render (op->data.gradient.gradient_spec,
op->data.gradient.alpha_spec,
cr, context, rx, ry, rwidth, rheight);
}
break;
case META_DRAW_IMAGE:
{
gdouble scale;
gdouble rx, ry, rwidth, rheight;
cairo_surface_t *surface;
scale = info->scale;
cairo_scale (cr, 1.0 / scale, 1.0 / scale);
if (op->data.image.pixbuf)
{
env->object_width = gdk_pixbuf_get_width (op->data.image.pixbuf);
env->object_height = gdk_pixbuf_get_height (op->data.image.pixbuf);
}
rwidth = meta_draw_spec_parse_size (op->data.image.width, env) * scale;
rheight = meta_draw_spec_parse_size (op->data.image.height, env) * scale;
surface = draw_op_as_surface (op, context, info, rwidth, rheight);
if (surface)
{
rx = meta_draw_spec_parse_x_position (op->data.image.x, env) * scale;
ry = meta_draw_spec_parse_y_position (op->data.image.y, env) * scale;
cairo_set_source_surface (cr, surface, rx, ry);
if (op->data.image.alpha_spec)
{
cairo_pattern_t *pattern;
cairo_translate (cr, rx, ry);
cairo_scale (cr, rwidth, rheight);
pattern = meta_alpha_gradient_spec_get_mask (op->data.image.alpha_spec);
cairo_mask (cr, pattern);
cairo_pattern_destroy (pattern);
}
else
{
cairo_paint (cr);
}
cairo_surface_destroy (surface);
}
}
break;
case META_DRAW_GTK_ARROW:
{
gdouble rx, ry, rwidth, rheight;
double angle = 0, size;
rx = meta_draw_spec_parse_x_position (op->data.gtk_arrow.x, env);
ry = meta_draw_spec_parse_y_position (op->data.gtk_arrow.y, env);
rwidth = meta_draw_spec_parse_size (op->data.gtk_arrow.width, env);
rheight = meta_draw_spec_parse_size (op->data.gtk_arrow.height, env);
size = MAX(rwidth, rheight);
switch (op->data.gtk_arrow.arrow)
{
case GTK_ARROW_UP:
angle = 0;
break;
case GTK_ARROW_RIGHT:
angle = M_PI / 2;
break;
case GTK_ARROW_DOWN:
angle = M_PI;
break;
case GTK_ARROW_LEFT:
angle = 3 * M_PI / 2;
break;
case GTK_ARROW_NONE:
return;
default:
break;
}
gtk_style_context_set_state (context, op->data.gtk_arrow.state);
gtk_render_arrow (context, cr, angle, rx, ry, size);
}
break;
case META_DRAW_GTK_BOX:
{
gdouble rx, ry, rwidth, rheight;
rx = meta_draw_spec_parse_x_position (op->data.gtk_box.x, env);
ry = meta_draw_spec_parse_y_position (op->data.gtk_box.y, env);
rwidth = meta_draw_spec_parse_size (op->data.gtk_box.width, env);
rheight = meta_draw_spec_parse_size (op->data.gtk_box.height, env);
gtk_style_context_set_state (context, op->data.gtk_box.state);
gtk_render_background (context, cr, rx, ry, rwidth, rheight);
gtk_render_frame (context, cr, rx, ry, rwidth, rheight);
}
break;
case META_DRAW_GTK_VLINE:
{
gdouble rx, ry1, ry2;
rx = meta_draw_spec_parse_x_position (op->data.gtk_vline.x, env);
ry1 = meta_draw_spec_parse_y_position (op->data.gtk_vline.y1, env);
ry2 = meta_draw_spec_parse_y_position (op->data.gtk_vline.y2, env);
gtk_style_context_set_state (context, op->data.gtk_vline.state);
gtk_render_line (context, cr, rx, ry1, rx, ry2);
}
break;
case META_DRAW_ICON:
{
gdouble rx, ry, rwidth, rheight;
cairo_surface_t *surface;
rwidth = meta_draw_spec_parse_size (op->data.icon.width, env);
rheight = meta_draw_spec_parse_size (op->data.icon.height, env);
surface = draw_op_as_surface (op, context, info, rwidth, rheight);
if (surface)
{
rx = meta_draw_spec_parse_x_position (op->data.icon.x, env);
ry = meta_draw_spec_parse_y_position (op->data.icon.y, env);
cairo_set_source_surface (cr, surface, rx, ry);
if (op->data.icon.alpha_spec)
{
cairo_pattern_t *pattern;
cairo_translate (cr, rx, ry);
cairo_scale (cr, rwidth, rheight);
pattern = meta_alpha_gradient_spec_get_mask (op->data.icon.alpha_spec);
cairo_mask (cr, pattern);
cairo_pattern_destroy (pattern);
}
else
{
cairo_paint (cr);
}
cairo_surface_destroy (surface);
}
}
break;
case META_DRAW_TITLE:
if (info->title_layout)
{
gdouble rx, ry;
PangoRectangle ink_rect, logical_rect;
meta_color_spec_render (op->data.title.color_spec, context, &color);
gdk_cairo_set_source_rgba (cr, &color);
rx = meta_draw_spec_parse_x_position (op->data.title.x, env);
ry = meta_draw_spec_parse_y_position (op->data.title.y, env);
if (op->data.title.ellipsize_width)
{
gdouble ellipsize_width;
int right_bearing;
ellipsize_width = meta_draw_spec_parse_x_position (op->data.title.ellipsize_width, env);
/* HACK: meta_draw_spec_parse_x_position adds in env->rect.x, subtract out again */
ellipsize_width -= env->rect.x;
pango_layout_set_width (info->title_layout, -1);
pango_layout_get_pixel_extents (info->title_layout,
&ink_rect, &logical_rect);
/* Pango's idea of ellipsization is with respect to the logical rect.
* correct for this, by reducing the ellipsization width by the overflow
* of the un-ellipsized text on the right... it's always the visual
* right we want regardless of bidi, since since the X we pass in to
* cairo_move_to() is always the left edge of the line.
*/
right_bearing = (ink_rect.x + ink_rect.width) - (logical_rect.x + logical_rect.width);
right_bearing = MAX (right_bearing, 0);
ellipsize_width -= right_bearing;
ellipsize_width = MAX (ellipsize_width, 0);
/* Only ellipsizing when necessary is a performance optimization -
* pango_layout_set_width() will force a relayout if it isn't the
* same as the current width of -1.
*/
if (ellipsize_width < logical_rect.width)
pango_layout_set_width (info->title_layout, PANGO_SCALE * ellipsize_width);
}
else if (rx - env->rect.x + env->title_width >= env->rect.width)
{
const double alpha_margin = 30.0;
int text_space = env->rect.x + env->rect.width -
(rx - env->rect.x) - env->right_width;
double startalpha = 1.0 - (alpha_margin/((double)text_space));
cairo_pattern_t *linpat;
linpat = cairo_pattern_create_linear (rx, ry, text_space,
env->title_height);
cairo_pattern_add_color_stop_rgba (linpat, 0, color.red,
color.green,
color.blue,
color.alpha);
cairo_pattern_add_color_stop_rgba (linpat, startalpha,
color.red,
color.green,
color.blue,
color.alpha);
cairo_pattern_add_color_stop_rgba (linpat, 1, color.red,
color.green,
color.blue, 0);
cairo_set_source(cr, linpat);
cairo_pattern_destroy(linpat);
}
cairo_move_to (cr, rx, ry);
pango_cairo_show_layout (cr, info->title_layout);
/* Remove any ellipsization we might have set; will short-circuit
* if the width is already -1 */
pango_layout_set_width (info->title_layout, -1);
}
break;
case META_DRAW_OP_LIST:
{
MetaRectangleDouble d_rect;
d_rect.x = meta_draw_spec_parse_x_position (op->data.op_list.x, env);
d_rect.y = meta_draw_spec_parse_y_position (op->data.op_list.y, env);
d_rect.width = meta_draw_spec_parse_size (op->data.op_list.width, env);
d_rect.height = meta_draw_spec_parse_size (op->data.op_list.height, env);
meta_draw_op_list_draw_with_style (op->data.op_list.op_list,
context, cr, info, d_rect);
}
break;
case META_DRAW_TILE:
{
gdouble rx, ry, rwidth, rheight;
gdouble tile_xoffset, tile_yoffset;
MetaRectangleDouble tile;
rx = meta_draw_spec_parse_x_position (op->data.tile.x, env);
ry = meta_draw_spec_parse_y_position (op->data.tile.y, env);
rwidth = meta_draw_spec_parse_size (op->data.tile.width, env);
rheight = meta_draw_spec_parse_size (op->data.tile.height, env);
cairo_save (cr);
cairo_rectangle (cr, rx, ry, rwidth, rheight);
cairo_clip (cr);
tile_xoffset = meta_draw_spec_parse_x_position (op->data.tile.tile_xoffset, env);
tile_yoffset = meta_draw_spec_parse_y_position (op->data.tile.tile_yoffset, env);
/* tile offset should not include x/y */
tile_xoffset -= env->rect.x;
tile_yoffset -= env->rect.y;
tile.width = meta_draw_spec_parse_size (op->data.tile.tile_width, env);
tile.height = meta_draw_spec_parse_size (op->data.tile.tile_height, env);
tile.x = rx - tile_xoffset;
while (tile.x < (rx + rwidth))
{
tile.y = ry - tile_yoffset;
while (tile.y < (ry + rheight))
{
meta_draw_op_list_draw_with_style (op->data.tile.op_list,
context, cr, info, tile);
tile.y += tile.height;
}
tile.x += tile.width;
}
cairo_restore (cr);
}
break;
default:
break;
}
cairo_restore (cr);
}
MetaDrawOp *
meta_draw_op_new (MetaDrawType type)
{
MetaDrawOp *op;
MetaDrawOp dummy;
int size;
size = G_STRUCT_OFFSET (MetaDrawOp, data);
switch (type)
{
case META_DRAW_LINE:
size += sizeof (dummy.data.line);
break;
case META_DRAW_RECTANGLE:
size += sizeof (dummy.data.rectangle);
break;
case META_DRAW_ARC:
size += sizeof (dummy.data.arc);
break;
case META_DRAW_CLIP:
size += sizeof (dummy.data.clip);
break;
case META_DRAW_TINT:
size += sizeof (dummy.data.tint);
break;
case META_DRAW_GRADIENT:
size += sizeof (dummy.data.gradient);
break;
case META_DRAW_IMAGE:
size += sizeof (dummy.data.image);
break;
case META_DRAW_GTK_ARROW:
size += sizeof (dummy.data.gtk_arrow);
break;
case META_DRAW_GTK_BOX:
size += sizeof (dummy.data.gtk_box);
break;
case META_DRAW_GTK_VLINE:
size += sizeof (dummy.data.gtk_vline);
break;
case META_DRAW_ICON:
size += sizeof (dummy.data.icon);
break;
case META_DRAW_TITLE:
size += sizeof (dummy.data.title);
break;
case META_DRAW_OP_LIST:
size += sizeof (dummy.data.op_list);
break;
case META_DRAW_TILE:
size += sizeof (dummy.data.tile);
break;
default:
break;
}
op = g_malloc0 (size);
op->type = type;
return op;
}
void
meta_draw_op_free (MetaDrawOp *op)
{
g_return_if_fail (op != NULL);
switch (op->type)
{
case META_DRAW_LINE:
if (op->data.line.color_spec)
meta_color_spec_free (op->data.line.color_spec);
meta_draw_spec_free (op->data.line.x1);
meta_draw_spec_free (op->data.line.y1);
meta_draw_spec_free (op->data.line.x2);
meta_draw_spec_free (op->data.line.y2);
break;
case META_DRAW_RECTANGLE:
if (op->data.rectangle.color_spec)
g_free (op->data.rectangle.color_spec);
meta_draw_spec_free (op->data.rectangle.x);
meta_draw_spec_free (op->data.rectangle.y);
meta_draw_spec_free (op->data.rectangle.width);
meta_draw_spec_free (op->data.rectangle.height);
break;
case META_DRAW_ARC:
if (op->data.arc.color_spec)
g_free (op->data.arc.color_spec);
meta_draw_spec_free (op->data.arc.x);
meta_draw_spec_free (op->data.arc.y);
meta_draw_spec_free (op->data.arc.width);
meta_draw_spec_free (op->data.arc.height);
break;
case META_DRAW_CLIP:
meta_draw_spec_free (op->data.clip.x);
meta_draw_spec_free (op->data.clip.y);
meta_draw_spec_free (op->data.clip.width);
meta_draw_spec_free (op->data.clip.height);
break;
case META_DRAW_TINT:
if (op->data.tint.color_spec)
meta_color_spec_free (op->data.tint.color_spec);
if (op->data.tint.alpha_spec)
meta_alpha_gradient_spec_free (op->data.tint.alpha_spec);
meta_draw_spec_free (op->data.tint.x);
meta_draw_spec_free (op->data.tint.y);
meta_draw_spec_free (op->data.tint.width);
meta_draw_spec_free (op->data.tint.height);
break;
case META_DRAW_GRADIENT:
if (op->data.gradient.gradient_spec)
meta_gradient_spec_free (op->data.gradient.gradient_spec);
if (op->data.gradient.alpha_spec)
meta_alpha_gradient_spec_free (op->data.gradient.alpha_spec);
meta_draw_spec_free (op->data.gradient.x);
meta_draw_spec_free (op->data.gradient.y);
meta_draw_spec_free (op->data.gradient.width);
meta_draw_spec_free (op->data.gradient.height);
break;
case META_DRAW_IMAGE:
if (op->data.image.alpha_spec)
meta_alpha_gradient_spec_free (op->data.image.alpha_spec);
if (op->data.image.pixbuf)
g_object_unref (G_OBJECT (op->data.image.pixbuf));
if (op->data.image.colorize_spec)
meta_color_spec_free (op->data.image.colorize_spec);
if (op->data.image.colorize_cache_pixbuf)
g_object_unref (G_OBJECT (op->data.image.colorize_cache_pixbuf));
meta_draw_spec_free (op->data.image.x);
meta_draw_spec_free (op->data.image.y);
meta_draw_spec_free (op->data.image.width);
meta_draw_spec_free (op->data.image.height);
break;
case META_DRAW_GTK_ARROW:
meta_draw_spec_free (op->data.gtk_arrow.x);
meta_draw_spec_free (op->data.gtk_arrow.y);
meta_draw_spec_free (op->data.gtk_arrow.width);
meta_draw_spec_free (op->data.gtk_arrow.height);
break;
case META_DRAW_GTK_BOX:
meta_draw_spec_free (op->data.gtk_box.x);
meta_draw_spec_free (op->data.gtk_box.y);
meta_draw_spec_free (op->data.gtk_box.width);
meta_draw_spec_free (op->data.gtk_box.height);
break;
case META_DRAW_GTK_VLINE:
meta_draw_spec_free (op->data.gtk_vline.x);
meta_draw_spec_free (op->data.gtk_vline.y1);
meta_draw_spec_free (op->data.gtk_vline.y2);
break;
case META_DRAW_ICON:
if (op->data.icon.alpha_spec)
meta_alpha_gradient_spec_free (op->data.icon.alpha_spec);
meta_draw_spec_free (op->data.icon.x);
meta_draw_spec_free (op->data.icon.y);
meta_draw_spec_free (op->data.icon.width);
meta_draw_spec_free (op->data.icon.height);
break;
case META_DRAW_TITLE:
if (op->data.title.color_spec)
meta_color_spec_free (op->data.title.color_spec);
meta_draw_spec_free (op->data.title.x);
meta_draw_spec_free (op->data.title.y);
if (op->data.title.ellipsize_width)
meta_draw_spec_free (op->data.title.ellipsize_width);
break;
case META_DRAW_OP_LIST:
if (op->data.op_list.op_list)
meta_draw_op_list_unref (op->data.op_list.op_list);
meta_draw_spec_free (op->data.op_list.x);
meta_draw_spec_free (op->data.op_list.y);
meta_draw_spec_free (op->data.op_list.width);
meta_draw_spec_free (op->data.op_list.height);
break;
case META_DRAW_TILE:
if (op->data.tile.op_list)
meta_draw_op_list_unref (op->data.tile.op_list);
meta_draw_spec_free (op->data.tile.x);
meta_draw_spec_free (op->data.tile.y);
meta_draw_spec_free (op->data.tile.width);
meta_draw_spec_free (op->data.tile.height);
meta_draw_spec_free (op->data.tile.tile_xoffset);
meta_draw_spec_free (op->data.tile.tile_yoffset);
meta_draw_spec_free (op->data.tile.tile_width);
meta_draw_spec_free (op->data.tile.tile_height);
break;
default:
break;
}
g_free (op);
}
MetaDrawOpList *
meta_draw_op_list_new (int n_preallocs)
{
MetaDrawOpList *op_list;
g_return_val_if_fail (n_preallocs >= 0, NULL);
op_list = g_new (MetaDrawOpList, 1);
op_list->refcount = 1;
op_list->n_allocated = n_preallocs;
op_list->ops = g_new (MetaDrawOp*, op_list->n_allocated);
op_list->n_ops = 0;
return op_list;
}
void
meta_draw_op_list_ref (MetaDrawOpList *op_list)
{
g_return_if_fail (op_list != NULL);
op_list->refcount += 1;
}
void
meta_draw_op_list_unref (MetaDrawOpList *op_list)
{
g_return_if_fail (op_list != NULL);
g_return_if_fail (op_list->refcount > 0);
op_list->refcount -= 1;
if (op_list->refcount == 0)
{
int i;
for (i = 0; i < op_list->n_ops; i++)
meta_draw_op_free (op_list->ops[i]);
g_free (op_list->ops);
g_free (op_list);
}
}
void
meta_draw_op_list_draw_with_style (const MetaDrawOpList *op_list,
GtkStyleContext *context,
cairo_t *cr,
const MetaDrawInfo *info,
MetaRectangleDouble rect)
{
int i;
MetaPositionExprEnv env;
fill_env (&env, info, rect);
cairo_save (cr);
for (i = 0; i < op_list->n_ops; i++)
{
MetaDrawOp *op = op_list->ops[i];
if (op->type == META_DRAW_CLIP)
{
cairo_restore (cr);
cairo_rectangle (cr,
meta_draw_spec_parse_x_position (op->data.clip.x, &env),
meta_draw_spec_parse_y_position (op->data.clip.y, &env),
meta_draw_spec_parse_size (op->data.clip.width, &env),
meta_draw_spec_parse_size (op->data.clip.height, &env));
cairo_clip (cr);
cairo_save (cr);
}
else if (gdk_cairo_get_clip_rectangle (cr, NULL))
{
draw_op_draw_with_env (op, context, cr, info, &env);
}
}
cairo_restore (cr);
}
void
meta_draw_op_list_append (MetaDrawOpList *op_list,
MetaDrawOp *op)
{
if (op_list->n_ops == op_list->n_allocated)
{
op_list->n_allocated *= 2;
op_list->ops = g_renew (MetaDrawOp*, op_list->ops, op_list->n_allocated);
}
op_list->ops[op_list->n_ops] = op;
op_list->n_ops += 1;
}
gboolean
meta_draw_op_list_validate (MetaDrawOpList *op_list,
GError **error)
{
g_return_val_if_fail (op_list != NULL, FALSE);
/* empty lists are OK, nothing else to check really */
return TRUE;
}
/* This is not done in validate, since we wouldn't know the name
* of the list to report the error. It might be nice to
* store names inside the list sometime.
*/
gboolean
meta_draw_op_list_contains (MetaDrawOpList *op_list,
MetaDrawOpList *child)
{
int i;
/* mmm, huge tree recursion */
for (i = 0; i < op_list->n_ops; i++)
{
if (op_list->ops[i]->type == META_DRAW_OP_LIST)
{
if (op_list->ops[i]->data.op_list.op_list == child)
return TRUE;
if (meta_draw_op_list_contains (op_list->ops[i]->data.op_list.op_list,
child))
return TRUE;
}
else if (op_list->ops[i]->type == META_DRAW_TILE)
{
if (op_list->ops[i]->data.tile.op_list == child)
return TRUE;
if (meta_draw_op_list_contains (op_list->ops[i]->data.tile.op_list,
child))
return TRUE;
}
}
return FALSE;
}