mirror of
https://github.com/brl/mutter.git
synced 2024-11-21 23:50:41 -05:00
90359a5346
* common.h: Added "above" to the list of flags a frame can have, so that we know when to mark it as always on top. Added six grab ops, one to do and one to undo each of the three new titlebar buttons (shade, above, stick). Added six new button functions, similarly. (#96229) * frame.c (meta_frame_get_flags): If a frame has the WM_STATE_ABOVE X attribute, set META_FRAME_ABOVE in its flags. * frames.c (meta_frames_apply_shapes): Allow variable amounts of rounding. (#113162) * frames.c (show_tip_now, meta_frames_paint_to_drawable, control_rect, get_control): extend handling of existing buttons to the 3*2 new kinds of button. (#96229) * frames.c (meta_frames_button_press_event): translate clicks on the 3*2 new kinds of button to the new grab ops. (#96229) * frames.c (meta_frames_button_release_event): implement the various actions for the 3*2 new kinds of button. (#96229) * frames.c (meta_frames_update_prelit_control, meta_frames_motion_notify_event): extend existing motion notifications for buttons to the 3*2 new kinds of button. (#96229) * frames.c (meta_frames_set_window_background): handle specified background colours and alpha transparency. (#151261) * frames.h (MetaFrameControl): New control types for the 3*2 new kinds of button. (#96229) * iconcache.[ch] (meta_read_icons): use theme's fallback icons if a window has no icon; use metacity's fallback icons only if the theme does not provide any. (#11363) * iconcache.[ch] (meta_invalidate_default_icons (new function)): clear icon cache on windows using default icons, and update them. (#11363) * main.c (main): added \n to error message. * prefs.c (button_function_from_string): extend for 3 new button types. (#96229) * prefs.c (button_opposite_function (new function)): return a button function's inverse (shade -> unshade, etc) (#96229) * prefs.c (update_button_layout): allocate space for a button's inverse, if it has one. (#96229) * theme-parser.c (ParseState): add state for fallback icons (#11363) * theme-parser.c (ParseInfo): add format_version; remove menu_icon_* (#114305) * theme-parser.c (parse_positive_integer): add lookup for integer constants (#331356) * theme-parser.c (parse_rounding (new function)): parse window rounding amount (#113162) * theme-parser.c (parse_alpha): don't set error if the number can't be parsed since it'll already be set; change tolerance in comparison from 1e6 to 1e-6 * theme-parser.c (parse_color (new function)): parse colour, including possible constant lookup. * theme-parser.c (parse_toplevel_element): allow defining of various new kinds of constant; allow hide_buttons (#121639) and more detailed rounding attributes on <frame_geometry> (#113162); allow background and alpha attributes on <frame_style>; (#151261) remove support for <menu_icon> except as stub; (#114305) add support for loading stock images (#113465); add support for <fallback>. (#11363)) * theme-parser.c (parse_draw_op_element): add from and to attribute for arcs. (#121603) * theme-parser.c (parse_style_element): add check for theme version supporting a button function. (#96229) * theme-parser.c (parse_style_set_element): add ability for shaded windows to be resizable (#114304) * theme-parser.c (meta_theme_load): add theme versioning routine. * theme.c ( meta_frame_layout_get_borders): return rectangles for the new 3*2 kinds of button, except where they're inapplicable. (#96229) * theme.c (meta_frame_layout_calc_geometry): don't format buttons on windows with no buttons (#121639); strip the 3*2 new kinds of button correctly (#96229); allow variable amounts of rounding (#113162). * theme.c (meta_frame_style_new): set alpha to 255 by default. (#151261) * theme.c (meta_frame_style_unref): free colour spec if allocated. (#151261) * theme.c (meta_frame_style_validate): it's only an error not to include a button if that button is valid in the current theme. (#96229) * theme.c (button_rect): return rectangles for the new 3*2 kinds of button. (#96229) * theme.c (meta_frame_style_set_unref): free differently resizable shaded styles. (#114304) * theme.c (get_style): look up differently resizable styles for shaded windows. (#114304) * theme.c (free_menu_ops (removed function), get_menu_icon (removed function), meta_theme_draw_menu_icon (removed function), meta_menu_icon_type_from_string (removed function), meta_menu_icon_type_to_string (removed function), meta_theme_free, meta_theme_validate): removed menu icon code. (#114305) * theme.c (meta_theme_load_image): add size_of_theme_icons parameter. (#113465) * theme.c (meta_theme_define_color_constant (new function), meta_theme_lookup_color_constant (new function)): allow definition of colour constants. (#129747) * theme.c (meta_button_type_from_string, meta_button_type_to_string): add the 3*2 new kinds of button. (#96229) * theme.c (meta_theme_earliest_version_with_button (new function)): return the theme version each button was introduced in. (#96229) * theme.h ( MetaFrameLayout): add "hide_buttons" flag (#121639) and corner radiuses. (#113162) * theme.h (MetaFrameGeometry): add rectangles for the 3*2 new buttons. (#96229) * theme.h (MetaButtonType): the 3*2 new buttons. (#96229) * theme.h (MetaFrameStyle): add window_background_color and window_background_alpha so that we can specify background on a <frame_style>. (#151261) * theme.h (MetaFrameStyleSet): shaded_styles gets resize dimension. (#114304) * theme.h (MetaTheme): added format_version, color_constants hash, (#129747) fallback_icon and fallback_mini_icon, (#11363) and removed menu_icons. (#114305) * theme.h (META_THEME_ALLOWS (new macro)): return whether a theme supports a given feature. Also, several macros representing new features in v2. * ui.c (meta_ui_set_current_theme)): also invalidate default icons. (#11363) * window.[ch] (meta_window_update_icon_now)): became non-static. (#11363)
6196 lines
170 KiB
C
6196 lines
170 KiB
C
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
|
|
|
/* Metacity Theme Rendering */
|
|
|
|
/*
|
|
* Copyright (C) 2001 Havoc Pennington
|
|
*
|
|
* 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, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
|
* 02111-1307, USA.
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include "theme.h"
|
|
#include "theme-parser.h"
|
|
#include "util.h"
|
|
#include "gradient.h"
|
|
#include <gtk/gtkwidget.h>
|
|
#include <gtk/gtkimage.h>
|
|
#include <gtk/gtkicontheme.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
|
|
#define GDK_COLOR_RGBA(color) \
|
|
((guint32) (0xff | \
|
|
(((color).red / 256) << 24) | \
|
|
(((color).green / 256) << 16) | \
|
|
(((color).blue / 256) << 8)))
|
|
|
|
#define GDK_COLOR_RGB(color) \
|
|
((guint32) ((((color).red / 256) << 16) | \
|
|
(((color).green / 256) << 8) | \
|
|
(((color).blue / 256))))
|
|
|
|
#define ALPHA_TO_UCHAR(d) ((unsigned char) ((d) * 255))
|
|
|
|
#define DEBUG_FILL_STRUCT(s) memset ((s), 0xef, sizeof (*(s)))
|
|
#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)
|
|
|
|
static void gtk_style_shade (GdkColor *a,
|
|
GdkColor *b,
|
|
gdouble k);
|
|
static void rgb_to_hls (gdouble *r,
|
|
gdouble *g,
|
|
gdouble *b);
|
|
static void hls_to_rgb (gdouble *h,
|
|
gdouble *l,
|
|
gdouble *s);
|
|
|
|
static GdkPixbuf *
|
|
colorize_pixbuf (GdkPixbuf *orig,
|
|
GdkColor *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) / 65535.0;
|
|
dg = (new_color->green * intensity * 2.0) / 65535.0;
|
|
db = (new_color->blue * intensity * 2.0) / 65535.0;
|
|
}
|
|
else
|
|
{
|
|
/* Go from new_color at intensity = 0.5 to white at intensity = 1.0 */
|
|
dr = (new_color->red + (65535 - new_color->red) * (intensity - 0.5) * 2.0) / 65535.0;
|
|
dg = (new_color->green + (65535 - new_color->green) * (intensity - 0.5) * 2.0) / 65535.0;
|
|
db = (new_color->blue + (65535 - new_color->blue) * (intensity - 0.5) * 2.0) / 65535.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 void
|
|
color_composite (const GdkColor *bg,
|
|
const GdkColor *fg,
|
|
double alpha_d,
|
|
GdkColor *color)
|
|
{
|
|
guint16 alpha;
|
|
|
|
*color = *bg;
|
|
alpha = alpha_d * 0xffff;
|
|
color->red = color->red + (((fg->red - color->red) * alpha + 0x8000) >> 16);
|
|
color->green = color->green + (((fg->green - color->green) * alpha + 0x8000) >> 16);
|
|
color->blue = color->blue + (((fg->blue - color->blue) * alpha + 0x8000) >> 16);
|
|
}
|
|
|
|
static void
|
|
init_border (GtkBorder *border)
|
|
{
|
|
border->top = -1;
|
|
border->bottom = -1;
|
|
border->left = -1;
|
|
border->right = -1;
|
|
}
|
|
|
|
MetaFrameLayout*
|
|
meta_frame_layout_new (void)
|
|
{
|
|
MetaFrameLayout *layout;
|
|
|
|
layout = g_new0 (MetaFrameLayout, 1);
|
|
|
|
layout->refcount = 1;
|
|
|
|
/* Fill with -1 values to detect invalid themes */
|
|
layout->left_width = -1;
|
|
layout->right_width = -1;
|
|
layout->bottom_height = -1;
|
|
|
|
init_border (&layout->title_border);
|
|
|
|
layout->title_vertical_pad = -1;
|
|
|
|
layout->right_titlebar_edge = -1;
|
|
layout->left_titlebar_edge = -1;
|
|
|
|
layout->button_sizing = META_BUTTON_SIZING_LAST;
|
|
layout->button_aspect = 1.0;
|
|
layout->button_width = -1;
|
|
layout->button_height = -1;
|
|
|
|
layout->has_title = TRUE;
|
|
layout->title_scale = 1.0;
|
|
|
|
init_border (&layout->button_border);
|
|
|
|
return layout;
|
|
}
|
|
|
|
static gboolean
|
|
validate_border (const GtkBorder *border,
|
|
const char **bad)
|
|
{
|
|
*bad = NULL;
|
|
|
|
if (border->top < 0)
|
|
*bad = _("top");
|
|
else if (border->bottom < 0)
|
|
*bad = _("bottom");
|
|
else if (border->left < 0)
|
|
*bad = _("left");
|
|
else if (border->right < 0)
|
|
*bad = _("right");
|
|
|
|
return *bad == NULL;
|
|
}
|
|
|
|
static gboolean
|
|
validate_geometry_value (int val,
|
|
const char *name,
|
|
GError **error)
|
|
{
|
|
if (val < 0)
|
|
{
|
|
g_set_error (error, META_THEME_ERROR,
|
|
META_THEME_ERROR_FRAME_GEOMETRY,
|
|
_("frame geometry does not specify \"%s\" dimension"),
|
|
name);
|
|
return FALSE;
|
|
}
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
validate_geometry_border (const GtkBorder *border,
|
|
const char *name,
|
|
GError **error)
|
|
{
|
|
const char *bad;
|
|
|
|
if (!validate_border (border, &bad))
|
|
{
|
|
g_set_error (error, META_THEME_ERROR,
|
|
META_THEME_ERROR_FRAME_GEOMETRY,
|
|
_("frame geometry does not specify dimension \"%s\" for border \"%s\""),
|
|
bad, name);
|
|
return FALSE;
|
|
}
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
meta_frame_layout_validate (const MetaFrameLayout *layout,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (layout != NULL, FALSE);
|
|
|
|
#define CHECK_GEOMETRY_VALUE(vname) if (!validate_geometry_value (layout->vname, #vname, error)) return FALSE
|
|
|
|
#define CHECK_GEOMETRY_BORDER(bname) if (!validate_geometry_border (&layout->bname, #bname, error)) return FALSE
|
|
|
|
CHECK_GEOMETRY_VALUE (left_width);
|
|
CHECK_GEOMETRY_VALUE (right_width);
|
|
CHECK_GEOMETRY_VALUE (bottom_height);
|
|
|
|
CHECK_GEOMETRY_BORDER (title_border);
|
|
|
|
CHECK_GEOMETRY_VALUE (title_vertical_pad);
|
|
|
|
CHECK_GEOMETRY_VALUE (right_titlebar_edge);
|
|
CHECK_GEOMETRY_VALUE (left_titlebar_edge);
|
|
|
|
switch (layout->button_sizing)
|
|
{
|
|
case META_BUTTON_SIZING_ASPECT:
|
|
if (layout->button_aspect < (0.1) ||
|
|
layout->button_aspect > (15.0))
|
|
{
|
|
g_set_error (error, META_THEME_ERROR,
|
|
META_THEME_ERROR_FRAME_GEOMETRY,
|
|
_("Button aspect ratio %g is not reasonable"),
|
|
layout->button_aspect);
|
|
return FALSE;
|
|
}
|
|
break;
|
|
case META_BUTTON_SIZING_FIXED:
|
|
CHECK_GEOMETRY_VALUE (button_width);
|
|
CHECK_GEOMETRY_VALUE (button_height);
|
|
break;
|
|
case META_BUTTON_SIZING_LAST:
|
|
g_set_error (error, META_THEME_ERROR,
|
|
META_THEME_ERROR_FRAME_GEOMETRY,
|
|
_("Frame geometry does not specify size of buttons"));
|
|
return FALSE;
|
|
}
|
|
|
|
CHECK_GEOMETRY_BORDER (button_border);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
MetaFrameLayout*
|
|
meta_frame_layout_copy (const MetaFrameLayout *src)
|
|
{
|
|
MetaFrameLayout *layout;
|
|
|
|
layout = g_new0 (MetaFrameLayout, 1);
|
|
|
|
*layout = *src;
|
|
|
|
layout->refcount = 1;
|
|
|
|
return layout;
|
|
}
|
|
|
|
void
|
|
meta_frame_layout_ref (MetaFrameLayout *layout)
|
|
{
|
|
g_return_if_fail (layout != NULL);
|
|
|
|
layout->refcount += 1;
|
|
}
|
|
|
|
void
|
|
meta_frame_layout_unref (MetaFrameLayout *layout)
|
|
{
|
|
g_return_if_fail (layout != NULL);
|
|
g_return_if_fail (layout->refcount > 0);
|
|
|
|
layout->refcount -= 1;
|
|
|
|
if (layout->refcount == 0)
|
|
{
|
|
DEBUG_FILL_STRUCT (layout);
|
|
g_free (layout);
|
|
}
|
|
}
|
|
|
|
void
|
|
meta_frame_layout_get_borders (const MetaFrameLayout *layout,
|
|
int text_height,
|
|
MetaFrameFlags flags,
|
|
int *top_height,
|
|
int *bottom_height,
|
|
int *left_width,
|
|
int *right_width)
|
|
{
|
|
int buttons_height, title_height;
|
|
|
|
g_return_if_fail (top_height != NULL);
|
|
g_return_if_fail (bottom_height != NULL);
|
|
g_return_if_fail (left_width != NULL);
|
|
g_return_if_fail (right_width != NULL);
|
|
|
|
if (!layout->has_title)
|
|
text_height = 0;
|
|
|
|
buttons_height = layout->button_height +
|
|
layout->button_border.top + layout->button_border.bottom;
|
|
title_height = text_height +
|
|
layout->title_vertical_pad +
|
|
layout->title_border.top + layout->title_border.bottom;
|
|
|
|
if (top_height)
|
|
{
|
|
*top_height = MAX (buttons_height, title_height);
|
|
}
|
|
|
|
if (left_width)
|
|
*left_width = layout->left_width;
|
|
if (right_width)
|
|
*right_width = layout->right_width;
|
|
|
|
if (bottom_height)
|
|
{
|
|
if (flags & META_FRAME_SHADED)
|
|
*bottom_height = 0;
|
|
else
|
|
*bottom_height = layout->bottom_height;
|
|
}
|
|
|
|
if (flags & META_FRAME_FULLSCREEN)
|
|
{
|
|
if (top_height)
|
|
*top_height = 0;
|
|
if (bottom_height)
|
|
*bottom_height = 0;
|
|
if (left_width)
|
|
*left_width = 0;
|
|
if (right_width)
|
|
*right_width = 0;
|
|
}
|
|
}
|
|
|
|
static MetaButtonSpace*
|
|
rect_for_function (MetaFrameGeometry *fgeom,
|
|
MetaFrameFlags flags,
|
|
MetaButtonFunction function,
|
|
MetaTheme *theme)
|
|
{
|
|
|
|
/* Firstly, check version-specific things. */
|
|
|
|
if (META_THEME_ALLOWS(theme, META_THEME_SHADE_STICK_ABOVE_BUTTONS))
|
|
{
|
|
switch (function)
|
|
{
|
|
case META_BUTTON_FUNCTION_SHADE:
|
|
if ((flags & META_FRAME_ALLOWS_SHADE) && !(flags & META_FRAME_SHADED))
|
|
return &fgeom->shade_rect;
|
|
else
|
|
return NULL;
|
|
case META_BUTTON_FUNCTION_ABOVE:
|
|
if (!(flags & META_FRAME_ABOVE))
|
|
return &fgeom->above_rect;
|
|
else
|
|
return NULL;
|
|
case META_BUTTON_FUNCTION_STICK:
|
|
if (!(flags & META_FRAME_STUCK))
|
|
return &fgeom->stick_rect;
|
|
else
|
|
return NULL;
|
|
case META_BUTTON_FUNCTION_UNSHADE:
|
|
if ((flags & META_FRAME_ALLOWS_SHADE) && (flags & META_FRAME_SHADED))
|
|
return &fgeom->unshade_rect;
|
|
else
|
|
return NULL;
|
|
case META_BUTTON_FUNCTION_UNABOVE:
|
|
if (flags & META_FRAME_ABOVE)
|
|
return &fgeom->unabove_rect;
|
|
else
|
|
return NULL;
|
|
case META_BUTTON_FUNCTION_UNSTICK:
|
|
if (flags & META_FRAME_STUCK)
|
|
return &fgeom->unstick_rect;
|
|
default:
|
|
/* just go on to the next switch block */;
|
|
}
|
|
}
|
|
|
|
/* now consider the buttons which exist in all versions */
|
|
|
|
switch (function)
|
|
{
|
|
case META_BUTTON_FUNCTION_MENU:
|
|
if (flags & META_FRAME_ALLOWS_MENU)
|
|
return &fgeom->menu_rect;
|
|
else
|
|
return NULL;
|
|
case META_BUTTON_FUNCTION_MINIMIZE:
|
|
if (flags & META_FRAME_ALLOWS_MINIMIZE)
|
|
return &fgeom->min_rect;
|
|
else
|
|
return NULL;
|
|
case META_BUTTON_FUNCTION_MAXIMIZE:
|
|
if (flags & META_FRAME_ALLOWS_MAXIMIZE)
|
|
return &fgeom->max_rect;
|
|
else
|
|
return NULL;
|
|
case META_BUTTON_FUNCTION_CLOSE:
|
|
if (flags & META_FRAME_ALLOWS_DELETE)
|
|
return &fgeom->close_rect;
|
|
else
|
|
return NULL;
|
|
case META_BUTTON_FUNCTION_STICK:
|
|
case META_BUTTON_FUNCTION_SHADE:
|
|
case META_BUTTON_FUNCTION_ABOVE:
|
|
case META_BUTTON_FUNCTION_UNSTICK:
|
|
case META_BUTTON_FUNCTION_UNSHADE:
|
|
case META_BUTTON_FUNCTION_UNABOVE:
|
|
/* we are being asked for a >v1 button which hasn't been handled yet,
|
|
* so obviously we're not in a theme which supports that version.
|
|
* therefore, we don't show the button. return NULL and all will
|
|
* be well.
|
|
*/
|
|
return NULL;
|
|
|
|
case META_BUTTON_FUNCTION_LAST:
|
|
return NULL;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static gboolean
|
|
strip_button (MetaButtonSpace *func_rects[MAX_BUTTONS_PER_CORNER],
|
|
GdkRectangle *bg_rects[MAX_BUTTONS_PER_CORNER],
|
|
int *n_rects,
|
|
MetaButtonSpace *to_strip)
|
|
{
|
|
int i;
|
|
|
|
i = 0;
|
|
while (i < *n_rects)
|
|
{
|
|
if (func_rects[i] == to_strip)
|
|
{
|
|
*n_rects -= 1;
|
|
|
|
/* shift the other rects back in the array */
|
|
while (i < *n_rects)
|
|
{
|
|
func_rects[i] = func_rects[i+1];
|
|
bg_rects[i] = bg_rects[i+1];
|
|
|
|
++i;
|
|
}
|
|
|
|
func_rects[i] = NULL;
|
|
bg_rects[i] = NULL;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
++i;
|
|
}
|
|
|
|
return FALSE; /* did not strip anything */
|
|
}
|
|
|
|
void
|
|
meta_frame_layout_calc_geometry (const MetaFrameLayout *layout,
|
|
int text_height,
|
|
MetaFrameFlags flags,
|
|
int client_width,
|
|
int client_height,
|
|
const MetaButtonLayout *button_layout,
|
|
MetaFrameGeometry *fgeom,
|
|
MetaTheme *theme)
|
|
{
|
|
int i, n_left, n_right;
|
|
int x;
|
|
int button_y;
|
|
int title_right_edge;
|
|
int width, height;
|
|
int button_width, button_height;
|
|
int min_size_for_rounding;
|
|
|
|
/* the left/right rects in order; the max # of rects
|
|
* is the number of button functions
|
|
*/
|
|
MetaButtonSpace *left_func_rects[MAX_BUTTONS_PER_CORNER];
|
|
MetaButtonSpace *right_func_rects[MAX_BUTTONS_PER_CORNER];
|
|
GdkRectangle *left_bg_rects[MAX_BUTTONS_PER_CORNER];
|
|
GdkRectangle *right_bg_rects[MAX_BUTTONS_PER_CORNER];
|
|
|
|
meta_frame_layout_get_borders (layout, text_height,
|
|
flags,
|
|
&fgeom->top_height,
|
|
&fgeom->bottom_height,
|
|
&fgeom->left_width,
|
|
&fgeom->right_width);
|
|
|
|
width = client_width + fgeom->left_width + fgeom->right_width;
|
|
|
|
height = ((flags & META_FRAME_SHADED) ? 0: client_height) +
|
|
fgeom->top_height + fgeom->bottom_height;
|
|
|
|
fgeom->width = width;
|
|
fgeom->height = height;
|
|
|
|
fgeom->top_titlebar_edge = layout->title_border.top;
|
|
fgeom->bottom_titlebar_edge = layout->title_border.bottom;
|
|
fgeom->left_titlebar_edge = layout->left_titlebar_edge;
|
|
fgeom->right_titlebar_edge = layout->right_titlebar_edge;
|
|
|
|
/* gcc warnings */
|
|
button_width = -1;
|
|
button_height = -1;
|
|
|
|
switch (layout->button_sizing)
|
|
{
|
|
case META_BUTTON_SIZING_ASPECT:
|
|
button_height = fgeom->top_height - layout->button_border.top - layout->button_border.bottom;
|
|
button_width = button_height / layout->button_aspect;
|
|
break;
|
|
case META_BUTTON_SIZING_FIXED:
|
|
button_width = layout->button_width;
|
|
button_height = layout->button_height;
|
|
break;
|
|
case META_BUTTON_SIZING_LAST:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
/* FIXME all this code sort of pretends that duplicate buttons
|
|
* with the same function are allowed, but that breaks the
|
|
* code in frames.c, so isn't really allowed right now.
|
|
* Would need left_close_rect, right_close_rect, etc.
|
|
*/
|
|
|
|
/* Init all button rects to 0, lame hack */
|
|
memset (ADDRESS_OF_BUTTON_RECTS (fgeom), '\0',
|
|
LENGTH_OF_BUTTON_RECTS);
|
|
|
|
n_left = 0;
|
|
n_right = 0;
|
|
for (i = 0; i < MAX_BUTTONS_PER_CORNER; i++)
|
|
{
|
|
/* NULL all unused */
|
|
left_func_rects[i] = NULL;
|
|
right_func_rects[i] = NULL;
|
|
|
|
/* Try to fill in rects */
|
|
if (button_layout->left_buttons[i] != META_BUTTON_FUNCTION_LAST && !layout->hide_buttons)
|
|
{
|
|
left_func_rects[n_left] = rect_for_function (fgeom, flags,
|
|
button_layout->left_buttons[i],
|
|
theme);
|
|
if (left_func_rects[n_left] != NULL)
|
|
++n_left;
|
|
}
|
|
|
|
if (button_layout->right_buttons[i] != META_BUTTON_FUNCTION_LAST && !layout->hide_buttons)
|
|
{
|
|
right_func_rects[n_right] = rect_for_function (fgeom, flags,
|
|
button_layout->right_buttons[i],
|
|
theme);
|
|
if (right_func_rects[n_right] != NULL)
|
|
++n_right;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < MAX_BUTTONS_PER_CORNER; i++)
|
|
{
|
|
left_bg_rects[i] = NULL;
|
|
right_bg_rects[i] = NULL;
|
|
}
|
|
|
|
for (i = 0; i < n_left; i++)
|
|
{
|
|
if (i == 0) /* prefer left background if only one button */
|
|
left_bg_rects[i] = &fgeom->left_left_background;
|
|
else if (i == (n_left - 1))
|
|
left_bg_rects[i] = &fgeom->left_right_background;
|
|
else
|
|
left_bg_rects[i] = &fgeom->left_middle_backgrounds[i - 1];
|
|
}
|
|
|
|
for (i = 0; i < n_right; i++)
|
|
{
|
|
/* prefer right background if only one button */
|
|
if (i == (n_right - 1))
|
|
right_bg_rects[i] = &fgeom->right_right_background;
|
|
else if (i == 0)
|
|
right_bg_rects[i] = &fgeom->right_left_background;
|
|
else
|
|
right_bg_rects[i] = &fgeom->right_middle_backgrounds[i - 1];
|
|
}
|
|
|
|
/* Be sure buttons fit */
|
|
while (n_left > 0 || n_right > 0)
|
|
{
|
|
int space_used_by_buttons;
|
|
int space_available;
|
|
|
|
space_available = fgeom->width - layout->left_titlebar_edge - layout->right_titlebar_edge;
|
|
|
|
space_used_by_buttons = 0;
|
|
|
|
space_used_by_buttons += button_width * n_left;
|
|
space_used_by_buttons += layout->button_border.left * n_left;
|
|
space_used_by_buttons += layout->button_border.right * n_left;
|
|
|
|
space_used_by_buttons += button_width * n_right;
|
|
space_used_by_buttons += layout->button_border.left * n_right;
|
|
space_used_by_buttons += layout->button_border.right * n_right;
|
|
|
|
if (space_used_by_buttons <= space_available)
|
|
break; /* Everything fits, bail out */
|
|
|
|
/* Otherwise we need to shave out a button. Shave
|
|
* above, stick, shade, min, max, close, then menu (menu is most useful);
|
|
* prefer the default button locations.
|
|
*/
|
|
if (strip_button (left_func_rects, left_bg_rects,
|
|
&n_left, &fgeom->above_rect))
|
|
continue;
|
|
else if (strip_button (right_func_rects, right_bg_rects,
|
|
&n_right, &fgeom->above_rect))
|
|
continue;
|
|
else if (strip_button (left_func_rects, left_bg_rects,
|
|
&n_left, &fgeom->stick_rect))
|
|
continue;
|
|
else if (strip_button (right_func_rects, right_bg_rects,
|
|
&n_right, &fgeom->stick_rect))
|
|
continue;
|
|
else if (strip_button (left_func_rects, left_bg_rects,
|
|
&n_left, &fgeom->shade_rect))
|
|
continue;
|
|
else if (strip_button (right_func_rects, right_bg_rects,
|
|
&n_right, &fgeom->shade_rect))
|
|
continue;
|
|
else if (strip_button (left_func_rects, left_bg_rects,
|
|
&n_left, &fgeom->min_rect))
|
|
continue;
|
|
else if (strip_button (right_func_rects, right_bg_rects,
|
|
&n_right, &fgeom->min_rect))
|
|
continue;
|
|
else if (strip_button (left_func_rects, left_bg_rects,
|
|
&n_left, &fgeom->max_rect))
|
|
continue;
|
|
else if (strip_button (right_func_rects, right_bg_rects,
|
|
&n_right, &fgeom->max_rect))
|
|
continue;
|
|
else if (strip_button (left_func_rects, left_bg_rects,
|
|
&n_left, &fgeom->close_rect))
|
|
continue;
|
|
else if (strip_button (right_func_rects, right_bg_rects,
|
|
&n_right, &fgeom->close_rect))
|
|
continue;
|
|
else if (strip_button (right_func_rects, right_bg_rects,
|
|
&n_right, &fgeom->menu_rect))
|
|
continue;
|
|
else if (strip_button (left_func_rects, left_bg_rects,
|
|
&n_left, &fgeom->menu_rect))
|
|
continue;
|
|
else
|
|
{
|
|
meta_bug ("Could not find a button to strip. n_left = %d n_right = %d\n",
|
|
n_left, n_right);
|
|
}
|
|
}
|
|
|
|
/* center buttons vertically */
|
|
button_y = (fgeom->top_height -
|
|
(button_height + layout->button_border.top + layout->button_border.bottom)) / 2 + layout->button_border.top;
|
|
|
|
/* right edge of farthest-right button */
|
|
x = width - layout->right_titlebar_edge;
|
|
|
|
i = n_right - 1;
|
|
while (i >= 0)
|
|
{
|
|
MetaButtonSpace *rect;
|
|
|
|
if (x < 0) /* if we go negative, leave the buttons we don't get to as 0-width */
|
|
break;
|
|
|
|
rect = right_func_rects[i];
|
|
|
|
rect->visible.x = x - layout->button_border.right - button_width;
|
|
rect->visible.y = button_y;
|
|
rect->visible.width = button_width;
|
|
rect->visible.height = button_height;
|
|
|
|
if (flags & META_FRAME_MAXIMIZED)
|
|
{
|
|
rect->clickable.x = rect->visible.x;
|
|
rect->clickable.y = 0;
|
|
rect->clickable.width = rect->visible.width;
|
|
rect->clickable.height = button_height + button_y;
|
|
|
|
if (i == n_right - 1)
|
|
rect->clickable.width += layout->right_titlebar_edge + layout->right_width + layout->button_border.right;
|
|
|
|
}
|
|
else
|
|
g_memmove (&(rect->clickable), &(rect->visible), sizeof(rect->clickable));
|
|
|
|
*(right_bg_rects[i]) = rect->visible;
|
|
|
|
x = rect->visible.x - layout->button_border.left;
|
|
|
|
--i;
|
|
}
|
|
|
|
/* save right edge of titlebar for later use */
|
|
title_right_edge = x - layout->title_border.right;
|
|
|
|
/* Now x changes to be position from the left and we go through
|
|
* the left-side buttons
|
|
*/
|
|
x = layout->left_titlebar_edge;
|
|
for (i = 0; i < n_left; i++)
|
|
{
|
|
MetaButtonSpace *rect;
|
|
|
|
rect = left_func_rects[i];
|
|
|
|
rect->visible.x = x + layout->button_border.left;
|
|
rect->visible.y = button_y;
|
|
rect->visible.width = button_width;
|
|
rect->visible.height = button_height;
|
|
|
|
if (flags & META_FRAME_MAXIMIZED)
|
|
{
|
|
if (i==0)
|
|
{
|
|
rect->clickable.x = 0;
|
|
rect->clickable.width = button_width + x;
|
|
}
|
|
else
|
|
{
|
|
rect->clickable.x = rect->visible.x;
|
|
rect->clickable.width = button_width;
|
|
}
|
|
|
|
rect->clickable.y = 0;
|
|
rect->clickable.height = button_height + button_y;
|
|
}
|
|
else
|
|
g_memmove (&(rect->clickable), &(rect->visible), sizeof(rect->clickable));
|
|
|
|
|
|
x = rect->visible.x + rect->visible.width + layout->button_border.right;
|
|
|
|
*(left_bg_rects[i]) = rect->visible;
|
|
}
|
|
|
|
/* We always fill as much vertical space as possible with title rect,
|
|
* rather than centering it like the buttons
|
|
*/
|
|
fgeom->title_rect.x = x + layout->title_border.left;
|
|
fgeom->title_rect.y = layout->title_border.top;
|
|
fgeom->title_rect.width = title_right_edge - fgeom->title_rect.x;
|
|
fgeom->title_rect.height = fgeom->top_height - layout->title_border.top - layout->title_border.bottom;
|
|
|
|
/* Nuke title if it won't fit */
|
|
if (fgeom->title_rect.width < 0 ||
|
|
fgeom->title_rect.height < 0)
|
|
{
|
|
fgeom->title_rect.width = 0;
|
|
fgeom->title_rect.height = 0;
|
|
}
|
|
|
|
if (flags & META_FRAME_SHADED)
|
|
min_size_for_rounding = 0;
|
|
else
|
|
min_size_for_rounding = 5;
|
|
|
|
fgeom->top_left_corner_rounded_radius = 0;
|
|
fgeom->top_right_corner_rounded_radius = 0;
|
|
fgeom->bottom_left_corner_rounded_radius = 0;
|
|
fgeom->bottom_right_corner_rounded_radius = 0;
|
|
|
|
if (fgeom->top_height + fgeom->left_width >= min_size_for_rounding)
|
|
fgeom->top_left_corner_rounded_radius = layout->top_left_corner_rounded_radius;
|
|
if (fgeom->top_height + fgeom->right_width >= min_size_for_rounding)
|
|
fgeom->top_right_corner_rounded_radius = layout->top_right_corner_rounded_radius;
|
|
|
|
if (fgeom->bottom_height + fgeom->left_width >= min_size_for_rounding)
|
|
fgeom->bottom_left_corner_rounded_radius = layout->bottom_left_corner_rounded_radius;
|
|
if (fgeom->bottom_height + fgeom->right_width >= min_size_for_rounding)
|
|
fgeom->bottom_right_corner_rounded_radius = layout->bottom_right_corner_rounded_radius;
|
|
}
|
|
|
|
MetaGradientSpec*
|
|
meta_gradient_spec_new (MetaGradientType type)
|
|
{
|
|
MetaGradientSpec *spec;
|
|
|
|
spec = g_new (MetaGradientSpec, 1);
|
|
|
|
spec->type = type;
|
|
spec->color_specs = NULL;
|
|
|
|
return spec;
|
|
}
|
|
|
|
static void
|
|
free_color_spec (gpointer spec, gpointer user_data)
|
|
{
|
|
meta_color_spec_free (spec);
|
|
}
|
|
|
|
void
|
|
meta_gradient_spec_free (MetaGradientSpec *spec)
|
|
{
|
|
g_return_if_fail (spec != NULL);
|
|
|
|
g_slist_foreach (spec->color_specs, free_color_spec, NULL);
|
|
g_slist_free (spec->color_specs);
|
|
|
|
DEBUG_FILL_STRUCT (spec);
|
|
g_free (spec);
|
|
}
|
|
|
|
GdkPixbuf*
|
|
meta_gradient_spec_render (const MetaGradientSpec *spec,
|
|
GtkWidget *widget,
|
|
int width,
|
|
int height)
|
|
{
|
|
int n_colors;
|
|
GdkColor *colors;
|
|
GSList *tmp;
|
|
int i;
|
|
GdkPixbuf *pixbuf;
|
|
|
|
n_colors = g_slist_length (spec->color_specs);
|
|
|
|
if (n_colors == 0)
|
|
return NULL;
|
|
|
|
colors = g_new (GdkColor, n_colors);
|
|
|
|
i = 0;
|
|
tmp = spec->color_specs;
|
|
while (tmp != NULL)
|
|
{
|
|
meta_color_spec_render (tmp->data, widget, &colors[i]);
|
|
|
|
tmp = tmp->next;
|
|
++i;
|
|
}
|
|
|
|
pixbuf = meta_gradient_create_multi (width, height,
|
|
colors, n_colors,
|
|
spec->type);
|
|
|
|
g_free (colors);
|
|
|
|
return pixbuf;
|
|
}
|
|
|
|
gboolean
|
|
meta_gradient_spec_validate (MetaGradientSpec *spec,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (spec != NULL, FALSE);
|
|
|
|
if (g_slist_length (spec->color_specs) < 2)
|
|
{
|
|
g_set_error (error, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Gradients should have at least two colors"));
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
MetaAlphaGradientSpec*
|
|
meta_alpha_gradient_spec_new (MetaGradientType type,
|
|
int n_alphas)
|
|
{
|
|
MetaAlphaGradientSpec *spec;
|
|
|
|
g_return_val_if_fail (n_alphas > 0, NULL);
|
|
|
|
spec = g_new0 (MetaAlphaGradientSpec, 1);
|
|
|
|
spec->type = type;
|
|
spec->alphas = g_new0 (unsigned char, n_alphas);
|
|
spec->n_alphas = n_alphas;
|
|
|
|
return spec;
|
|
}
|
|
|
|
void
|
|
meta_alpha_gradient_spec_free (MetaAlphaGradientSpec *spec)
|
|
{
|
|
g_return_if_fail (spec != NULL);
|
|
|
|
g_free (spec->alphas);
|
|
g_free (spec);
|
|
}
|
|
|
|
MetaColorSpec*
|
|
meta_color_spec_new (MetaColorSpecType type)
|
|
{
|
|
MetaColorSpec *spec;
|
|
MetaColorSpec dummy;
|
|
int size;
|
|
|
|
size = G_STRUCT_OFFSET (MetaColorSpec, data);
|
|
|
|
switch (type)
|
|
{
|
|
case META_COLOR_SPEC_BASIC:
|
|
size += sizeof (dummy.data.basic);
|
|
break;
|
|
|
|
case META_COLOR_SPEC_GTK:
|
|
size += sizeof (dummy.data.gtk);
|
|
break;
|
|
|
|
case META_COLOR_SPEC_BLEND:
|
|
size += sizeof (dummy.data.blend);
|
|
break;
|
|
|
|
case META_COLOR_SPEC_SHADE:
|
|
size += sizeof (dummy.data.shade);
|
|
break;
|
|
}
|
|
|
|
spec = g_malloc0 (size);
|
|
|
|
spec->type = type;
|
|
|
|
return spec;
|
|
}
|
|
|
|
void
|
|
meta_color_spec_free (MetaColorSpec *spec)
|
|
{
|
|
g_return_if_fail (spec != NULL);
|
|
|
|
switch (spec->type)
|
|
{
|
|
case META_COLOR_SPEC_BASIC:
|
|
DEBUG_FILL_STRUCT (&spec->data.basic);
|
|
break;
|
|
|
|
case META_COLOR_SPEC_GTK:
|
|
DEBUG_FILL_STRUCT (&spec->data.gtk);
|
|
break;
|
|
|
|
case META_COLOR_SPEC_BLEND:
|
|
if (spec->data.blend.foreground)
|
|
meta_color_spec_free (spec->data.blend.foreground);
|
|
if (spec->data.blend.background)
|
|
meta_color_spec_free (spec->data.blend.background);
|
|
DEBUG_FILL_STRUCT (&spec->data.blend);
|
|
break;
|
|
|
|
case META_COLOR_SPEC_SHADE:
|
|
if (spec->data.shade.base)
|
|
meta_color_spec_free (spec->data.shade.base);
|
|
DEBUG_FILL_STRUCT (&spec->data.shade);
|
|
break;
|
|
}
|
|
|
|
g_free (spec);
|
|
}
|
|
|
|
MetaColorSpec*
|
|
meta_color_spec_new_from_string (const char *str,
|
|
GError **err)
|
|
{
|
|
MetaColorSpec *spec;
|
|
|
|
spec = NULL;
|
|
|
|
if (str[0] == 'g' && str[1] == 't' && str[2] == 'k' && str[3] == ':')
|
|
{
|
|
/* GTK color */
|
|
const char *bracket;
|
|
const char *end_bracket;
|
|
char *tmp;
|
|
GtkStateType state;
|
|
MetaGtkColorComponent component;
|
|
|
|
bracket = str;
|
|
while (*bracket && *bracket != '[')
|
|
++bracket;
|
|
|
|
if (*bracket == '\0')
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("GTK color specification must have the state in brackets, e.g. gtk:fg[NORMAL] where NORMAL is the state; could not parse \"%s\""),
|
|
str);
|
|
return NULL;
|
|
}
|
|
|
|
end_bracket = bracket;
|
|
++end_bracket;
|
|
while (*end_bracket && *end_bracket != ']')
|
|
++end_bracket;
|
|
|
|
if (*end_bracket == '\0')
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("GTK color specification must have a close bracket after the state, e.g. gtk:fg[NORMAL] where NORMAL is the state; could not parse \"%s\""),
|
|
str);
|
|
return NULL;
|
|
}
|
|
|
|
tmp = g_strndup (bracket + 1, end_bracket - bracket - 1);
|
|
state = meta_gtk_state_from_string (tmp);
|
|
if (((int) state) == -1)
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Did not understand state \"%s\" in color specification"),
|
|
tmp);
|
|
g_free (tmp);
|
|
return NULL;
|
|
}
|
|
g_free (tmp);
|
|
|
|
tmp = g_strndup (str + 4, bracket - str - 4);
|
|
component = meta_color_component_from_string (tmp);
|
|
if (component == META_GTK_COLOR_LAST)
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Did not understand color component \"%s\" in color specification"),
|
|
tmp);
|
|
g_free (tmp);
|
|
return NULL;
|
|
}
|
|
g_free (tmp);
|
|
|
|
spec = meta_color_spec_new (META_COLOR_SPEC_GTK);
|
|
spec->data.gtk.state = state;
|
|
spec->data.gtk.component = component;
|
|
g_assert (spec->data.gtk.state < N_GTK_STATES);
|
|
g_assert (spec->data.gtk.component < META_GTK_COLOR_LAST);
|
|
}
|
|
else if (str[0] == 'b' && str[1] == 'l' && str[2] == 'e' && str[3] == 'n' &&
|
|
str[4] == 'd' && str[5] == '/')
|
|
{
|
|
/* blend */
|
|
char **split;
|
|
double alpha;
|
|
char *end;
|
|
MetaColorSpec *fg;
|
|
MetaColorSpec *bg;
|
|
|
|
split = g_strsplit (str, "/", 4);
|
|
|
|
if (split[0] == NULL || split[1] == NULL ||
|
|
split[2] == NULL || split[3] == NULL)
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Blend format is \"blend/bg_color/fg_color/alpha\", \"%s\" does not fit the format"),
|
|
str);
|
|
g_strfreev (split);
|
|
return NULL;
|
|
}
|
|
|
|
alpha = g_ascii_strtod (split[3], &end);
|
|
if (end == split[3])
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Could not parse alpha value \"%s\" in blended color"),
|
|
split[3]);
|
|
g_strfreev (split);
|
|
return NULL;
|
|
}
|
|
|
|
if (alpha < (0.0 - 1e6) || alpha > (1.0 + 1e6))
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Alpha value \"%s\" in blended color is not between 0.0 and 1.0"),
|
|
split[3]);
|
|
g_strfreev (split);
|
|
return NULL;
|
|
}
|
|
|
|
fg = NULL;
|
|
bg = NULL;
|
|
|
|
bg = meta_color_spec_new_from_string (split[1], err);
|
|
if (bg == NULL)
|
|
{
|
|
g_strfreev (split);
|
|
return NULL;
|
|
}
|
|
|
|
fg = meta_color_spec_new_from_string (split[2], err);
|
|
if (fg == NULL)
|
|
{
|
|
meta_color_spec_free (bg);
|
|
g_strfreev (split);
|
|
return NULL;
|
|
}
|
|
|
|
g_strfreev (split);
|
|
|
|
spec = meta_color_spec_new (META_COLOR_SPEC_BLEND);
|
|
spec->data.blend.alpha = alpha;
|
|
spec->data.blend.background = bg;
|
|
spec->data.blend.foreground = fg;
|
|
}
|
|
else if (str[0] == 's' && str[1] == 'h' && str[2] == 'a' && str[3] == 'd' &&
|
|
str[4] == 'e' && str[5] == '/')
|
|
{
|
|
/* shade */
|
|
char **split;
|
|
double factor;
|
|
char *end;
|
|
MetaColorSpec *base;
|
|
|
|
split = g_strsplit (str, "/", 3);
|
|
|
|
if (split[0] == NULL || split[1] == NULL ||
|
|
split[2] == NULL)
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Shade format is \"shade/base_color/factor\", \"%s\" does not fit the format"),
|
|
str);
|
|
g_strfreev (split);
|
|
return NULL;
|
|
}
|
|
|
|
factor = g_ascii_strtod (split[2], &end);
|
|
if (end == split[2])
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Could not parse shade factor \"%s\" in shaded color"),
|
|
split[2]);
|
|
g_strfreev (split);
|
|
return NULL;
|
|
}
|
|
|
|
if (factor < (0.0 - 1e6))
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Shade factor \"%s\" in shaded color is negative"),
|
|
split[2]);
|
|
g_strfreev (split);
|
|
return NULL;
|
|
}
|
|
|
|
base = NULL;
|
|
|
|
base = meta_color_spec_new_from_string (split[1], err);
|
|
if (base == NULL)
|
|
{
|
|
g_strfreev (split);
|
|
return NULL;
|
|
}
|
|
|
|
g_strfreev (split);
|
|
|
|
spec = meta_color_spec_new (META_COLOR_SPEC_SHADE);
|
|
spec->data.shade.factor = factor;
|
|
spec->data.shade.base = base;
|
|
}
|
|
else
|
|
{
|
|
spec = meta_color_spec_new (META_COLOR_SPEC_BASIC);
|
|
|
|
if (!gdk_color_parse (str, &spec->data.basic.color))
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Could not parse color \"%s\""),
|
|
str);
|
|
meta_color_spec_free (spec);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
g_assert (spec);
|
|
|
|
return spec;
|
|
}
|
|
|
|
MetaColorSpec*
|
|
meta_color_spec_new_gtk (MetaGtkColorComponent component,
|
|
GtkStateType state)
|
|
{
|
|
MetaColorSpec *spec;
|
|
|
|
spec = meta_color_spec_new (META_COLOR_SPEC_GTK);
|
|
|
|
spec->data.gtk.component = component;
|
|
spec->data.gtk.state = state;
|
|
|
|
return spec;
|
|
}
|
|
|
|
void
|
|
meta_color_spec_render (MetaColorSpec *spec,
|
|
GtkWidget *widget,
|
|
GdkColor *color)
|
|
{
|
|
g_return_if_fail (spec != NULL);
|
|
g_return_if_fail (GTK_IS_WIDGET (widget));
|
|
g_return_if_fail (widget->style != NULL);
|
|
|
|
switch (spec->type)
|
|
{
|
|
case META_COLOR_SPEC_BASIC:
|
|
*color = spec->data.basic.color;
|
|
break;
|
|
|
|
case META_COLOR_SPEC_GTK:
|
|
switch (spec->data.gtk.component)
|
|
{
|
|
case META_GTK_COLOR_BG:
|
|
*color = widget->style->bg[spec->data.gtk.state];
|
|
break;
|
|
case META_GTK_COLOR_FG:
|
|
*color = widget->style->fg[spec->data.gtk.state];
|
|
break;
|
|
case META_GTK_COLOR_BASE:
|
|
*color = widget->style->base[spec->data.gtk.state];
|
|
break;
|
|
case META_GTK_COLOR_TEXT:
|
|
*color = widget->style->text[spec->data.gtk.state];
|
|
break;
|
|
case META_GTK_COLOR_LIGHT:
|
|
*color = widget->style->light[spec->data.gtk.state];
|
|
break;
|
|
case META_GTK_COLOR_DARK:
|
|
*color = widget->style->dark[spec->data.gtk.state];
|
|
break;
|
|
case META_GTK_COLOR_MID:
|
|
*color = widget->style->mid[spec->data.gtk.state];
|
|
break;
|
|
case META_GTK_COLOR_TEXT_AA:
|
|
*color = widget->style->text_aa[spec->data.gtk.state];
|
|
break;
|
|
case META_GTK_COLOR_LAST:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case META_COLOR_SPEC_BLEND:
|
|
{
|
|
GdkColor bg, fg;
|
|
|
|
meta_color_spec_render (spec->data.blend.background, widget, &bg);
|
|
meta_color_spec_render (spec->data.blend.foreground, widget, &fg);
|
|
|
|
color_composite (&bg, &fg, spec->data.blend.alpha, color);
|
|
}
|
|
break;
|
|
|
|
case META_COLOR_SPEC_SHADE:
|
|
{
|
|
GdkColor base;
|
|
|
|
meta_color_spec_render (spec->data.shade.base, widget, &base);
|
|
|
|
gtk_style_shade (&base, &base, spec->data.shade.factor);
|
|
|
|
*color = base;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
typedef enum
|
|
{
|
|
POS_TOKEN_INT,
|
|
POS_TOKEN_DOUBLE,
|
|
POS_TOKEN_OPERATOR,
|
|
POS_TOKEN_VARIABLE,
|
|
POS_TOKEN_OPEN_PAREN,
|
|
POS_TOKEN_CLOSE_PAREN
|
|
} PosTokenType;
|
|
|
|
typedef enum
|
|
{
|
|
POS_OP_NONE,
|
|
POS_OP_ADD,
|
|
POS_OP_SUBTRACT,
|
|
POS_OP_MULTIPLY,
|
|
POS_OP_DIVIDE,
|
|
POS_OP_MOD,
|
|
POS_OP_MAX,
|
|
POS_OP_MIN
|
|
} PosOperatorType;
|
|
|
|
static const char*
|
|
op_name (PosOperatorType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case POS_OP_ADD:
|
|
return "+";
|
|
case POS_OP_SUBTRACT:
|
|
return "-";
|
|
case POS_OP_MULTIPLY:
|
|
return "*";
|
|
case POS_OP_DIVIDE:
|
|
return "/";
|
|
case POS_OP_MOD:
|
|
return "%";
|
|
case POS_OP_MAX:
|
|
return "`max`";
|
|
case POS_OP_MIN:
|
|
return "`min`";
|
|
case POS_OP_NONE:
|
|
break;
|
|
}
|
|
|
|
return "<unknown>";
|
|
}
|
|
|
|
static PosOperatorType
|
|
op_from_string (const char *p,
|
|
int *len)
|
|
{
|
|
*len = 0;
|
|
|
|
switch (*p)
|
|
{
|
|
case '+':
|
|
*len = 1;
|
|
return POS_OP_ADD;
|
|
case '-':
|
|
*len = 1;
|
|
return POS_OP_SUBTRACT;
|
|
case '*':
|
|
*len = 1;
|
|
return POS_OP_MULTIPLY;
|
|
case '/':
|
|
*len = 1;
|
|
return POS_OP_DIVIDE;
|
|
case '%':
|
|
*len = 1;
|
|
return POS_OP_MOD;
|
|
|
|
case '`':
|
|
if (p[0] == '`' &&
|
|
p[1] == 'm' &&
|
|
p[2] == 'a' &&
|
|
p[3] == 'x' &&
|
|
p[4] == '`')
|
|
{
|
|
*len = 5;
|
|
return POS_OP_MAX;
|
|
}
|
|
else if (p[0] == '`' &&
|
|
p[1] == 'm' &&
|
|
p[2] == 'i' &&
|
|
p[3] == 'n' &&
|
|
p[4] == '`')
|
|
{
|
|
*len = 5;
|
|
return POS_OP_MIN;
|
|
}
|
|
}
|
|
|
|
return POS_OP_NONE;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
PosTokenType type;
|
|
|
|
union
|
|
{
|
|
struct {
|
|
int val;
|
|
} i;
|
|
|
|
struct {
|
|
double val;
|
|
} d;
|
|
|
|
struct {
|
|
PosOperatorType op;
|
|
} o;
|
|
|
|
struct {
|
|
char *name;
|
|
} v;
|
|
|
|
} d;
|
|
|
|
} PosToken;
|
|
|
|
|
|
static void
|
|
free_tokens (PosToken *tokens,
|
|
int n_tokens)
|
|
{
|
|
int i;
|
|
|
|
/* n_tokens can be 0 since tokens may have been allocated more than
|
|
* it was initialized
|
|
*/
|
|
|
|
for (i = 0; i < n_tokens; i++)
|
|
if (tokens[i].type == POS_TOKEN_VARIABLE)
|
|
g_free (tokens[i].d.v.name);
|
|
|
|
g_free (tokens);
|
|
}
|
|
|
|
static gboolean
|
|
parse_number (const char *p,
|
|
const char **end_return,
|
|
PosToken *next,
|
|
GError **err)
|
|
{
|
|
const char *start = p;
|
|
char *end;
|
|
gboolean is_float;
|
|
char *num_str;
|
|
|
|
while (*p && (*p == '.' || g_ascii_isdigit (*p)))
|
|
++p;
|
|
|
|
if (p == start)
|
|
{
|
|
char buf[7] = { '\0' };
|
|
buf[g_unichar_to_utf8 (g_utf8_get_char (p), buf)] = '\0';
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_BAD_CHARACTER,
|
|
_("Coordinate expression contains character '%s' which is not allowed"),
|
|
buf);
|
|
return FALSE;
|
|
}
|
|
|
|
*end_return = p;
|
|
|
|
/* we need this to exclude floats like "1e6" */
|
|
num_str = g_strndup (start, p - start);
|
|
start = num_str;
|
|
is_float = FALSE;
|
|
while (*start)
|
|
{
|
|
if (*start == '.')
|
|
is_float = TRUE;
|
|
++start;
|
|
}
|
|
|
|
if (is_float)
|
|
{
|
|
next->type = POS_TOKEN_DOUBLE;
|
|
next->d.d.val = g_ascii_strtod (num_str, &end);
|
|
|
|
if (end == num_str)
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Coordinate expression contains floating point number '%s' which could not be parsed"),
|
|
num_str);
|
|
g_free (num_str);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
next->type = POS_TOKEN_INT;
|
|
next->d.i.val = strtol (num_str, &end, 10);
|
|
if (end == num_str)
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Coordinate expression contains integer '%s' which could not be parsed"),
|
|
num_str);
|
|
g_free (num_str);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
g_free (num_str);
|
|
|
|
g_assert (next->type == POS_TOKEN_INT || next->type == POS_TOKEN_DOUBLE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#define IS_VARIABLE_CHAR(c) (g_ascii_isalpha ((c)) || (c) == '_')
|
|
|
|
static gboolean
|
|
pos_tokenize (const char *expr,
|
|
PosToken **tokens_p,
|
|
int *n_tokens_p,
|
|
GError **err)
|
|
{
|
|
PosToken *tokens;
|
|
int n_tokens;
|
|
int allocated;
|
|
const char *p;
|
|
|
|
*tokens_p = NULL;
|
|
*n_tokens_p = 0;
|
|
|
|
allocated = 3;
|
|
n_tokens = 0;
|
|
tokens = g_new (PosToken, allocated);
|
|
|
|
p = expr;
|
|
while (*p)
|
|
{
|
|
PosToken *next;
|
|
int len;
|
|
|
|
if (n_tokens == allocated)
|
|
{
|
|
allocated *= 2;
|
|
tokens = g_renew (PosToken, tokens, allocated);
|
|
}
|
|
|
|
next = &tokens[n_tokens];
|
|
|
|
switch (*p)
|
|
{
|
|
case '*':
|
|
case '/':
|
|
case '+':
|
|
case '-': /* negative numbers aren't allowed so this is easy */
|
|
case '%':
|
|
case '`':
|
|
next->type = POS_TOKEN_OPERATOR;
|
|
next->d.o.op = op_from_string (p, &len);
|
|
if (next->d.o.op != POS_OP_NONE)
|
|
{
|
|
++n_tokens;
|
|
p = p + (len - 1); /* -1 since we ++p later */
|
|
}
|
|
else
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Coordinate expression contained unknown operator at the start of this text: \"%s\""),
|
|
p);
|
|
|
|
goto error;
|
|
}
|
|
break;
|
|
|
|
case '(':
|
|
next->type = POS_TOKEN_OPEN_PAREN;
|
|
++n_tokens;
|
|
break;
|
|
|
|
case ')':
|
|
next->type = POS_TOKEN_CLOSE_PAREN;
|
|
++n_tokens;
|
|
break;
|
|
|
|
case ' ':
|
|
case '\t':
|
|
case '\n':
|
|
break;
|
|
|
|
default:
|
|
if (IS_VARIABLE_CHAR (*p))
|
|
{
|
|
/* Assume variable */
|
|
const char *start = p;
|
|
while (*p && IS_VARIABLE_CHAR (*p))
|
|
++p;
|
|
g_assert (p != start);
|
|
next->type = POS_TOKEN_VARIABLE;
|
|
next->d.v.name = g_strndup (start, p - start);
|
|
++n_tokens;
|
|
--p; /* since we ++p again at the end of while loop */
|
|
}
|
|
else
|
|
{
|
|
/* Assume number */
|
|
const char *end;
|
|
|
|
if (!parse_number (p, &end, next, err))
|
|
goto error;
|
|
|
|
++n_tokens;
|
|
p = end - 1; /* -1 since we ++p again at the end of while loop */
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
++p;
|
|
}
|
|
|
|
if (n_tokens == 0)
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Coordinate expression was empty or not understood"));
|
|
|
|
goto error;
|
|
}
|
|
|
|
*tokens_p = tokens;
|
|
*n_tokens_p = n_tokens;
|
|
|
|
return TRUE;
|
|
|
|
error:
|
|
g_assert (err == NULL || *err != NULL);
|
|
|
|
free_tokens (tokens, n_tokens);
|
|
return FALSE;
|
|
}
|
|
|
|
#if 0
|
|
static void
|
|
debug_print_tokens (PosToken *tokens,
|
|
int n_tokens)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < n_tokens; i++)
|
|
{
|
|
PosToken *t = &tokens[i];
|
|
|
|
g_print (" ");
|
|
|
|
switch (t->type)
|
|
{
|
|
case POS_TOKEN_INT:
|
|
g_print ("\"%d\"", t->d.i.val);
|
|
break;
|
|
case POS_TOKEN_DOUBLE:
|
|
g_print ("\"%g\"", t->d.d.val);
|
|
break;
|
|
case POS_TOKEN_OPEN_PAREN:
|
|
g_print ("\"(\"");
|
|
break;
|
|
case POS_TOKEN_CLOSE_PAREN:
|
|
g_print ("\")\"");
|
|
break;
|
|
case POS_TOKEN_VARIABLE:
|
|
g_print ("\"%s\"", t->d.v.name);
|
|
break;
|
|
case POS_TOKEN_OPERATOR:
|
|
g_print ("\"%s\"", op_name (t->d.o.op));
|
|
break;
|
|
}
|
|
}
|
|
|
|
g_print ("\n");
|
|
}
|
|
#endif
|
|
|
|
typedef enum
|
|
{
|
|
POS_EXPR_INT,
|
|
POS_EXPR_DOUBLE,
|
|
POS_EXPR_OPERATOR
|
|
} PosExprType;
|
|
|
|
typedef struct
|
|
{
|
|
PosExprType type;
|
|
union
|
|
{
|
|
double double_val;
|
|
int int_val;
|
|
char operator;
|
|
} d;
|
|
} PosExpr;
|
|
|
|
#if 0
|
|
static void
|
|
debug_print_exprs (PosExpr *exprs,
|
|
int n_exprs)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < n_exprs; i++)
|
|
{
|
|
switch (exprs[i].type)
|
|
{
|
|
case POS_EXPR_INT:
|
|
g_print (" %d", exprs[i].d.int_val);
|
|
break;
|
|
case POS_EXPR_DOUBLE:
|
|
g_print (" %g", exprs[i].d.double_val);
|
|
break;
|
|
case POS_EXPR_OPERATOR:
|
|
g_print (" %s", op_name (exprs[i].d.operator));
|
|
break;
|
|
}
|
|
}
|
|
g_print ("\n");
|
|
}
|
|
#endif
|
|
|
|
static gboolean
|
|
do_operation (PosExpr *a,
|
|
PosExpr *b,
|
|
PosOperatorType op,
|
|
GError **err)
|
|
{
|
|
/* Promote types to double if required */
|
|
if (a->type == POS_EXPR_DOUBLE ||
|
|
b->type == POS_EXPR_DOUBLE)
|
|
{
|
|
if (a->type != POS_EXPR_DOUBLE)
|
|
{
|
|
a->type = POS_EXPR_DOUBLE;
|
|
a->d.double_val = a->d.int_val;
|
|
}
|
|
if (b->type != POS_EXPR_DOUBLE)
|
|
{
|
|
b->type = POS_EXPR_DOUBLE;
|
|
b->d.double_val = b->d.int_val;
|
|
}
|
|
}
|
|
|
|
g_assert (a->type == b->type);
|
|
|
|
if (a->type == POS_EXPR_INT)
|
|
{
|
|
switch (op)
|
|
{
|
|
case POS_OP_MULTIPLY:
|
|
a->d.int_val = a->d.int_val * b->d.int_val;
|
|
break;
|
|
case POS_OP_DIVIDE:
|
|
if (b->d.int_val == 0)
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_DIVIDE_BY_ZERO,
|
|
_("Coordinate expression results in division by zero"));
|
|
return FALSE;
|
|
}
|
|
a->d.int_val = a->d.int_val / b->d.int_val;
|
|
break;
|
|
case POS_OP_MOD:
|
|
if (b->d.int_val == 0)
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_DIVIDE_BY_ZERO,
|
|
_("Coordinate expression results in division by zero"));
|
|
return FALSE;
|
|
}
|
|
a->d.int_val = a->d.int_val % b->d.int_val;
|
|
break;
|
|
case POS_OP_ADD:
|
|
a->d.int_val = a->d.int_val + b->d.int_val;
|
|
break;
|
|
case POS_OP_SUBTRACT:
|
|
a->d.int_val = a->d.int_val - b->d.int_val;
|
|
break;
|
|
case POS_OP_MAX:
|
|
a->d.int_val = MAX (a->d.int_val, b->d.int_val);
|
|
break;
|
|
case POS_OP_MIN:
|
|
a->d.int_val = MIN (a->d.int_val, b->d.int_val);
|
|
break;
|
|
case POS_OP_NONE:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
}
|
|
else if (a->type == POS_EXPR_DOUBLE)
|
|
{
|
|
switch (op)
|
|
{
|
|
case POS_OP_MULTIPLY:
|
|
a->d.double_val = a->d.double_val * b->d.double_val;
|
|
break;
|
|
case POS_OP_DIVIDE:
|
|
if (b->d.double_val == 0.0)
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_DIVIDE_BY_ZERO,
|
|
_("Coordinate expression results in division by zero"));
|
|
return FALSE;
|
|
}
|
|
a->d.double_val = a->d.double_val / b->d.double_val;
|
|
break;
|
|
case POS_OP_MOD:
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_MOD_ON_FLOAT,
|
|
_("Coordinate expression tries to use mod operator on a floating-point number"));
|
|
return FALSE;
|
|
case POS_OP_ADD:
|
|
a->d.double_val = a->d.double_val + b->d.double_val;
|
|
break;
|
|
case POS_OP_SUBTRACT:
|
|
a->d.double_val = a->d.double_val - b->d.double_val;
|
|
break;
|
|
case POS_OP_MAX:
|
|
a->d.double_val = MAX (a->d.double_val, b->d.double_val);
|
|
break;
|
|
case POS_OP_MIN:
|
|
a->d.double_val = MIN (a->d.double_val, b->d.double_val);
|
|
break;
|
|
case POS_OP_NONE:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
g_assert_not_reached ();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
do_operations (PosExpr *exprs,
|
|
int *n_exprs,
|
|
int precedence,
|
|
GError **err)
|
|
{
|
|
int i;
|
|
|
|
#if 0
|
|
g_print ("Doing prec %d ops on %d exprs\n", precedence, *n_exprs);
|
|
debug_print_exprs (exprs, *n_exprs);
|
|
#endif
|
|
|
|
i = 1;
|
|
while (i < *n_exprs)
|
|
{
|
|
gboolean compress;
|
|
|
|
/* exprs[i-1] first operand
|
|
* exprs[i] operator
|
|
* exprs[i+1] second operand
|
|
*
|
|
* we replace first operand with result of mul/div/mod,
|
|
* or skip over operator and second operand if we have
|
|
* an add/subtract
|
|
*/
|
|
|
|
if (exprs[i-1].type == POS_EXPR_OPERATOR)
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Coordinate expression has an operator \"%s\" where an operand was expected"),
|
|
op_name (exprs[i-1].d.operator));
|
|
return FALSE;
|
|
}
|
|
|
|
if (exprs[i].type != POS_EXPR_OPERATOR)
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Coordinate expression had an operand where an operator was expected"));
|
|
return FALSE;
|
|
}
|
|
|
|
if (i == (*n_exprs - 1))
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Coordinate expression ended with an operator instead of an operand"));
|
|
return FALSE;
|
|
}
|
|
|
|
g_assert ((i+1) < *n_exprs);
|
|
|
|
if (exprs[i+1].type == POS_EXPR_OPERATOR)
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Coordinate expression has operator \"%c\" following operator \"%c\" with no operand in between"),
|
|
exprs[i+1].d.operator,
|
|
exprs[i].d.operator);
|
|
return FALSE;
|
|
}
|
|
|
|
compress = FALSE;
|
|
|
|
switch (precedence)
|
|
{
|
|
case 2:
|
|
switch (exprs[i].d.operator)
|
|
{
|
|
case POS_OP_DIVIDE:
|
|
case POS_OP_MOD:
|
|
case POS_OP_MULTIPLY:
|
|
compress = TRUE;
|
|
if (!do_operation (&exprs[i-1], &exprs[i+1],
|
|
exprs[i].d.operator,
|
|
err))
|
|
return FALSE;
|
|
break;
|
|
}
|
|
break;
|
|
case 1:
|
|
switch (exprs[i].d.operator)
|
|
{
|
|
case POS_OP_ADD:
|
|
case POS_OP_SUBTRACT:
|
|
compress = TRUE;
|
|
if (!do_operation (&exprs[i-1], &exprs[i+1],
|
|
exprs[i].d.operator,
|
|
err))
|
|
return FALSE;
|
|
break;
|
|
}
|
|
break;
|
|
/* I have no rationale at all for making these low-precedence */
|
|
case 0:
|
|
switch (exprs[i].d.operator)
|
|
{
|
|
case POS_OP_MAX:
|
|
case POS_OP_MIN:
|
|
compress = TRUE;
|
|
if (!do_operation (&exprs[i-1], &exprs[i+1],
|
|
exprs[i].d.operator,
|
|
err))
|
|
return FALSE;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (compress)
|
|
{
|
|
/* exprs[i-1] first operand (now result)
|
|
* exprs[i] operator
|
|
* exprs[i+1] second operand
|
|
* exprs[i+2] new operator
|
|
*
|
|
* we move new operator just after first operand
|
|
*/
|
|
if ((i+2) < *n_exprs)
|
|
{
|
|
g_memmove (&exprs[i], &exprs[i+2],
|
|
sizeof (PosExpr) * (*n_exprs - i - 2));
|
|
}
|
|
|
|
*n_exprs -= 2;
|
|
}
|
|
else
|
|
{
|
|
/* Skip operator and next operand */
|
|
i += 2;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
pos_eval_helper (PosToken *tokens,
|
|
int n_tokens,
|
|
const MetaPositionExprEnv *env,
|
|
PosExpr *result,
|
|
GError **err)
|
|
{
|
|
/* lazy-ass hardcoded limit on expression size */
|
|
#define MAX_EXPRS 32
|
|
int paren_level;
|
|
int first_paren;
|
|
int i;
|
|
PosExpr exprs[MAX_EXPRS];
|
|
int n_exprs;
|
|
int ival;
|
|
double dval;
|
|
int precedence;
|
|
|
|
#if 0
|
|
g_print ("Pos eval helper on %d tokens:\n", n_tokens);
|
|
debug_print_tokens (tokens, n_tokens);
|
|
#endif
|
|
|
|
/* Our first goal is to get a list of PosExpr, essentially
|
|
* substituting variables and handling parentheses.
|
|
*/
|
|
|
|
first_paren = 0;
|
|
paren_level = 0;
|
|
n_exprs = 0;
|
|
for (i = 0; i < n_tokens; i++)
|
|
{
|
|
PosToken *t = &tokens[i];
|
|
|
|
if (n_exprs >= MAX_EXPRS)
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Coordinate expression parser overflowed its buffer."));
|
|
return FALSE;
|
|
}
|
|
|
|
if (paren_level == 0)
|
|
{
|
|
switch (t->type)
|
|
{
|
|
case POS_TOKEN_INT:
|
|
exprs[n_exprs].type = POS_EXPR_INT;
|
|
exprs[n_exprs].d.int_val = t->d.i.val;
|
|
++n_exprs;
|
|
break;
|
|
|
|
case POS_TOKEN_DOUBLE:
|
|
exprs[n_exprs].type = POS_EXPR_DOUBLE;
|
|
exprs[n_exprs].d.double_val = t->d.d.val;
|
|
++n_exprs;
|
|
break;
|
|
|
|
case POS_TOKEN_OPEN_PAREN:
|
|
++paren_level;
|
|
if (paren_level == 1)
|
|
first_paren = i;
|
|
break;
|
|
|
|
case POS_TOKEN_CLOSE_PAREN:
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_BAD_PARENS,
|
|
_("Coordinate expression had a close parenthesis with no open parenthesis"));
|
|
return FALSE;
|
|
|
|
case POS_TOKEN_VARIABLE:
|
|
exprs[n_exprs].type = POS_EXPR_INT;
|
|
|
|
/* FIXME we should just dump all this crap
|
|
* in a hash, maybe keep width/height out
|
|
* for optimization purposes
|
|
*/
|
|
if (strcmp (t->d.v.name, "width") == 0)
|
|
exprs[n_exprs].d.int_val = env->rect.width;
|
|
else if (strcmp (t->d.v.name, "height") == 0)
|
|
exprs[n_exprs].d.int_val = env->rect.height;
|
|
else if (env->object_width >= 0 &&
|
|
strcmp (t->d.v.name, "object_width") == 0)
|
|
exprs[n_exprs].d.int_val = env->object_width;
|
|
else if (env->object_height >= 0 &&
|
|
strcmp (t->d.v.name, "object_height") == 0)
|
|
exprs[n_exprs].d.int_val = env->object_height;
|
|
else if (strcmp (t->d.v.name, "left_width") == 0)
|
|
exprs[n_exprs].d.int_val = env->left_width;
|
|
else if (strcmp (t->d.v.name, "right_width") == 0)
|
|
exprs[n_exprs].d.int_val = env->right_width;
|
|
else if (strcmp (t->d.v.name, "top_height") == 0)
|
|
exprs[n_exprs].d.int_val = env->top_height;
|
|
else if (strcmp (t->d.v.name, "bottom_height") == 0)
|
|
exprs[n_exprs].d.int_val = env->bottom_height;
|
|
else if (strcmp (t->d.v.name, "mini_icon_width") == 0)
|
|
exprs[n_exprs].d.int_val = env->mini_icon_width;
|
|
else if (strcmp (t->d.v.name, "mini_icon_height") == 0)
|
|
exprs[n_exprs].d.int_val = env->mini_icon_height;
|
|
else if (strcmp (t->d.v.name, "icon_width") == 0)
|
|
exprs[n_exprs].d.int_val = env->icon_width;
|
|
else if (strcmp (t->d.v.name, "icon_height") == 0)
|
|
exprs[n_exprs].d.int_val = env->icon_height;
|
|
else if (strcmp (t->d.v.name, "title_width") == 0)
|
|
exprs[n_exprs].d.int_val = env->title_width;
|
|
else if (strcmp (t->d.v.name, "title_height") == 0)
|
|
exprs[n_exprs].d.int_val = env->title_height;
|
|
/* In practice we only hit this code on initial theme
|
|
* parse; after that we always optimize constants away
|
|
*/
|
|
else if (env->theme &&
|
|
meta_theme_lookup_int_constant (env->theme,
|
|
t->d.v.name,
|
|
&ival))
|
|
{
|
|
exprs[n_exprs].d.int_val = ival;
|
|
}
|
|
else if (env->theme &&
|
|
meta_theme_lookup_float_constant (env->theme,
|
|
t->d.v.name,
|
|
&dval))
|
|
{
|
|
exprs[n_exprs].type = POS_EXPR_DOUBLE;
|
|
exprs[n_exprs].d.double_val = dval;
|
|
}
|
|
else
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_UNKNOWN_VARIABLE,
|
|
_("Coordinate expression had unknown variable or constant \"%s\""),
|
|
t->d.v.name);
|
|
return FALSE;
|
|
}
|
|
++n_exprs;
|
|
break;
|
|
|
|
case POS_TOKEN_OPERATOR:
|
|
exprs[n_exprs].type = POS_EXPR_OPERATOR;
|
|
exprs[n_exprs].d.operator = t->d.o.op;
|
|
++n_exprs;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_assert (paren_level > 0);
|
|
|
|
switch (t->type)
|
|
{
|
|
case POS_TOKEN_INT:
|
|
case POS_TOKEN_DOUBLE:
|
|
case POS_TOKEN_VARIABLE:
|
|
case POS_TOKEN_OPERATOR:
|
|
break;
|
|
|
|
case POS_TOKEN_OPEN_PAREN:
|
|
++paren_level;
|
|
break;
|
|
|
|
case POS_TOKEN_CLOSE_PAREN:
|
|
if (paren_level == 1)
|
|
{
|
|
/* We closed a toplevel paren group, so recurse */
|
|
if (!pos_eval_helper (&tokens[first_paren+1],
|
|
i - first_paren - 1,
|
|
env,
|
|
&exprs[n_exprs],
|
|
err))
|
|
return FALSE;
|
|
|
|
++n_exprs;
|
|
}
|
|
|
|
--paren_level;
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if (paren_level > 0)
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_BAD_PARENS,
|
|
_("Coordinate expression had an open parenthesis with no close parenthesis"));
|
|
return FALSE;
|
|
}
|
|
|
|
/* Now we have no parens and no vars; so we just do all the multiplies
|
|
* and divides, then all the add and subtract.
|
|
*/
|
|
if (n_exprs == 0)
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Coordinate expression doesn't seem to have any operators or operands"));
|
|
return FALSE;
|
|
}
|
|
|
|
/* precedence 1 ops */
|
|
precedence = 2;
|
|
while (precedence >= 0)
|
|
{
|
|
if (!do_operations (exprs, &n_exprs, precedence, err))
|
|
return FALSE;
|
|
--precedence;
|
|
}
|
|
|
|
g_assert (n_exprs == 1);
|
|
|
|
*result = *exprs;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* expr = int | double | expr * expr | expr / expr |
|
|
* expr + expr | expr - expr | (expr)
|
|
*
|
|
* so very not worth fooling with bison, yet so very painful by hand.
|
|
*/
|
|
static gboolean
|
|
pos_eval (PosToken *tokens,
|
|
int n_tokens,
|
|
const MetaPositionExprEnv *env,
|
|
int *val_p,
|
|
GError **err)
|
|
{
|
|
PosExpr expr;
|
|
|
|
*val_p = 0;
|
|
|
|
if (pos_eval_helper (tokens, n_tokens, env, &expr, err))
|
|
{
|
|
switch (expr.type)
|
|
{
|
|
case POS_EXPR_INT:
|
|
*val_p = expr.d.int_val;
|
|
break;
|
|
case POS_EXPR_DOUBLE:
|
|
*val_p = expr.d.double_val;
|
|
break;
|
|
case POS_EXPR_OPERATOR:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* We always return both X and Y, but only one will be meaningful in
|
|
* most contexts.
|
|
*/
|
|
|
|
gboolean
|
|
meta_parse_position_expression (const char *expr,
|
|
const MetaPositionExprEnv *env,
|
|
int *x_return,
|
|
int *y_return,
|
|
GError **err)
|
|
{
|
|
/* All positions are in a coordinate system with x, y at the origin.
|
|
* The expression can have -, +, *, / as operators, floating point
|
|
* or integer constants, and the variables "width" and "height" and
|
|
* optionally "object_width" and object_height". Negative numbers
|
|
* aren't allowed.
|
|
*/
|
|
PosToken *tokens;
|
|
int n_tokens;
|
|
int val;
|
|
|
|
if (!pos_tokenize (expr, &tokens, &n_tokens, err))
|
|
{
|
|
g_assert (err == NULL || *err != NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
#if 0
|
|
g_print ("Tokenized \"%s\" to --->\n", expr);
|
|
debug_print_tokens (tokens, n_tokens);
|
|
#endif
|
|
|
|
if (pos_eval (tokens, n_tokens, env, &val, err))
|
|
{
|
|
if (x_return)
|
|
*x_return = env->rect.x + val;
|
|
if (y_return)
|
|
*y_return = env->rect.y + val;
|
|
free_tokens (tokens, n_tokens);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
g_assert (err == NULL || *err != NULL);
|
|
free_tokens (tokens, n_tokens);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
gboolean
|
|
meta_parse_size_expression (const char *expr,
|
|
const MetaPositionExprEnv *env,
|
|
int *val_return,
|
|
GError **err)
|
|
{
|
|
PosToken *tokens;
|
|
int n_tokens;
|
|
int val;
|
|
|
|
if (!pos_tokenize (expr, &tokens, &n_tokens, err))
|
|
{
|
|
g_assert (err == NULL || *err != NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
#if 0
|
|
g_print ("Tokenized \"%s\" to --->\n", expr);
|
|
debug_print_tokens (tokens, n_tokens);
|
|
#endif
|
|
|
|
if (pos_eval (tokens, n_tokens, env, &val, err))
|
|
{
|
|
if (val_return)
|
|
*val_return = MAX (val, 1); /* require that sizes be at least 1x1 */
|
|
free_tokens (tokens, n_tokens);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
g_assert (err == NULL || *err != NULL);
|
|
free_tokens (tokens, n_tokens);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* To do this we tokenize, replace variable tokens
|
|
* that are constants, then reassemble. The purpose
|
|
* here is to optimize expressions so we don't do hash
|
|
* lookups to eval them. Obviously it's a tradeoff that
|
|
* slows down theme load times.
|
|
*/
|
|
char*
|
|
meta_theme_replace_constants (MetaTheme *theme,
|
|
const char *expr,
|
|
GError **err)
|
|
{
|
|
PosToken *tokens;
|
|
int n_tokens;
|
|
int i;
|
|
GString *str;
|
|
char buf[G_ASCII_DTOSTR_BUF_SIZE];
|
|
double dval;
|
|
int ival;
|
|
|
|
if (!pos_tokenize (expr, &tokens, &n_tokens, err))
|
|
{
|
|
g_assert (err == NULL || *err != NULL);
|
|
return NULL;
|
|
}
|
|
|
|
#if 0
|
|
g_print ("Tokenized \"%s\" to --->\n", expr);
|
|
debug_print_tokens (tokens, n_tokens);
|
|
#endif
|
|
|
|
str = g_string_new (NULL);
|
|
|
|
for (i = 0; i < n_tokens; i++)
|
|
{
|
|
PosToken *t = &tokens[i];
|
|
|
|
/* spaces so we don't accidentally merge variables
|
|
* or anything like that
|
|
*/
|
|
if (i > 0)
|
|
g_string_append_c (str, ' ');
|
|
|
|
switch (t->type)
|
|
{
|
|
case POS_TOKEN_INT:
|
|
g_string_append_printf (str, "%d", t->d.i.val);
|
|
break;
|
|
case POS_TOKEN_DOUBLE:
|
|
g_ascii_formatd (buf, G_ASCII_DTOSTR_BUF_SIZE,
|
|
"%g", t->d.d.val);
|
|
g_string_append (str, buf);
|
|
break;
|
|
case POS_TOKEN_OPEN_PAREN:
|
|
g_string_append_c (str, '(');
|
|
break;
|
|
case POS_TOKEN_CLOSE_PAREN:
|
|
g_string_append_c (str, ')');
|
|
break;
|
|
case POS_TOKEN_VARIABLE:
|
|
if (meta_theme_lookup_int_constant (theme, t->d.v.name, &ival))
|
|
g_string_append_printf (str, "%d", ival);
|
|
else if (meta_theme_lookup_float_constant (theme, t->d.v.name, &dval))
|
|
{
|
|
g_ascii_formatd (buf, G_ASCII_DTOSTR_BUF_SIZE,
|
|
"%g", dval);
|
|
g_string_append (str, buf);
|
|
}
|
|
else
|
|
{
|
|
g_string_append (str, t->d.v.name);
|
|
}
|
|
break;
|
|
case POS_TOKEN_OPERATOR:
|
|
g_string_append (str, op_name (t->d.o.op));
|
|
break;
|
|
}
|
|
}
|
|
|
|
free_tokens (tokens, n_tokens);
|
|
|
|
return g_string_free (str, FALSE);
|
|
}
|
|
|
|
static int
|
|
parse_x_position_unchecked (const char *expr,
|
|
const MetaPositionExprEnv *env)
|
|
{
|
|
int retval;
|
|
GError *error;
|
|
|
|
retval = 0;
|
|
error = NULL;
|
|
if (!meta_parse_position_expression (expr, env,
|
|
&retval, NULL,
|
|
&error))
|
|
{
|
|
meta_warning (_("Theme contained an expression \"%s\" that resulted in an error: %s\n"),
|
|
expr, error->message);
|
|
|
|
g_error_free (error);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int
|
|
parse_y_position_unchecked (const char *expr,
|
|
const MetaPositionExprEnv *env)
|
|
{
|
|
int retval;
|
|
GError *error;
|
|
|
|
retval = 0;
|
|
error = NULL;
|
|
if (!meta_parse_position_expression (expr, env,
|
|
NULL, &retval,
|
|
&error))
|
|
{
|
|
meta_warning (_("Theme contained an expression \"%s\" that resulted in an error: %s\n"),
|
|
expr, error->message);
|
|
|
|
g_error_free (error);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int
|
|
parse_size_unchecked (const char *expr,
|
|
MetaPositionExprEnv *env)
|
|
{
|
|
int retval;
|
|
GError *error;
|
|
|
|
retval = 0;
|
|
error = NULL;
|
|
if (!meta_parse_size_expression (expr, env,
|
|
&retval, &error))
|
|
{
|
|
meta_warning (_("Theme contained an expression \"%s\" that resulted in an error: %s\n"),
|
|
expr, error->message);
|
|
|
|
g_error_free (error);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
g_free (op->data.line.x1);
|
|
g_free (op->data.line.y1);
|
|
g_free (op->data.line.x2);
|
|
g_free (op->data.line.y2);
|
|
break;
|
|
|
|
case META_DRAW_RECTANGLE:
|
|
if (op->data.rectangle.color_spec)
|
|
g_free (op->data.rectangle.color_spec);
|
|
g_free (op->data.rectangle.x);
|
|
g_free (op->data.rectangle.y);
|
|
g_free (op->data.rectangle.width);
|
|
g_free (op->data.rectangle.height);
|
|
break;
|
|
|
|
case META_DRAW_ARC:
|
|
if (op->data.arc.color_spec)
|
|
g_free (op->data.arc.color_spec);
|
|
g_free (op->data.arc.x);
|
|
g_free (op->data.arc.y);
|
|
g_free (op->data.arc.width);
|
|
g_free (op->data.arc.height);
|
|
break;
|
|
|
|
case META_DRAW_CLIP:
|
|
g_free (op->data.clip.x);
|
|
g_free (op->data.clip.y);
|
|
g_free (op->data.clip.width);
|
|
g_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);
|
|
g_free (op->data.tint.x);
|
|
g_free (op->data.tint.y);
|
|
g_free (op->data.tint.width);
|
|
g_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);
|
|
g_free (op->data.gradient.x);
|
|
g_free (op->data.gradient.y);
|
|
g_free (op->data.gradient.width);
|
|
g_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));
|
|
g_free (op->data.image.x);
|
|
g_free (op->data.image.y);
|
|
g_free (op->data.image.width);
|
|
g_free (op->data.image.height);
|
|
break;
|
|
|
|
case META_DRAW_GTK_ARROW:
|
|
g_free (op->data.gtk_arrow.x);
|
|
g_free (op->data.gtk_arrow.y);
|
|
g_free (op->data.gtk_arrow.width);
|
|
g_free (op->data.gtk_arrow.height);
|
|
break;
|
|
|
|
case META_DRAW_GTK_BOX:
|
|
g_free (op->data.gtk_box.x);
|
|
g_free (op->data.gtk_box.y);
|
|
g_free (op->data.gtk_box.width);
|
|
g_free (op->data.gtk_box.height);
|
|
break;
|
|
|
|
case META_DRAW_GTK_VLINE:
|
|
g_free (op->data.gtk_vline.x);
|
|
g_free (op->data.gtk_vline.y1);
|
|
g_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);
|
|
g_free (op->data.icon.x);
|
|
g_free (op->data.icon.y);
|
|
g_free (op->data.icon.width);
|
|
g_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);
|
|
g_free (op->data.title.x);
|
|
g_free (op->data.title.y);
|
|
break;
|
|
|
|
case META_DRAW_OP_LIST:
|
|
if (op->data.op_list.op_list)
|
|
meta_draw_op_list_unref (op->data.op_list.op_list);
|
|
g_free (op->data.op_list.x);
|
|
g_free (op->data.op_list.y);
|
|
g_free (op->data.op_list.width);
|
|
g_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);
|
|
g_free (op->data.tile.x);
|
|
g_free (op->data.tile.y);
|
|
g_free (op->data.tile.width);
|
|
g_free (op->data.tile.height);
|
|
g_free (op->data.tile.tile_xoffset);
|
|
g_free (op->data.tile.tile_yoffset);
|
|
g_free (op->data.tile.tile_width);
|
|
g_free (op->data.tile.tile_height);
|
|
break;
|
|
}
|
|
|
|
g_free (op);
|
|
}
|
|
|
|
static GdkGC*
|
|
get_gc_for_primitive (GtkWidget *widget,
|
|
GdkDrawable *drawable,
|
|
MetaColorSpec *color_spec,
|
|
const GdkRectangle *clip,
|
|
int line_width)
|
|
{
|
|
GdkGC *gc;
|
|
GdkGCValues values;
|
|
GdkColor color;
|
|
|
|
meta_color_spec_render (color_spec, widget, &color);
|
|
|
|
values.foreground = color;
|
|
|
|
gdk_rgb_find_color (gdk_drawable_get_colormap (drawable),
|
|
&values.foreground);
|
|
|
|
values.line_width = line_width;
|
|
|
|
gc = gdk_gc_new_with_values (drawable, &values,
|
|
GDK_GC_FOREGROUND | GDK_GC_LINE_WIDTH);
|
|
|
|
if (clip)
|
|
gdk_gc_set_clip_rectangle (gc,
|
|
(GdkRectangle*) clip); /* const cast */
|
|
|
|
return gc;
|
|
}
|
|
|
|
static GdkPixbuf*
|
|
apply_alpha (GdkPixbuf *pixbuf,
|
|
MetaAlphaGradientSpec *spec,
|
|
gboolean force_copy)
|
|
{
|
|
GdkPixbuf *new_pixbuf;
|
|
gboolean needs_alpha;
|
|
|
|
g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);
|
|
|
|
needs_alpha = spec && (spec->n_alphas > 1 ||
|
|
spec->alphas[0] != 0xff);
|
|
|
|
if (!needs_alpha)
|
|
return pixbuf;
|
|
|
|
if (!gdk_pixbuf_get_has_alpha (pixbuf))
|
|
{
|
|
new_pixbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0);
|
|
g_object_unref (G_OBJECT (pixbuf));
|
|
pixbuf = new_pixbuf;
|
|
}
|
|
else if (force_copy)
|
|
{
|
|
new_pixbuf = gdk_pixbuf_copy (pixbuf);
|
|
g_object_unref (G_OBJECT (pixbuf));
|
|
pixbuf = new_pixbuf;
|
|
}
|
|
|
|
g_assert (gdk_pixbuf_get_has_alpha (pixbuf));
|
|
|
|
meta_gradient_add_alpha (pixbuf, spec->alphas, spec->n_alphas, spec->type);
|
|
|
|
return pixbuf;
|
|
}
|
|
|
|
static void
|
|
render_pixbuf (GdkDrawable *drawable,
|
|
const GdkRectangle *clip,
|
|
GdkPixbuf *pixbuf,
|
|
int x,
|
|
int y)
|
|
{
|
|
/* grumble, render_to_drawable_alpha does not accept a clip
|
|
* mask, so we have to go through some BS
|
|
*/
|
|
/* FIXME once GTK 1.3.13 has been out a while we can use
|
|
* render_to_drawable() which now does alpha with clip.
|
|
*
|
|
* Though the gdk_rectangle_intersect() check may be a useful
|
|
* optimization anyway.
|
|
*/
|
|
GdkRectangle pixbuf_rect;
|
|
GdkRectangle draw_rect;
|
|
|
|
pixbuf_rect.x = x;
|
|
pixbuf_rect.y = y;
|
|
pixbuf_rect.width = gdk_pixbuf_get_width (pixbuf);
|
|
pixbuf_rect.height = gdk_pixbuf_get_height (pixbuf);
|
|
|
|
if (clip)
|
|
{
|
|
if (!gdk_rectangle_intersect ((GdkRectangle*)clip,
|
|
&pixbuf_rect, &draw_rect))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
draw_rect = pixbuf_rect;
|
|
}
|
|
|
|
gdk_pixbuf_render_to_drawable_alpha (pixbuf,
|
|
drawable,
|
|
draw_rect.x - pixbuf_rect.x,
|
|
draw_rect.y - pixbuf_rect.y,
|
|
draw_rect.x, draw_rect.y,
|
|
draw_rect.width,
|
|
draw_rect.height,
|
|
GDK_PIXBUF_ALPHA_FULL, /* ignored */
|
|
128, /* ignored */
|
|
GDK_RGB_DITHER_NORMAL,
|
|
draw_rect.x - pixbuf_rect.x,
|
|
draw_rect.y - pixbuf_rect.y);
|
|
}
|
|
|
|
static GdkPixbuf*
|
|
pixbuf_tile (GdkPixbuf *tile,
|
|
int width,
|
|
int height)
|
|
{
|
|
GdkPixbuf *pixbuf;
|
|
int tile_width;
|
|
int tile_height;
|
|
int i, j;
|
|
|
|
tile_width = gdk_pixbuf_get_width (tile);
|
|
tile_height = gdk_pixbuf_get_height (tile);
|
|
|
|
pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
|
|
gdk_pixbuf_get_has_alpha (tile),
|
|
8, width, height);
|
|
|
|
i = 0;
|
|
while (i < width)
|
|
{
|
|
j = 0;
|
|
while (j < height)
|
|
{
|
|
int w, h;
|
|
|
|
w = MIN (tile_width, width - i);
|
|
h = MIN (tile_height, height - j);
|
|
|
|
gdk_pixbuf_copy_area (tile,
|
|
0, 0,
|
|
w, h,
|
|
pixbuf,
|
|
i, j);
|
|
|
|
j += tile_height;
|
|
}
|
|
|
|
i += tile_width;
|
|
}
|
|
|
|
return pixbuf;
|
|
}
|
|
|
|
static GdkPixbuf *
|
|
replicate_rows (GdkPixbuf *src,
|
|
int src_x,
|
|
int src_y,
|
|
int width,
|
|
int height)
|
|
{
|
|
unsigned int n_channels = gdk_pixbuf_get_n_channels (src);
|
|
unsigned int src_rowstride = gdk_pixbuf_get_rowstride (src);
|
|
unsigned char *pixels = (gdk_pixbuf_get_pixels (src) + src_y * src_rowstride + src_x
|
|
* n_channels);
|
|
unsigned char *dest_pixels;
|
|
GdkPixbuf *result;
|
|
unsigned int dest_rowstride;
|
|
int i;
|
|
|
|
result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
|
|
width, height);
|
|
dest_rowstride = gdk_pixbuf_get_rowstride (result);
|
|
dest_pixels = gdk_pixbuf_get_pixels (result);
|
|
|
|
for (i = 0; i < height; i++)
|
|
memcpy (dest_pixels + dest_rowstride * i, pixels, n_channels * width);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GdkPixbuf *
|
|
replicate_cols (GdkPixbuf *src,
|
|
int src_x,
|
|
int src_y,
|
|
int width,
|
|
int height)
|
|
{
|
|
unsigned int n_channels = gdk_pixbuf_get_n_channels (src);
|
|
unsigned int src_rowstride = gdk_pixbuf_get_rowstride (src);
|
|
unsigned char *pixels = (gdk_pixbuf_get_pixels (src) + src_y * src_rowstride + src_x
|
|
* n_channels);
|
|
unsigned char *dest_pixels;
|
|
GdkPixbuf *result;
|
|
unsigned int dest_rowstride;
|
|
int i, j;
|
|
|
|
result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
|
|
width, height);
|
|
dest_rowstride = gdk_pixbuf_get_rowstride (result);
|
|
dest_pixels = gdk_pixbuf_get_pixels (result);
|
|
|
|
for (i = 0; i < height; i++)
|
|
{
|
|
unsigned char *p = dest_pixels + dest_rowstride * i;
|
|
unsigned char *q = pixels + src_rowstride * i;
|
|
|
|
unsigned char r = *(q++);
|
|
unsigned char g = *(q++);
|
|
unsigned char b = *(q++);
|
|
|
|
if (n_channels == 4)
|
|
{
|
|
unsigned char a;
|
|
|
|
a = *(q++);
|
|
|
|
for (j = 0; j < width; j++)
|
|
{
|
|
*(p++) = r;
|
|
*(p++) = g;
|
|
*(p++) = b;
|
|
*(p++) = a;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (j = 0; j < width; j++)
|
|
{
|
|
*(p++) = r;
|
|
*(p++) = g;
|
|
*(p++) = b;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static GdkPixbuf*
|
|
scale_and_alpha_pixbuf (GdkPixbuf *src,
|
|
MetaAlphaGradientSpec *alpha_spec,
|
|
MetaImageFillType fill_type,
|
|
int width,
|
|
int height,
|
|
gboolean vertical_stripes,
|
|
gboolean horizontal_stripes)
|
|
{
|
|
GdkPixbuf *pixbuf;
|
|
GdkPixbuf *temp_pixbuf;
|
|
|
|
pixbuf = NULL;
|
|
|
|
pixbuf = src;
|
|
|
|
if (gdk_pixbuf_get_width (pixbuf) == width &&
|
|
gdk_pixbuf_get_height (pixbuf) == height)
|
|
{
|
|
g_object_ref (G_OBJECT (pixbuf));
|
|
}
|
|
else
|
|
{
|
|
if (fill_type == META_IMAGE_FILL_TILE)
|
|
{
|
|
pixbuf = pixbuf_tile (pixbuf, width, height);
|
|
}
|
|
else
|
|
{
|
|
int src_h, src_w, dest_h, dest_w;
|
|
src_h = gdk_pixbuf_get_height (src);
|
|
src_w = gdk_pixbuf_get_width (src);
|
|
|
|
/* prefer to replicate_cols if possible, as that
|
|
* is faster (no memory reads)
|
|
*/
|
|
if (horizontal_stripes)
|
|
{
|
|
dest_w = gdk_pixbuf_get_width (src);
|
|
dest_h = height;
|
|
}
|
|
else if (vertical_stripes)
|
|
{
|
|
dest_w = width;
|
|
dest_h = gdk_pixbuf_get_height (src);
|
|
}
|
|
|
|
else
|
|
{
|
|
dest_w = width;
|
|
dest_h = height;
|
|
}
|
|
|
|
if (dest_w == src_w && dest_h == src_h)
|
|
{
|
|
temp_pixbuf = src;
|
|
g_object_ref (G_OBJECT (temp_pixbuf));
|
|
}
|
|
else
|
|
{
|
|
temp_pixbuf = gdk_pixbuf_scale_simple (src,
|
|
dest_w, dest_h,
|
|
GDK_INTERP_BILINEAR);
|
|
}
|
|
|
|
/* prefer to replicate_cols if possible, as that
|
|
* is faster (no memory reads)
|
|
*/
|
|
if (horizontal_stripes)
|
|
{
|
|
pixbuf = replicate_cols (temp_pixbuf, 0, 0, width, height);
|
|
g_object_unref (G_OBJECT (temp_pixbuf));
|
|
}
|
|
else if (vertical_stripes)
|
|
{
|
|
pixbuf = replicate_rows (temp_pixbuf, 0, 0, width, height);
|
|
g_object_unref (G_OBJECT (temp_pixbuf));
|
|
}
|
|
else
|
|
{
|
|
pixbuf = temp_pixbuf;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pixbuf)
|
|
pixbuf = apply_alpha (pixbuf, alpha_spec, pixbuf == src);
|
|
|
|
return pixbuf;
|
|
}
|
|
|
|
static GdkPixbuf*
|
|
draw_op_as_pixbuf (const MetaDrawOp *op,
|
|
GtkWidget *widget,
|
|
const MetaDrawInfo *info,
|
|
int width,
|
|
int height)
|
|
{
|
|
/* Try to get the op as a pixbuf, assuming w/h in the op
|
|
* matches the width/height passed in. return NULL
|
|
* if the op can't be converted to an equivalent pixbuf.
|
|
*/
|
|
GdkPixbuf *pixbuf;
|
|
|
|
pixbuf = NULL;
|
|
|
|
switch (op->type)
|
|
{
|
|
case META_DRAW_LINE:
|
|
break;
|
|
|
|
case META_DRAW_RECTANGLE:
|
|
if (op->data.rectangle.filled)
|
|
{
|
|
GdkColor color;
|
|
|
|
meta_color_spec_render (op->data.rectangle.color_spec,
|
|
widget,
|
|
&color);
|
|
|
|
pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
|
|
FALSE,
|
|
8, width, height);
|
|
|
|
gdk_pixbuf_fill (pixbuf, GDK_COLOR_RGBA (color));
|
|
}
|
|
break;
|
|
|
|
case META_DRAW_ARC:
|
|
break;
|
|
|
|
case META_DRAW_CLIP:
|
|
break;
|
|
|
|
case META_DRAW_TINT:
|
|
{
|
|
GdkColor color;
|
|
guint32 rgba;
|
|
gboolean has_alpha;
|
|
|
|
meta_color_spec_render (op->data.rectangle.color_spec,
|
|
widget,
|
|
&color);
|
|
|
|
has_alpha =
|
|
op->data.tint.alpha_spec &&
|
|
(op->data.tint.alpha_spec->n_alphas > 1 ||
|
|
op->data.tint.alpha_spec->alphas[0] != 0xff);
|
|
|
|
pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
|
|
has_alpha,
|
|
8, width, height);
|
|
|
|
if (!has_alpha)
|
|
{
|
|
rgba = GDK_COLOR_RGBA (color);
|
|
|
|
gdk_pixbuf_fill (pixbuf, rgba);
|
|
}
|
|
else if (op->data.tint.alpha_spec->n_alphas == 1)
|
|
{
|
|
rgba = GDK_COLOR_RGBA (color);
|
|
rgba &= ~0xff;
|
|
rgba |= op->data.tint.alpha_spec->alphas[0];
|
|
|
|
gdk_pixbuf_fill (pixbuf, rgba);
|
|
}
|
|
else
|
|
{
|
|
rgba = GDK_COLOR_RGBA (color);
|
|
|
|
gdk_pixbuf_fill (pixbuf, rgba);
|
|
|
|
meta_gradient_add_alpha (pixbuf,
|
|
op->data.tint.alpha_spec->alphas,
|
|
op->data.tint.alpha_spec->n_alphas,
|
|
op->data.tint.alpha_spec->type);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case META_DRAW_GRADIENT:
|
|
{
|
|
pixbuf = meta_gradient_spec_render (op->data.gradient.gradient_spec,
|
|
widget, width, height);
|
|
|
|
pixbuf = apply_alpha (pixbuf,
|
|
op->data.gradient.alpha_spec,
|
|
FALSE);
|
|
}
|
|
break;
|
|
|
|
|
|
case META_DRAW_IMAGE:
|
|
{
|
|
if (op->data.image.colorize_spec)
|
|
{
|
|
GdkColor color;
|
|
|
|
meta_color_spec_render (op->data.image.colorize_spec,
|
|
widget, &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)
|
|
{
|
|
pixbuf = scale_and_alpha_pixbuf (op->data.image.colorize_cache_pixbuf,
|
|
op->data.image.alpha_spec,
|
|
op->data.image.fill_type,
|
|
width, height,
|
|
op->data.image.vertical_stripes,
|
|
op->data.image.horizontal_stripes);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pixbuf = scale_and_alpha_pixbuf (op->data.image.pixbuf,
|
|
op->data.image.alpha_spec,
|
|
op->data.image.fill_type,
|
|
width, height,
|
|
op->data.image.vertical_stripes,
|
|
op->data.image.horizontal_stripes);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case META_DRAW_GTK_ARROW:
|
|
case META_DRAW_GTK_BOX:
|
|
case META_DRAW_GTK_VLINE:
|
|
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))
|
|
pixbuf = scale_and_alpha_pixbuf (info->mini_icon,
|
|
op->data.icon.alpha_spec,
|
|
op->data.icon.fill_type,
|
|
width, height,
|
|
FALSE, FALSE);
|
|
else if (info->icon)
|
|
pixbuf = scale_and_alpha_pixbuf (info->icon,
|
|
op->data.icon.alpha_spec,
|
|
op->data.icon.fill_type,
|
|
width, height,
|
|
FALSE, FALSE);
|
|
break;
|
|
|
|
case META_DRAW_TITLE:
|
|
break;
|
|
|
|
case META_DRAW_OP_LIST:
|
|
break;
|
|
|
|
case META_DRAW_TILE:
|
|
break;
|
|
}
|
|
|
|
return pixbuf;
|
|
}
|
|
|
|
static void
|
|
fill_env (MetaPositionExprEnv *env,
|
|
const MetaDrawInfo *info,
|
|
MetaRectangle logical_region)
|
|
{
|
|
/* FIXME this stuff could be raised into draw_op_list_draw() probably
|
|
*/
|
|
env->rect = logical_region;
|
|
env->object_width = -1;
|
|
env->object_height = -1;
|
|
if (info->fgeom)
|
|
{
|
|
env->left_width = info->fgeom->left_width;
|
|
env->right_width = info->fgeom->right_width;
|
|
env->top_height = info->fgeom->top_height;
|
|
env->bottom_height = info->fgeom->bottom_height;
|
|
}
|
|
else
|
|
{
|
|
env->left_width = 0;
|
|
env->right_width = 0;
|
|
env->top_height = 0;
|
|
env->bottom_height = 0;
|
|
}
|
|
|
|
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->theme = NULL; /* not required, constants have been optimized out */
|
|
}
|
|
|
|
static void
|
|
meta_draw_op_draw_with_env (const MetaDrawOp *op,
|
|
GtkWidget *widget,
|
|
GdkDrawable *drawable,
|
|
const GdkRectangle *clip,
|
|
const MetaDrawInfo *info,
|
|
MetaRectangle rect,
|
|
MetaPositionExprEnv *env)
|
|
{
|
|
GdkGC *gc;
|
|
|
|
switch (op->type)
|
|
{
|
|
case META_DRAW_LINE:
|
|
{
|
|
int x1, x2, y1, y2;
|
|
|
|
gc = get_gc_for_primitive (widget, drawable,
|
|
op->data.line.color_spec,
|
|
clip,
|
|
op->data.line.width);
|
|
|
|
if (op->data.line.dash_on_length > 0 &&
|
|
op->data.line.dash_off_length > 0)
|
|
{
|
|
gint8 dash_list[2];
|
|
dash_list[0] = op->data.line.dash_on_length;
|
|
dash_list[1] = op->data.line.dash_off_length;
|
|
gdk_gc_set_dashes (gc, 0, dash_list, 2);
|
|
}
|
|
|
|
x1 = parse_x_position_unchecked (op->data.line.x1, env);
|
|
y1 = parse_y_position_unchecked (op->data.line.y1, env);
|
|
x2 = parse_x_position_unchecked (op->data.line.x2, env);
|
|
y2 = parse_y_position_unchecked (op->data.line.y2, env);
|
|
|
|
gdk_draw_line (drawable, gc, x1, y1, x2, y2);
|
|
|
|
g_object_unref (G_OBJECT (gc));
|
|
}
|
|
break;
|
|
|
|
case META_DRAW_RECTANGLE:
|
|
{
|
|
int rx, ry, rwidth, rheight;
|
|
|
|
gc = get_gc_for_primitive (widget, drawable,
|
|
op->data.rectangle.color_spec,
|
|
clip, 0);
|
|
|
|
rx = parse_x_position_unchecked (op->data.rectangle.x, env);
|
|
ry = parse_y_position_unchecked (op->data.rectangle.y, env);
|
|
rwidth = parse_size_unchecked (op->data.rectangle.width, env);
|
|
rheight = parse_size_unchecked (op->data.rectangle.height, env);
|
|
|
|
gdk_draw_rectangle (drawable, gc,
|
|
op->data.rectangle.filled,
|
|
rx, ry, rwidth, rheight);
|
|
|
|
g_object_unref (G_OBJECT (gc));
|
|
}
|
|
break;
|
|
|
|
case META_DRAW_ARC:
|
|
{
|
|
int rx, ry, rwidth, rheight;
|
|
|
|
gc = get_gc_for_primitive (widget, drawable,
|
|
op->data.arc.color_spec,
|
|
clip, 0);
|
|
|
|
rx = parse_x_position_unchecked (op->data.arc.x, env);
|
|
ry = parse_y_position_unchecked (op->data.arc.y, env);
|
|
rwidth = parse_size_unchecked (op->data.arc.width, env);
|
|
rheight = parse_size_unchecked (op->data.arc.height, env);
|
|
|
|
gdk_draw_arc (drawable,
|
|
gc,
|
|
op->data.arc.filled,
|
|
rx, ry, rwidth, rheight,
|
|
op->data.arc.start_angle * (360.0 * 64.0) -
|
|
(90.0 * 64.0), /* start at 12 instead of 3 oclock */
|
|
op->data.arc.extent_angle * (360.0 * 64.0));
|
|
|
|
g_object_unref (G_OBJECT (gc));
|
|
}
|
|
break;
|
|
|
|
case META_DRAW_CLIP:
|
|
break;
|
|
|
|
case META_DRAW_TINT:
|
|
{
|
|
int rx, ry, rwidth, rheight;
|
|
gboolean needs_alpha;
|
|
|
|
needs_alpha = op->data.tint.alpha_spec &&
|
|
(op->data.tint.alpha_spec->n_alphas > 1 ||
|
|
op->data.tint.alpha_spec->alphas[0] != 0xff);
|
|
|
|
rx = parse_x_position_unchecked (op->data.tint.x, env);
|
|
ry = parse_y_position_unchecked (op->data.tint.y, env);
|
|
rwidth = parse_size_unchecked (op->data.tint.width, env);
|
|
rheight = parse_size_unchecked (op->data.tint.height, env);
|
|
|
|
if (!needs_alpha)
|
|
{
|
|
gc = get_gc_for_primitive (widget, drawable,
|
|
op->data.tint.color_spec,
|
|
clip, 0);
|
|
|
|
gdk_draw_rectangle (drawable, gc,
|
|
TRUE,
|
|
rx, ry, rwidth, rheight);
|
|
|
|
g_object_unref (G_OBJECT (gc));
|
|
}
|
|
else
|
|
{
|
|
GdkPixbuf *pixbuf;
|
|
|
|
pixbuf = draw_op_as_pixbuf (op, widget, info,
|
|
rwidth, rheight);
|
|
|
|
if (pixbuf)
|
|
{
|
|
render_pixbuf (drawable, clip, pixbuf, rx, ry);
|
|
|
|
g_object_unref (G_OBJECT (pixbuf));
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case META_DRAW_GRADIENT:
|
|
{
|
|
int rx, ry, rwidth, rheight;
|
|
GdkPixbuf *pixbuf;
|
|
|
|
rx = parse_x_position_unchecked (op->data.gradient.x, env);
|
|
ry = parse_y_position_unchecked (op->data.gradient.y, env);
|
|
rwidth = parse_size_unchecked (op->data.gradient.width, env);
|
|
rheight = parse_size_unchecked (op->data.gradient.height, env);
|
|
|
|
pixbuf = draw_op_as_pixbuf (op, widget, info,
|
|
rwidth, rheight);
|
|
|
|
if (pixbuf)
|
|
{
|
|
render_pixbuf (drawable, clip, pixbuf, rx, ry);
|
|
|
|
g_object_unref (G_OBJECT (pixbuf));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case META_DRAW_IMAGE:
|
|
{
|
|
int rx, ry, rwidth, rheight;
|
|
GdkPixbuf *pixbuf;
|
|
|
|
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 = parse_size_unchecked (op->data.image.width, env);
|
|
rheight = parse_size_unchecked (op->data.image.height, env);
|
|
|
|
pixbuf = draw_op_as_pixbuf (op, widget, info,
|
|
rwidth, rheight);
|
|
|
|
if (pixbuf)
|
|
{
|
|
rx = parse_x_position_unchecked (op->data.image.x, env);
|
|
ry = parse_y_position_unchecked (op->data.image.y, env);
|
|
|
|
render_pixbuf (drawable, clip, pixbuf, rx, ry);
|
|
|
|
g_object_unref (G_OBJECT (pixbuf));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case META_DRAW_GTK_ARROW:
|
|
{
|
|
int rx, ry, rwidth, rheight;
|
|
|
|
rx = parse_x_position_unchecked (op->data.gtk_arrow.x, env);
|
|
ry = parse_y_position_unchecked (op->data.gtk_arrow.y, env);
|
|
rwidth = parse_size_unchecked (op->data.gtk_arrow.width, env);
|
|
rheight = parse_size_unchecked (op->data.gtk_arrow.height, env);
|
|
|
|
gtk_paint_arrow (widget->style,
|
|
drawable,
|
|
op->data.gtk_arrow.state,
|
|
op->data.gtk_arrow.shadow,
|
|
(GdkRectangle*) clip,
|
|
widget,
|
|
"metacity",
|
|
op->data.gtk_arrow.arrow,
|
|
op->data.gtk_arrow.filled,
|
|
rx, ry, rwidth, rheight);
|
|
}
|
|
break;
|
|
|
|
case META_DRAW_GTK_BOX:
|
|
{
|
|
int rx, ry, rwidth, rheight;
|
|
|
|
rx = parse_x_position_unchecked (op->data.gtk_box.x, env);
|
|
ry = parse_y_position_unchecked (op->data.gtk_box.y, env);
|
|
rwidth = parse_size_unchecked (op->data.gtk_box.width, env);
|
|
rheight = parse_size_unchecked (op->data.gtk_box.height, env);
|
|
|
|
gtk_paint_box (widget->style,
|
|
drawable,
|
|
op->data.gtk_box.state,
|
|
op->data.gtk_box.shadow,
|
|
(GdkRectangle*) clip,
|
|
widget,
|
|
"metacity",
|
|
rx, ry, rwidth, rheight);
|
|
}
|
|
break;
|
|
|
|
case META_DRAW_GTK_VLINE:
|
|
{
|
|
int rx, ry1, ry2;
|
|
|
|
rx = parse_x_position_unchecked (op->data.gtk_vline.x, env);
|
|
ry1 = parse_y_position_unchecked (op->data.gtk_vline.y1, env);
|
|
ry2 = parse_y_position_unchecked (op->data.gtk_vline.y2, env);
|
|
|
|
gtk_paint_vline (widget->style,
|
|
drawable,
|
|
op->data.gtk_vline.state,
|
|
(GdkRectangle*) clip,
|
|
widget,
|
|
"metacity",
|
|
ry1, ry2, rx);
|
|
}
|
|
break;
|
|
|
|
case META_DRAW_ICON:
|
|
{
|
|
int rx, ry, rwidth, rheight;
|
|
GdkPixbuf *pixbuf;
|
|
|
|
rwidth = parse_size_unchecked (op->data.icon.width, env);
|
|
rheight = parse_size_unchecked (op->data.icon.height, env);
|
|
|
|
pixbuf = draw_op_as_pixbuf (op, widget, info,
|
|
rwidth, rheight);
|
|
|
|
if (pixbuf)
|
|
{
|
|
rx = parse_x_position_unchecked (op->data.icon.x, env);
|
|
ry = parse_y_position_unchecked (op->data.icon.y, env);
|
|
|
|
render_pixbuf (drawable, clip, pixbuf, rx, ry);
|
|
|
|
g_object_unref (G_OBJECT (pixbuf));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case META_DRAW_TITLE:
|
|
if (info->title_layout)
|
|
{
|
|
int rx, ry;
|
|
|
|
gc = get_gc_for_primitive (widget, drawable,
|
|
op->data.title.color_spec,
|
|
clip, 0);
|
|
|
|
rx = parse_x_position_unchecked (op->data.title.x, env);
|
|
ry = parse_y_position_unchecked (op->data.title.y, env);
|
|
|
|
gdk_draw_layout (drawable, gc,
|
|
rx, ry,
|
|
info->title_layout);
|
|
|
|
g_object_unref (G_OBJECT (gc));
|
|
}
|
|
break;
|
|
|
|
case META_DRAW_OP_LIST:
|
|
{
|
|
MetaRectangle d_rect;
|
|
|
|
d_rect.x = parse_x_position_unchecked (op->data.op_list.x, env);
|
|
d_rect.y = parse_y_position_unchecked (op->data.op_list.y, env);
|
|
d_rect.width = parse_size_unchecked (op->data.op_list.width, env);
|
|
d_rect.height = parse_size_unchecked (op->data.op_list.height, env);
|
|
|
|
meta_draw_op_list_draw (op->data.op_list.op_list,
|
|
widget, drawable, clip, info,
|
|
d_rect);
|
|
}
|
|
break;
|
|
|
|
case META_DRAW_TILE:
|
|
{
|
|
int rx, ry, rwidth, rheight;
|
|
int tile_xoffset, tile_yoffset;
|
|
GdkRectangle new_clip;
|
|
MetaRectangle tile;
|
|
|
|
rx = parse_x_position_unchecked (op->data.tile.x, env);
|
|
ry = parse_y_position_unchecked (op->data.tile.y, env);
|
|
rwidth = parse_size_unchecked (op->data.tile.width, env);
|
|
rheight = parse_size_unchecked (op->data.tile.height, env);
|
|
|
|
new_clip.x = rx;
|
|
new_clip.y = ry;
|
|
new_clip.width = rwidth;
|
|
new_clip.height = rheight;
|
|
|
|
if (clip == NULL || gdk_rectangle_intersect ((GdkRectangle*)clip, &new_clip,
|
|
&new_clip))
|
|
{
|
|
tile_xoffset = parse_x_position_unchecked (op->data.tile.tile_xoffset, env);
|
|
tile_yoffset = parse_y_position_unchecked (op->data.tile.tile_yoffset, env);
|
|
/* tile offset should not include x/y */
|
|
tile_xoffset -= rect.x;
|
|
tile_yoffset -= rect.y;
|
|
|
|
tile.width = parse_size_unchecked (op->data.tile.tile_width, env);
|
|
tile.height = parse_size_unchecked (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 (op->data.tile.op_list,
|
|
widget, drawable, &new_clip, info,
|
|
tile);
|
|
|
|
tile.y += tile.height;
|
|
}
|
|
|
|
tile.x += tile.width;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
meta_draw_op_draw (const MetaDrawOp *op,
|
|
GtkWidget *widget,
|
|
GdkDrawable *drawable,
|
|
const GdkRectangle *clip,
|
|
const MetaDrawInfo *info,
|
|
MetaRectangle logical_region)
|
|
{
|
|
MetaPositionExprEnv env;
|
|
|
|
fill_env (&env, info, logical_region);
|
|
|
|
meta_draw_op_draw_with_env (op, widget, drawable, clip,
|
|
info, logical_region,
|
|
&env);
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
DEBUG_FILL_STRUCT (op_list);
|
|
g_free (op_list);
|
|
}
|
|
}
|
|
|
|
void
|
|
meta_draw_op_list_draw (const MetaDrawOpList *op_list,
|
|
GtkWidget *widget,
|
|
GdkDrawable *drawable,
|
|
const GdkRectangle *clip,
|
|
const MetaDrawInfo *info,
|
|
MetaRectangle rect)
|
|
{
|
|
int i;
|
|
GdkRectangle active_clip;
|
|
GdkRectangle orig_clip;
|
|
MetaPositionExprEnv env;
|
|
|
|
if (op_list->n_ops == 0)
|
|
return;
|
|
|
|
fill_env (&env, info, rect);
|
|
|
|
/* FIXME this can be optimized, potentially a lot, by
|
|
* compressing multiple ops when possible. For example,
|
|
* anything convertible to a pixbuf can be composited
|
|
* client-side, and putting a color tint over a pixbuf
|
|
* can be done without creating the solid-color pixbuf.
|
|
*
|
|
* To implement this my plan is to have the idea of a
|
|
* compiled draw op (with the string expressions already
|
|
* evaluated), we make an array of those, and then fold
|
|
* adjacent items when possible.
|
|
*/
|
|
if (clip)
|
|
{
|
|
orig_clip = *clip;
|
|
}
|
|
else
|
|
{
|
|
orig_clip.x = rect.x;
|
|
orig_clip.y = rect.y;
|
|
orig_clip.width = rect.width;
|
|
orig_clip.height = rect.height;
|
|
}
|
|
|
|
active_clip = orig_clip;
|
|
|
|
for (i = 0; i < op_list->n_ops; i++)
|
|
{
|
|
MetaDrawOp *op = op_list->ops[i];
|
|
|
|
if (op->type == META_DRAW_CLIP)
|
|
{
|
|
active_clip.x = parse_x_position_unchecked (op->data.clip.x, &env);
|
|
active_clip.y = parse_y_position_unchecked (op->data.clip.y, &env);
|
|
active_clip.width = parse_size_unchecked (op->data.clip.width, &env);
|
|
active_clip.height = parse_size_unchecked (op->data.clip.height, &env);
|
|
|
|
gdk_rectangle_intersect (&orig_clip, &active_clip, &active_clip);
|
|
}
|
|
else if (active_clip.width > 0 &&
|
|
active_clip.height > 0)
|
|
{
|
|
meta_draw_op_draw_with_env (op,
|
|
widget, drawable, &active_clip, info,
|
|
rect,
|
|
&env);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
MetaFrameStyle*
|
|
meta_frame_style_new (MetaFrameStyle *parent)
|
|
{
|
|
MetaFrameStyle *style;
|
|
|
|
style = g_new0 (MetaFrameStyle, 1);
|
|
|
|
style->refcount = 1;
|
|
|
|
/* Default alpha is fully opaque */
|
|
style->window_background_alpha = 255;
|
|
|
|
style->parent = parent;
|
|
if (parent)
|
|
meta_frame_style_ref (parent);
|
|
|
|
return style;
|
|
}
|
|
|
|
void
|
|
meta_frame_style_ref (MetaFrameStyle *style)
|
|
{
|
|
g_return_if_fail (style != NULL);
|
|
|
|
style->refcount += 1;
|
|
}
|
|
|
|
static void
|
|
free_button_ops (MetaDrawOpList *op_lists[META_BUTTON_TYPE_LAST][META_BUTTON_STATE_LAST])
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 0; i < META_BUTTON_TYPE_LAST; i++)
|
|
for (j = 0; j < META_BUTTON_STATE_LAST; j++)
|
|
if (op_lists[i][j])
|
|
meta_draw_op_list_unref (op_lists[i][j]);
|
|
}
|
|
|
|
void
|
|
meta_frame_style_unref (MetaFrameStyle *style)
|
|
{
|
|
g_return_if_fail (style != NULL);
|
|
g_return_if_fail (style->refcount > 0);
|
|
|
|
style->refcount -= 1;
|
|
|
|
if (style->refcount == 0)
|
|
{
|
|
int i;
|
|
|
|
free_button_ops (style->buttons);
|
|
|
|
for (i = 0; i < META_FRAME_PIECE_LAST; i++)
|
|
if (style->pieces[i])
|
|
meta_draw_op_list_unref (style->pieces[i]);
|
|
|
|
if (style->layout)
|
|
meta_frame_layout_unref (style->layout);
|
|
|
|
if (style->window_background_color)
|
|
meta_color_spec_free (style->window_background_color);
|
|
|
|
/* we hold a reference to any parent style */
|
|
if (style->parent)
|
|
meta_frame_style_unref (style->parent);
|
|
|
|
DEBUG_FILL_STRUCT (style);
|
|
g_free (style);
|
|
}
|
|
}
|
|
|
|
static MetaDrawOpList*
|
|
get_button (MetaFrameStyle *style,
|
|
MetaButtonType type,
|
|
MetaButtonState state)
|
|
{
|
|
MetaDrawOpList *op_list;
|
|
MetaFrameStyle *parent;
|
|
|
|
parent = style;
|
|
op_list = NULL;
|
|
while (parent && op_list == NULL)
|
|
{
|
|
op_list = parent->buttons[type][state];
|
|
parent = parent->parent;
|
|
}
|
|
|
|
/* We fall back to normal if no prelight */
|
|
if (op_list == NULL &&
|
|
state == META_BUTTON_STATE_PRELIGHT)
|
|
return get_button (style, type, META_BUTTON_STATE_NORMAL);
|
|
|
|
/* We fall back to middle button backgrounds if we don't
|
|
* have the ones on the sides
|
|
*/
|
|
|
|
if (op_list == NULL &&
|
|
(type == META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND ||
|
|
type == META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND))
|
|
return get_button (style, META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND,
|
|
state);
|
|
|
|
if (op_list == NULL &&
|
|
(type == META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND ||
|
|
type == META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND))
|
|
return get_button (style, META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND,
|
|
state);
|
|
|
|
return op_list;
|
|
}
|
|
|
|
gboolean
|
|
meta_frame_style_validate (MetaFrameStyle *style,
|
|
guint current_theme_version,
|
|
GError **error)
|
|
{
|
|
int i, j;
|
|
|
|
g_return_val_if_fail (style != NULL, FALSE);
|
|
g_return_val_if_fail (style->layout != NULL, FALSE);
|
|
|
|
for (i = 0; i < META_BUTTON_TYPE_LAST; i++)
|
|
{
|
|
/* for now the "positional" buttons are optional */
|
|
if (i >= META_BUTTON_TYPE_CLOSE)
|
|
{
|
|
for (j = 0; j < META_BUTTON_STATE_LAST; j++)
|
|
{
|
|
if (get_button (style, i, j) == NULL &&
|
|
meta_theme_earliest_version_with_button (i) <= current_theme_version
|
|
)
|
|
{
|
|
g_set_error (error, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("<button function=\"%s\" state=\"%s\" draw_ops=\"whatever\"/> must be specified for this frame style"),
|
|
meta_button_type_to_string (i),
|
|
meta_button_state_to_string (j));
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
button_rect (MetaButtonType type,
|
|
const MetaFrameGeometry *fgeom,
|
|
int middle_background_offset,
|
|
GdkRectangle *rect)
|
|
{
|
|
switch (type)
|
|
{
|
|
case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND:
|
|
*rect = fgeom->left_left_background;
|
|
break;
|
|
|
|
case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND:
|
|
*rect = fgeom->left_middle_backgrounds[middle_background_offset];
|
|
break;
|
|
|
|
case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND:
|
|
*rect = fgeom->left_right_background;
|
|
break;
|
|
|
|
case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND:
|
|
*rect = fgeom->right_left_background;
|
|
break;
|
|
|
|
case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND:
|
|
*rect = fgeom->right_middle_backgrounds[middle_background_offset];
|
|
break;
|
|
|
|
case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND:
|
|
*rect = fgeom->right_right_background;
|
|
break;
|
|
|
|
case META_BUTTON_TYPE_CLOSE:
|
|
*rect = fgeom->close_rect.visible;
|
|
break;
|
|
|
|
case META_BUTTON_TYPE_SHADE:
|
|
*rect = fgeom->shade_rect.visible;
|
|
break;
|
|
|
|
case META_BUTTON_TYPE_UNSHADE:
|
|
*rect = fgeom->unshade_rect.visible;
|
|
break;
|
|
|
|
case META_BUTTON_TYPE_ABOVE:
|
|
*rect = fgeom->above_rect.visible;
|
|
break;
|
|
|
|
case META_BUTTON_TYPE_UNABOVE:
|
|
*rect = fgeom->unabove_rect.visible;
|
|
break;
|
|
|
|
case META_BUTTON_TYPE_STICK:
|
|
*rect = fgeom->stick_rect.visible;
|
|
break;
|
|
|
|
case META_BUTTON_TYPE_UNSTICK:
|
|
*rect = fgeom->unstick_rect.visible;
|
|
break;
|
|
|
|
case META_BUTTON_TYPE_MAXIMIZE:
|
|
*rect = fgeom->max_rect.visible;
|
|
break;
|
|
|
|
case META_BUTTON_TYPE_MINIMIZE:
|
|
*rect = fgeom->min_rect.visible;
|
|
break;
|
|
|
|
case META_BUTTON_TYPE_MENU:
|
|
*rect = fgeom->menu_rect.visible;
|
|
break;
|
|
|
|
case META_BUTTON_TYPE_LAST:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
meta_frame_style_draw (MetaFrameStyle *style,
|
|
GtkWidget *widget,
|
|
GdkDrawable *drawable,
|
|
int x_offset,
|
|
int y_offset,
|
|
const GdkRectangle *clip,
|
|
const MetaFrameGeometry *fgeom,
|
|
int client_width,
|
|
int client_height,
|
|
PangoLayout *title_layout,
|
|
int text_height,
|
|
MetaButtonState button_states[META_BUTTON_TYPE_LAST],
|
|
GdkPixbuf *mini_icon,
|
|
GdkPixbuf *icon)
|
|
{
|
|
int i, j;
|
|
GdkRectangle titlebar_rect;
|
|
GdkRectangle left_titlebar_edge;
|
|
GdkRectangle right_titlebar_edge;
|
|
GdkRectangle bottom_titlebar_edge;
|
|
GdkRectangle top_titlebar_edge;
|
|
GdkRectangle left_edge, right_edge, bottom_edge;
|
|
PangoRectangle extents;
|
|
MetaDrawInfo draw_info;
|
|
|
|
titlebar_rect.x = 0;
|
|
titlebar_rect.y = 0;
|
|
titlebar_rect.width = fgeom->width;
|
|
titlebar_rect.height = fgeom->top_height;
|
|
|
|
left_titlebar_edge.x = titlebar_rect.x;
|
|
left_titlebar_edge.y = titlebar_rect.y + fgeom->top_titlebar_edge;
|
|
left_titlebar_edge.width = fgeom->left_titlebar_edge;
|
|
left_titlebar_edge.height = titlebar_rect.height - fgeom->top_titlebar_edge - fgeom->bottom_titlebar_edge;
|
|
|
|
right_titlebar_edge.y = left_titlebar_edge.y;
|
|
right_titlebar_edge.height = left_titlebar_edge.height;
|
|
right_titlebar_edge.width = fgeom->right_titlebar_edge;
|
|
right_titlebar_edge.x = titlebar_rect.x + titlebar_rect.width - right_titlebar_edge.width;
|
|
|
|
top_titlebar_edge.x = titlebar_rect.x;
|
|
top_titlebar_edge.y = titlebar_rect.y;
|
|
top_titlebar_edge.width = titlebar_rect.width;
|
|
top_titlebar_edge.height = fgeom->top_titlebar_edge;
|
|
|
|
bottom_titlebar_edge.x = titlebar_rect.x;
|
|
bottom_titlebar_edge.width = titlebar_rect.width;
|
|
bottom_titlebar_edge.height = fgeom->bottom_titlebar_edge;
|
|
bottom_titlebar_edge.y = titlebar_rect.y + titlebar_rect.height - bottom_titlebar_edge.height;
|
|
|
|
left_edge.x = 0;
|
|
left_edge.y = fgeom->top_height;
|
|
left_edge.width = fgeom->left_width;
|
|
left_edge.height = fgeom->height - fgeom->top_height - fgeom->bottom_height;
|
|
|
|
right_edge.x = fgeom->width - fgeom->right_width;
|
|
right_edge.y = fgeom->top_height;
|
|
right_edge.width = fgeom->right_width;
|
|
right_edge.height = fgeom->height - fgeom->top_height - fgeom->bottom_height;
|
|
|
|
bottom_edge.x = 0;
|
|
bottom_edge.y = fgeom->height - fgeom->bottom_height;
|
|
bottom_edge.width = fgeom->width;
|
|
bottom_edge.height = fgeom->bottom_height;
|
|
|
|
if (title_layout)
|
|
pango_layout_get_pixel_extents (title_layout,
|
|
NULL, &extents);
|
|
|
|
draw_info.mini_icon = mini_icon;
|
|
draw_info.icon = icon;
|
|
draw_info.title_layout = title_layout;
|
|
draw_info.title_layout_width = title_layout ? extents.width : 0;
|
|
draw_info.title_layout_height = title_layout ? extents.height : 0;
|
|
draw_info.fgeom = fgeom;
|
|
|
|
/* The enum is in the order the pieces should be rendered. */
|
|
i = 0;
|
|
while (i < META_FRAME_PIECE_LAST)
|
|
{
|
|
GdkRectangle rect;
|
|
GdkRectangle combined_clip;
|
|
|
|
switch ((MetaFramePiece) i)
|
|
{
|
|
case META_FRAME_PIECE_ENTIRE_BACKGROUND:
|
|
rect.x = 0;
|
|
rect.y = 0;
|
|
rect.width = fgeom->width;
|
|
rect.height = fgeom->height;
|
|
break;
|
|
|
|
case META_FRAME_PIECE_TITLEBAR:
|
|
rect = titlebar_rect;
|
|
break;
|
|
|
|
case META_FRAME_PIECE_LEFT_TITLEBAR_EDGE:
|
|
rect = left_titlebar_edge;
|
|
break;
|
|
|
|
case META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE:
|
|
rect = right_titlebar_edge;
|
|
break;
|
|
|
|
case META_FRAME_PIECE_TOP_TITLEBAR_EDGE:
|
|
rect = top_titlebar_edge;
|
|
break;
|
|
|
|
case META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE:
|
|
rect = bottom_titlebar_edge;
|
|
break;
|
|
|
|
case META_FRAME_PIECE_TITLEBAR_MIDDLE:
|
|
rect.x = left_titlebar_edge.x + left_titlebar_edge.width;
|
|
rect.y = top_titlebar_edge.y + top_titlebar_edge.height;
|
|
rect.width = titlebar_rect.width - left_titlebar_edge.width -
|
|
right_titlebar_edge.width;
|
|
rect.height = titlebar_rect.height - top_titlebar_edge.height - bottom_titlebar_edge.height;
|
|
break;
|
|
|
|
case META_FRAME_PIECE_TITLE:
|
|
rect = fgeom->title_rect;
|
|
break;
|
|
|
|
case META_FRAME_PIECE_LEFT_EDGE:
|
|
rect = left_edge;
|
|
break;
|
|
|
|
case META_FRAME_PIECE_RIGHT_EDGE:
|
|
rect = right_edge;
|
|
break;
|
|
|
|
case META_FRAME_PIECE_BOTTOM_EDGE:
|
|
rect = bottom_edge;
|
|
break;
|
|
|
|
case META_FRAME_PIECE_OVERLAY:
|
|
rect.x = 0;
|
|
rect.y = 0;
|
|
rect.width = fgeom->width;
|
|
rect.height = fgeom->height;
|
|
break;
|
|
|
|
case META_FRAME_PIECE_LAST:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
rect.x += x_offset;
|
|
rect.y += y_offset;
|
|
|
|
if (clip == NULL)
|
|
combined_clip = rect;
|
|
else
|
|
gdk_rectangle_intersect ((GdkRectangle*) clip, /* const cast */
|
|
&rect,
|
|
&combined_clip);
|
|
|
|
if (combined_clip.width > 0 && combined_clip.height > 0)
|
|
{
|
|
MetaDrawOpList *op_list;
|
|
MetaFrameStyle *parent;
|
|
|
|
parent = style;
|
|
op_list = NULL;
|
|
while (parent && op_list == NULL)
|
|
{
|
|
op_list = parent->pieces[i];
|
|
parent = parent->parent;
|
|
}
|
|
|
|
if (op_list)
|
|
{
|
|
MetaRectangle m_rect;
|
|
m_rect = meta_rect (rect.x, rect.y, rect.width, rect.height);
|
|
meta_draw_op_list_draw (op_list,
|
|
widget,
|
|
drawable,
|
|
&combined_clip,
|
|
&draw_info,
|
|
m_rect);
|
|
}
|
|
}
|
|
|
|
|
|
/* Draw buttons just before overlay */
|
|
if ((i + 1) == META_FRAME_PIECE_OVERLAY)
|
|
{
|
|
int middle_bg_offset;
|
|
|
|
middle_bg_offset = 0;
|
|
j = 0;
|
|
while (j < META_BUTTON_TYPE_LAST)
|
|
{
|
|
button_rect (j, fgeom, middle_bg_offset, &rect);
|
|
|
|
rect.x += x_offset;
|
|
rect.y += y_offset;
|
|
|
|
if (clip == NULL)
|
|
combined_clip = rect;
|
|
else
|
|
gdk_rectangle_intersect ((GdkRectangle*) clip, /* const cast */
|
|
&rect,
|
|
&combined_clip);
|
|
|
|
if (combined_clip.width > 0 && combined_clip.height > 0)
|
|
{
|
|
MetaDrawOpList *op_list;
|
|
|
|
op_list = get_button (style, j, button_states[j]);
|
|
|
|
if (op_list)
|
|
{
|
|
MetaRectangle m_rect;
|
|
m_rect = meta_rect (rect.x, rect.y,
|
|
rect.width, rect.height);
|
|
meta_draw_op_list_draw (op_list,
|
|
widget,
|
|
drawable,
|
|
&combined_clip,
|
|
&draw_info,
|
|
m_rect);
|
|
}
|
|
}
|
|
|
|
/* MIDDLE_BACKGROUND type may get drawn more than once */
|
|
if ((j == META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND ||
|
|
j == META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND) &&
|
|
middle_bg_offset < MAX_MIDDLE_BACKGROUNDS)
|
|
{
|
|
++middle_bg_offset;
|
|
}
|
|
else
|
|
{
|
|
middle_bg_offset = 0;
|
|
++j;
|
|
}
|
|
}
|
|
}
|
|
|
|
++i;
|
|
}
|
|
}
|
|
|
|
MetaFrameStyleSet*
|
|
meta_frame_style_set_new (MetaFrameStyleSet *parent)
|
|
{
|
|
MetaFrameStyleSet *style_set;
|
|
|
|
style_set = g_new0 (MetaFrameStyleSet, 1);
|
|
|
|
style_set->parent = parent;
|
|
if (parent)
|
|
meta_frame_style_set_ref (parent);
|
|
|
|
style_set->refcount = 1;
|
|
|
|
return style_set;
|
|
}
|
|
|
|
static void
|
|
free_focus_styles (MetaFrameStyle *focus_styles[META_FRAME_FOCUS_LAST])
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < META_FRAME_FOCUS_LAST; i++)
|
|
if (focus_styles[i])
|
|
meta_frame_style_unref (focus_styles[i]);
|
|
}
|
|
|
|
void
|
|
meta_frame_style_set_ref (MetaFrameStyleSet *style_set)
|
|
{
|
|
g_return_if_fail (style_set != NULL);
|
|
|
|
style_set->refcount += 1;
|
|
}
|
|
|
|
void
|
|
meta_frame_style_set_unref (MetaFrameStyleSet *style_set)
|
|
{
|
|
g_return_if_fail (style_set != NULL);
|
|
g_return_if_fail (style_set->refcount > 0);
|
|
|
|
style_set->refcount -= 1;
|
|
|
|
if (style_set->refcount == 0)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < META_FRAME_RESIZE_LAST; i++)
|
|
{
|
|
free_focus_styles (style_set->normal_styles[i]);
|
|
free_focus_styles (style_set->shaded_styles[i]);
|
|
}
|
|
|
|
free_focus_styles (style_set->maximized_styles);
|
|
free_focus_styles (style_set->maximized_and_shaded_styles);
|
|
|
|
if (style_set->parent)
|
|
meta_frame_style_set_unref (style_set->parent);
|
|
|
|
DEBUG_FILL_STRUCT (style_set);
|
|
g_free (style_set);
|
|
}
|
|
}
|
|
|
|
|
|
static MetaFrameStyle*
|
|
get_style (MetaFrameStyleSet *style_set,
|
|
MetaFrameState state,
|
|
MetaFrameResize resize,
|
|
MetaFrameFocus focus)
|
|
{
|
|
MetaFrameStyle *style;
|
|
|
|
style = NULL;
|
|
|
|
switch (state)
|
|
{
|
|
case META_FRAME_STATE_NORMAL:
|
|
case META_FRAME_STATE_SHADED:
|
|
{
|
|
if (state == META_FRAME_STATE_SHADED)
|
|
style = style_set->shaded_styles[resize][focus];
|
|
else
|
|
style = style_set->normal_styles[resize][focus];
|
|
|
|
/* Try parent if we failed here */
|
|
if (style == NULL && style_set->parent)
|
|
style = get_style (style_set->parent, state, resize, focus);
|
|
|
|
/* Allow people to omit the vert/horz/none resize modes */
|
|
if (style == NULL &&
|
|
resize != META_FRAME_RESIZE_BOTH)
|
|
style = get_style (style_set, state, META_FRAME_RESIZE_BOTH, focus);
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
MetaFrameStyle **styles;
|
|
|
|
styles = NULL;
|
|
|
|
switch (state)
|
|
{
|
|
case META_FRAME_STATE_MAXIMIZED:
|
|
styles = style_set->maximized_styles;
|
|
break;
|
|
case META_FRAME_STATE_MAXIMIZED_AND_SHADED:
|
|
styles = style_set->maximized_and_shaded_styles;
|
|
break;
|
|
case META_FRAME_STATE_NORMAL:
|
|
case META_FRAME_STATE_SHADED:
|
|
case META_FRAME_STATE_LAST:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
style = styles[focus];
|
|
|
|
/* Try parent if we failed here */
|
|
if (style == NULL && style_set->parent)
|
|
style = get_style (style_set->parent, state, resize, focus);
|
|
}
|
|
}
|
|
|
|
return style;
|
|
}
|
|
|
|
static gboolean
|
|
check_state (MetaFrameStyleSet *style_set,
|
|
MetaFrameState state,
|
|
GError **error)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < META_FRAME_FOCUS_LAST; i++)
|
|
{
|
|
if (get_style (style_set, state,
|
|
META_FRAME_RESIZE_NONE, i) == NULL)
|
|
{
|
|
g_set_error (error, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Missing <frame state=\"%s\" resize=\"%s\" focus=\"%s\" style=\"whatever\"/>"),
|
|
meta_frame_state_to_string (state),
|
|
meta_frame_resize_to_string (META_FRAME_RESIZE_NONE),
|
|
meta_frame_focus_to_string (i));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
meta_frame_style_set_validate (MetaFrameStyleSet *style_set,
|
|
GError **error)
|
|
{
|
|
int i, j;
|
|
|
|
g_return_val_if_fail (style_set != NULL, FALSE);
|
|
|
|
for (i = 0; i < META_FRAME_RESIZE_LAST; i++)
|
|
for (j = 0; j < META_FRAME_FOCUS_LAST; j++)
|
|
if (get_style (style_set, META_FRAME_STATE_NORMAL, i, j) == NULL)
|
|
{
|
|
g_set_error (error, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Missing <frame state=\"%s\" resize=\"%s\" focus=\"%s\" style=\"whatever\"/>"),
|
|
meta_frame_state_to_string (META_FRAME_STATE_NORMAL),
|
|
meta_frame_resize_to_string (i),
|
|
meta_frame_focus_to_string (j));
|
|
return FALSE;
|
|
}
|
|
|
|
if (!check_state (style_set, META_FRAME_STATE_SHADED, error))
|
|
return FALSE;
|
|
|
|
if (!check_state (style_set, META_FRAME_STATE_MAXIMIZED, error))
|
|
return FALSE;
|
|
|
|
if (!check_state (style_set, META_FRAME_STATE_MAXIMIZED_AND_SHADED, error))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static MetaTheme *meta_current_theme = NULL;
|
|
|
|
MetaTheme*
|
|
meta_theme_get_current (void)
|
|
{
|
|
return meta_current_theme;
|
|
}
|
|
|
|
void
|
|
meta_theme_set_current (const char *name,
|
|
gboolean force_reload)
|
|
{
|
|
MetaTheme *new_theme;
|
|
GError *err;
|
|
|
|
meta_topic (META_DEBUG_THEMES, "Setting current theme to \"%s\"\n", name);
|
|
|
|
if (!force_reload &&
|
|
meta_current_theme &&
|
|
strcmp (name, meta_current_theme->name) == 0)
|
|
return;
|
|
|
|
err = NULL;
|
|
new_theme = meta_theme_load (name, &err);
|
|
|
|
if (new_theme == NULL)
|
|
{
|
|
meta_warning (_("Failed to load theme \"%s\": %s\n"),
|
|
name, err->message);
|
|
g_error_free (err);
|
|
}
|
|
else
|
|
{
|
|
if (meta_current_theme)
|
|
meta_theme_free (meta_current_theme);
|
|
|
|
meta_current_theme = new_theme;
|
|
|
|
meta_topic (META_DEBUG_THEMES, "New theme is \"%s\"\n", meta_current_theme->name);
|
|
}
|
|
}
|
|
|
|
MetaTheme*
|
|
meta_theme_new (void)
|
|
{
|
|
MetaTheme *theme;
|
|
|
|
theme = g_new0 (MetaTheme, 1);
|
|
|
|
theme->images_by_filename =
|
|
g_hash_table_new_full (g_str_hash,
|
|
g_str_equal,
|
|
g_free,
|
|
(GDestroyNotify) g_object_unref);
|
|
|
|
theme->layouts_by_name =
|
|
g_hash_table_new_full (g_str_hash,
|
|
g_str_equal,
|
|
g_free,
|
|
(GDestroyNotify) meta_frame_layout_unref);
|
|
|
|
theme->draw_op_lists_by_name =
|
|
g_hash_table_new_full (g_str_hash,
|
|
g_str_equal,
|
|
g_free,
|
|
(GDestroyNotify) meta_draw_op_list_unref);
|
|
|
|
theme->styles_by_name =
|
|
g_hash_table_new_full (g_str_hash,
|
|
g_str_equal,
|
|
g_free,
|
|
(GDestroyNotify) meta_frame_style_unref);
|
|
|
|
theme->style_sets_by_name =
|
|
g_hash_table_new_full (g_str_hash,
|
|
g_str_equal,
|
|
g_free,
|
|
(GDestroyNotify) meta_frame_style_set_unref);
|
|
|
|
return theme;
|
|
}
|
|
|
|
|
|
void
|
|
meta_theme_free (MetaTheme *theme)
|
|
{
|
|
int i;
|
|
|
|
g_return_if_fail (theme != NULL);
|
|
|
|
g_free (theme->name);
|
|
g_free (theme->dirname);
|
|
g_free (theme->filename);
|
|
g_free (theme->readable_name);
|
|
g_free (theme->date);
|
|
g_free (theme->description);
|
|
g_free (theme->author);
|
|
g_free (theme->copyright);
|
|
|
|
/* be more careful when destroying the theme hash tables,
|
|
since they are only constructed as needed, and may be NULL. */
|
|
if (theme->integer_constants)
|
|
g_hash_table_destroy (theme->integer_constants);
|
|
if (theme->images_by_filename)
|
|
g_hash_table_destroy (theme->images_by_filename);
|
|
if (theme->layouts_by_name)
|
|
g_hash_table_destroy (theme->layouts_by_name);
|
|
if (theme->draw_op_lists_by_name)
|
|
g_hash_table_destroy (theme->draw_op_lists_by_name);
|
|
if (theme->styles_by_name)
|
|
g_hash_table_destroy (theme->styles_by_name);
|
|
if (theme->style_sets_by_name)
|
|
g_hash_table_destroy (theme->style_sets_by_name);
|
|
|
|
for (i = 0; i < META_FRAME_TYPE_LAST; i++)
|
|
if (theme->style_sets_by_type[i])
|
|
meta_frame_style_set_unref (theme->style_sets_by_type[i]);
|
|
|
|
DEBUG_FILL_STRUCT (theme);
|
|
g_free (theme);
|
|
}
|
|
|
|
gboolean
|
|
meta_theme_validate (MetaTheme *theme,
|
|
GError **error)
|
|
{
|
|
int i;
|
|
|
|
g_return_val_if_fail (theme != NULL, FALSE);
|
|
|
|
/* FIXME what else should be checked? */
|
|
|
|
g_assert (theme->name);
|
|
|
|
if (theme->readable_name == NULL)
|
|
{
|
|
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
|
|
_("No <%s> set for theme \"%s\""), "name", theme->name);
|
|
return FALSE;
|
|
}
|
|
|
|
if (theme->author == NULL)
|
|
{
|
|
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
|
|
_("No <%s> set for theme \"%s\""), "author", theme->name);
|
|
return FALSE;
|
|
}
|
|
|
|
if (theme->date == NULL)
|
|
{
|
|
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
|
|
_("No <%s> set for theme \"%s\""), "date", theme->name);
|
|
return FALSE;
|
|
}
|
|
|
|
if (theme->description == NULL)
|
|
{
|
|
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
|
|
_("No <%s> set for theme \"%s\""), "description", theme->name);
|
|
return FALSE;
|
|
}
|
|
|
|
if (theme->copyright == NULL)
|
|
{
|
|
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
|
|
_("No <%s> set for theme \"%s\""), "copyright", theme->name);
|
|
return FALSE;
|
|
}
|
|
|
|
for (i = 0; i < (int)META_FRAME_TYPE_LAST; i++)
|
|
if (theme->style_sets_by_type[i] == NULL)
|
|
{
|
|
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
|
|
_("No frame style set for window type \"%s\" in theme \"%s\", add a <window type=\"%s\" style_set=\"whatever\"/> element"),
|
|
meta_frame_type_to_string (i),
|
|
theme->name,
|
|
meta_frame_type_to_string (i));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
GdkPixbuf*
|
|
meta_theme_load_image (MetaTheme *theme,
|
|
const char *filename,
|
|
guint size_of_theme_icons,
|
|
GError **error)
|
|
{
|
|
GdkPixbuf *pixbuf;
|
|
|
|
pixbuf = g_hash_table_lookup (theme->images_by_filename,
|
|
filename);
|
|
|
|
if (pixbuf == NULL)
|
|
{
|
|
|
|
if (g_str_has_prefix (filename, "theme:") &&
|
|
META_THEME_ALLOWS (theme, META_THEME_IMAGES_FROM_ICON_THEMES))
|
|
{
|
|
pixbuf = gtk_icon_theme_load_icon (
|
|
gtk_icon_theme_get_default (),
|
|
filename+6,
|
|
size_of_theme_icons,
|
|
0,
|
|
error);
|
|
if (pixbuf == NULL) return NULL;
|
|
}
|
|
else
|
|
{
|
|
char *full_path;
|
|
full_path = g_build_filename (theme->dirname, filename, NULL);
|
|
|
|
pixbuf = gdk_pixbuf_new_from_file (full_path, error);
|
|
if (pixbuf == NULL)
|
|
{
|
|
g_free (full_path);
|
|
return NULL;
|
|
}
|
|
|
|
g_free (full_path);
|
|
}
|
|
g_hash_table_replace (theme->images_by_filename,
|
|
g_strdup (filename),
|
|
pixbuf);
|
|
}
|
|
|
|
g_assert (pixbuf);
|
|
|
|
g_object_ref (G_OBJECT (pixbuf));
|
|
|
|
return pixbuf;
|
|
}
|
|
|
|
static MetaFrameStyle*
|
|
theme_get_style (MetaTheme *theme,
|
|
MetaFrameType type,
|
|
MetaFrameFlags flags)
|
|
{
|
|
MetaFrameState state;
|
|
MetaFrameResize resize;
|
|
MetaFrameFocus focus;
|
|
MetaFrameStyle *style;
|
|
MetaFrameStyleSet *style_set;
|
|
|
|
style_set = theme->style_sets_by_type[type];
|
|
|
|
/* Right now the parser forces a style set for all types,
|
|
* but this fallback code is here in case I take that out.
|
|
*/
|
|
if (style_set == NULL)
|
|
style_set = theme->style_sets_by_type[META_FRAME_TYPE_NORMAL];
|
|
if (style_set == NULL)
|
|
return NULL;
|
|
|
|
switch (flags & (META_FRAME_MAXIMIZED | META_FRAME_SHADED))
|
|
{
|
|
case 0:
|
|
state = META_FRAME_STATE_NORMAL;
|
|
break;
|
|
case META_FRAME_MAXIMIZED:
|
|
state = META_FRAME_STATE_MAXIMIZED;
|
|
break;
|
|
case META_FRAME_SHADED:
|
|
state = META_FRAME_STATE_SHADED;
|
|
break;
|
|
case (META_FRAME_MAXIMIZED | META_FRAME_SHADED):
|
|
state = META_FRAME_STATE_MAXIMIZED_AND_SHADED;
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
state = META_FRAME_STATE_LAST; /* compiler */
|
|
break;
|
|
}
|
|
|
|
switch (flags & (META_FRAME_ALLOWS_VERTICAL_RESIZE | META_FRAME_ALLOWS_HORIZONTAL_RESIZE))
|
|
{
|
|
case 0:
|
|
resize = META_FRAME_RESIZE_NONE;
|
|
break;
|
|
case META_FRAME_ALLOWS_VERTICAL_RESIZE:
|
|
resize = META_FRAME_RESIZE_VERTICAL;
|
|
break;
|
|
case META_FRAME_ALLOWS_HORIZONTAL_RESIZE:
|
|
resize = META_FRAME_RESIZE_HORIZONTAL;
|
|
break;
|
|
case (META_FRAME_ALLOWS_VERTICAL_RESIZE | META_FRAME_ALLOWS_HORIZONTAL_RESIZE):
|
|
resize = META_FRAME_RESIZE_BOTH;
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
resize = META_FRAME_RESIZE_LAST; /* compiler */
|
|
break;
|
|
}
|
|
|
|
/* re invert the styles used for focus/unfocussed while flashing a frame */
|
|
if (((flags & META_FRAME_HAS_FOCUS) && !(flags & META_FRAME_IS_FLASHING))
|
|
|| (!(flags & META_FRAME_HAS_FOCUS) && (flags & META_FRAME_IS_FLASHING)))
|
|
focus = META_FRAME_FOCUS_YES;
|
|
else
|
|
focus = META_FRAME_FOCUS_NO;
|
|
|
|
style = get_style (style_set, state, resize, focus);
|
|
|
|
return style;
|
|
}
|
|
|
|
MetaFrameStyle*
|
|
meta_theme_get_frame_style (MetaTheme *theme,
|
|
MetaFrameType type,
|
|
MetaFrameFlags flags)
|
|
{
|
|
MetaFrameStyle *style;
|
|
|
|
g_return_val_if_fail (type < META_FRAME_TYPE_LAST, NULL);
|
|
|
|
style = theme_get_style (theme, type, flags);
|
|
|
|
return style;
|
|
}
|
|
|
|
double
|
|
meta_theme_get_title_scale (MetaTheme *theme,
|
|
MetaFrameType type,
|
|
MetaFrameFlags flags)
|
|
{
|
|
MetaFrameStyle *style;
|
|
|
|
g_return_val_if_fail (type < META_FRAME_TYPE_LAST, 1.0);
|
|
|
|
style = theme_get_style (theme, type, flags);
|
|
|
|
/* Parser is not supposed to allow this currently */
|
|
if (style == NULL)
|
|
return 1.0;
|
|
|
|
return style->layout->title_scale;
|
|
}
|
|
|
|
void
|
|
meta_theme_draw_frame (MetaTheme *theme,
|
|
GtkWidget *widget,
|
|
GdkDrawable *drawable,
|
|
const GdkRectangle *clip,
|
|
int x_offset,
|
|
int y_offset,
|
|
MetaFrameType type,
|
|
MetaFrameFlags flags,
|
|
int client_width,
|
|
int client_height,
|
|
PangoLayout *title_layout,
|
|
int text_height,
|
|
const MetaButtonLayout *button_layout,
|
|
MetaButtonState button_states[META_BUTTON_TYPE_LAST],
|
|
GdkPixbuf *mini_icon,
|
|
GdkPixbuf *icon)
|
|
{
|
|
MetaFrameGeometry fgeom;
|
|
MetaFrameStyle *style;
|
|
|
|
g_return_if_fail (type < META_FRAME_TYPE_LAST);
|
|
|
|
style = theme_get_style (theme, type, flags);
|
|
|
|
/* Parser is not supposed to allow this currently */
|
|
if (style == NULL)
|
|
return;
|
|
|
|
meta_frame_layout_calc_geometry (style->layout,
|
|
text_height,
|
|
flags,
|
|
client_width, client_height,
|
|
button_layout,
|
|
&fgeom,
|
|
theme);
|
|
|
|
meta_frame_style_draw (style,
|
|
widget,
|
|
drawable,
|
|
x_offset, y_offset,
|
|
clip,
|
|
&fgeom,
|
|
client_width, client_height,
|
|
title_layout,
|
|
text_height,
|
|
button_states,
|
|
mini_icon, icon);
|
|
}
|
|
|
|
void
|
|
meta_theme_get_frame_borders (MetaTheme *theme,
|
|
MetaFrameType type,
|
|
int text_height,
|
|
MetaFrameFlags flags,
|
|
int *top_height,
|
|
int *bottom_height,
|
|
int *left_width,
|
|
int *right_width)
|
|
{
|
|
MetaFrameStyle *style;
|
|
|
|
g_return_if_fail (type < META_FRAME_TYPE_LAST);
|
|
|
|
if (top_height)
|
|
*top_height = 0;
|
|
if (bottom_height)
|
|
*bottom_height = 0;
|
|
if (left_width)
|
|
*left_width = 0;
|
|
if (right_width)
|
|
*right_width = 0;
|
|
|
|
style = theme_get_style (theme, type, flags);
|
|
|
|
/* Parser is not supposed to allow this currently */
|
|
if (style == NULL)
|
|
return;
|
|
|
|
meta_frame_layout_get_borders (style->layout,
|
|
text_height,
|
|
flags,
|
|
top_height, bottom_height,
|
|
left_width, right_width);
|
|
}
|
|
|
|
void
|
|
meta_theme_calc_geometry (MetaTheme *theme,
|
|
MetaFrameType type,
|
|
int text_height,
|
|
MetaFrameFlags flags,
|
|
int client_width,
|
|
int client_height,
|
|
const MetaButtonLayout *button_layout,
|
|
MetaFrameGeometry *fgeom)
|
|
{
|
|
MetaFrameStyle *style;
|
|
|
|
g_return_if_fail (type < META_FRAME_TYPE_LAST);
|
|
|
|
style = theme_get_style (theme, type, flags);
|
|
|
|
/* Parser is not supposed to allow this currently */
|
|
if (style == NULL)
|
|
return;
|
|
|
|
meta_frame_layout_calc_geometry (style->layout,
|
|
text_height,
|
|
flags,
|
|
client_width, client_height,
|
|
button_layout,
|
|
fgeom,
|
|
theme);
|
|
}
|
|
|
|
MetaFrameLayout*
|
|
meta_theme_lookup_layout (MetaTheme *theme,
|
|
const char *name)
|
|
{
|
|
return g_hash_table_lookup (theme->layouts_by_name, name);
|
|
}
|
|
|
|
void
|
|
meta_theme_insert_layout (MetaTheme *theme,
|
|
const char *name,
|
|
MetaFrameLayout *layout)
|
|
{
|
|
meta_frame_layout_ref (layout);
|
|
g_hash_table_replace (theme->layouts_by_name, g_strdup (name), layout);
|
|
}
|
|
|
|
MetaDrawOpList*
|
|
meta_theme_lookup_draw_op_list (MetaTheme *theme,
|
|
const char *name)
|
|
{
|
|
return g_hash_table_lookup (theme->draw_op_lists_by_name, name);
|
|
}
|
|
|
|
void
|
|
meta_theme_insert_draw_op_list (MetaTheme *theme,
|
|
const char *name,
|
|
MetaDrawOpList *op_list)
|
|
{
|
|
meta_draw_op_list_ref (op_list);
|
|
g_hash_table_replace (theme->draw_op_lists_by_name, g_strdup (name), op_list);
|
|
}
|
|
|
|
MetaFrameStyle*
|
|
meta_theme_lookup_style (MetaTheme *theme,
|
|
const char *name)
|
|
{
|
|
return g_hash_table_lookup (theme->styles_by_name, name);
|
|
}
|
|
|
|
void
|
|
meta_theme_insert_style (MetaTheme *theme,
|
|
const char *name,
|
|
MetaFrameStyle *style)
|
|
{
|
|
meta_frame_style_ref (style);
|
|
g_hash_table_replace (theme->styles_by_name, g_strdup (name), style);
|
|
}
|
|
|
|
MetaFrameStyleSet*
|
|
meta_theme_lookup_style_set (MetaTheme *theme,
|
|
const char *name)
|
|
{
|
|
return g_hash_table_lookup (theme->style_sets_by_name, name);
|
|
}
|
|
|
|
void
|
|
meta_theme_insert_style_set (MetaTheme *theme,
|
|
const char *name,
|
|
MetaFrameStyleSet *style_set)
|
|
{
|
|
meta_frame_style_set_ref (style_set);
|
|
g_hash_table_replace (theme->style_sets_by_name, g_strdup (name), style_set);
|
|
}
|
|
|
|
static gboolean
|
|
first_uppercase (const char *str)
|
|
{
|
|
return g_ascii_isupper (*str);
|
|
}
|
|
|
|
gboolean
|
|
meta_theme_define_int_constant (MetaTheme *theme,
|
|
const char *name,
|
|
int value,
|
|
GError **error)
|
|
{
|
|
if (theme->integer_constants == NULL)
|
|
theme->integer_constants = g_hash_table_new_full (g_str_hash,
|
|
g_str_equal,
|
|
g_free,
|
|
NULL);
|
|
|
|
if (!first_uppercase (name))
|
|
{
|
|
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
|
|
_("User-defined constants must begin with a capital letter; \"%s\" does not"),
|
|
name);
|
|
return FALSE;
|
|
}
|
|
|
|
if (g_hash_table_lookup_extended (theme->integer_constants, name, NULL, NULL))
|
|
{
|
|
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
|
|
_("Constant \"%s\" has already been defined"),
|
|
name);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
g_hash_table_insert (theme->integer_constants,
|
|
g_strdup (name),
|
|
GINT_TO_POINTER (value));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
meta_theme_lookup_int_constant (MetaTheme *theme,
|
|
const char *name,
|
|
int *value)
|
|
{
|
|
gpointer old_value;
|
|
|
|
*value = 0;
|
|
|
|
if (theme->integer_constants == NULL)
|
|
return FALSE;
|
|
|
|
if (g_hash_table_lookup_extended (theme->integer_constants,
|
|
name, NULL, &old_value))
|
|
{
|
|
*value = GPOINTER_TO_INT (old_value);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
meta_theme_define_float_constant (MetaTheme *theme,
|
|
const char *name,
|
|
double value,
|
|
GError **error)
|
|
{
|
|
double *d;
|
|
|
|
if (theme->float_constants == NULL)
|
|
theme->float_constants = g_hash_table_new_full (g_str_hash,
|
|
g_str_equal,
|
|
g_free,
|
|
g_free);
|
|
|
|
if (!first_uppercase (name))
|
|
{
|
|
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
|
|
_("User-defined constants must begin with a capital letter; \"%s\" does not"),
|
|
name);
|
|
return FALSE;
|
|
}
|
|
|
|
if (g_hash_table_lookup_extended (theme->float_constants, name, NULL, NULL))
|
|
{
|
|
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
|
|
_("Constant \"%s\" has already been defined"),
|
|
name);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
d = g_new (double, 1);
|
|
*d = value;
|
|
|
|
g_hash_table_insert (theme->float_constants,
|
|
g_strdup (name), d);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
meta_theme_lookup_float_constant (MetaTheme *theme,
|
|
const char *name,
|
|
double *value)
|
|
{
|
|
double *d;
|
|
|
|
*value = 0.0;
|
|
|
|
if (theme->float_constants == NULL)
|
|
return FALSE;
|
|
|
|
d = g_hash_table_lookup (theme->float_constants, name);
|
|
|
|
if (d)
|
|
{
|
|
*value = *d;
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
meta_theme_define_color_constant (MetaTheme *theme,
|
|
const char *name,
|
|
const char *value,
|
|
GError **error)
|
|
{
|
|
if (theme->color_constants == NULL)
|
|
theme->color_constants = g_hash_table_new_full (g_str_hash,
|
|
g_str_equal,
|
|
g_free,
|
|
NULL);
|
|
|
|
if (!first_uppercase (name))
|
|
{
|
|
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
|
|
_("User-defined constants must begin with a capital letter; \"%s\" does not"),
|
|
name);
|
|
return FALSE;
|
|
}
|
|
|
|
if (g_hash_table_lookup_extended (theme->color_constants, name, NULL, NULL))
|
|
{
|
|
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
|
|
_("Constant \"%s\" has already been defined"),
|
|
name);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
g_hash_table_insert (theme->color_constants,
|
|
g_strdup (name),
|
|
g_strdup (value));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
meta_theme_lookup_color_constant (MetaTheme *theme,
|
|
const char *name,
|
|
char **value)
|
|
{
|
|
char *result;
|
|
|
|
*value = NULL;
|
|
|
|
if (theme->color_constants == NULL)
|
|
return FALSE;
|
|
|
|
result = g_hash_table_lookup (theme->color_constants, name);
|
|
|
|
if (result)
|
|
{
|
|
*value = result;
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
PangoFontDescription*
|
|
meta_gtk_widget_get_font_desc (GtkWidget *widget,
|
|
double scale,
|
|
const PangoFontDescription *override)
|
|
{
|
|
PangoFontDescription *font_desc;
|
|
|
|
g_return_val_if_fail (GTK_WIDGET_REALIZED (widget), NULL);
|
|
|
|
font_desc = pango_font_description_copy (widget->style->font_desc);
|
|
|
|
if (override)
|
|
pango_font_description_merge (font_desc, override, TRUE);
|
|
|
|
pango_font_description_set_size (font_desc,
|
|
MAX (pango_font_description_get_size (font_desc) * scale, 1));
|
|
|
|
return font_desc;
|
|
}
|
|
|
|
int
|
|
meta_pango_font_desc_get_text_height (const PangoFontDescription *font_desc,
|
|
PangoContext *context)
|
|
{
|
|
PangoFontMetrics *metrics;
|
|
PangoLanguage *lang;
|
|
int retval;
|
|
|
|
lang = pango_context_get_language (context);
|
|
metrics = pango_context_get_metrics (context, font_desc, lang);
|
|
|
|
retval = PANGO_PIXELS (pango_font_metrics_get_ascent (metrics) +
|
|
pango_font_metrics_get_descent (metrics));
|
|
|
|
pango_font_metrics_unref (metrics);
|
|
|
|
return retval;
|
|
}
|
|
|
|
MetaGtkColorComponent
|
|
meta_color_component_from_string (const char *str)
|
|
{
|
|
if (strcmp ("fg", str) == 0)
|
|
return META_GTK_COLOR_FG;
|
|
else if (strcmp ("bg", str) == 0)
|
|
return META_GTK_COLOR_BG;
|
|
else if (strcmp ("light", str) == 0)
|
|
return META_GTK_COLOR_LIGHT;
|
|
else if (strcmp ("dark", str) == 0)
|
|
return META_GTK_COLOR_DARK;
|
|
else if (strcmp ("mid", str) == 0)
|
|
return META_GTK_COLOR_MID;
|
|
else if (strcmp ("text", str) == 0)
|
|
return META_GTK_COLOR_TEXT;
|
|
else if (strcmp ("base", str) == 0)
|
|
return META_GTK_COLOR_BASE;
|
|
else if (strcmp ("text_aa", str) == 0)
|
|
return META_GTK_COLOR_TEXT_AA;
|
|
else
|
|
return META_GTK_COLOR_LAST;
|
|
}
|
|
|
|
const char*
|
|
meta_color_component_to_string (MetaGtkColorComponent component)
|
|
{
|
|
switch (component)
|
|
{
|
|
case META_GTK_COLOR_FG:
|
|
return "fg";
|
|
case META_GTK_COLOR_BG:
|
|
return "bg";
|
|
case META_GTK_COLOR_LIGHT:
|
|
return "light";
|
|
case META_GTK_COLOR_DARK:
|
|
return "dark";
|
|
case META_GTK_COLOR_MID:
|
|
return "mid";
|
|
case META_GTK_COLOR_TEXT:
|
|
return "text";
|
|
case META_GTK_COLOR_BASE:
|
|
return "base";
|
|
case META_GTK_COLOR_TEXT_AA:
|
|
return "text_aa";
|
|
case META_GTK_COLOR_LAST:
|
|
break;
|
|
}
|
|
|
|
return "<unknown>";
|
|
}
|
|
|
|
MetaButtonState
|
|
meta_button_state_from_string (const char *str)
|
|
{
|
|
if (strcmp ("normal", str) == 0)
|
|
return META_BUTTON_STATE_NORMAL;
|
|
else if (strcmp ("pressed", str) == 0)
|
|
return META_BUTTON_STATE_PRESSED;
|
|
else if (strcmp ("prelight", str) == 0)
|
|
return META_BUTTON_STATE_PRELIGHT;
|
|
else
|
|
return META_BUTTON_STATE_LAST;
|
|
}
|
|
|
|
const char*
|
|
meta_button_state_to_string (MetaButtonState state)
|
|
{
|
|
switch (state)
|
|
{
|
|
case META_BUTTON_STATE_NORMAL:
|
|
return "normal";
|
|
case META_BUTTON_STATE_PRESSED:
|
|
return "pressed";
|
|
case META_BUTTON_STATE_PRELIGHT:
|
|
return "prelight";
|
|
case META_BUTTON_STATE_LAST:
|
|
break;
|
|
}
|
|
|
|
return "<unknown>";
|
|
}
|
|
|
|
MetaButtonType
|
|
meta_button_type_from_string (const char *str, MetaTheme *theme)
|
|
{
|
|
if (META_THEME_ALLOWS(theme, META_THEME_SHADE_STICK_ABOVE_BUTTONS))
|
|
{
|
|
if (strcmp ("shade", str) == 0)
|
|
return META_BUTTON_TYPE_SHADE;
|
|
else if (strcmp ("above", str) == 0)
|
|
return META_BUTTON_TYPE_ABOVE;
|
|
else if (strcmp ("stick", str) == 0)
|
|
return META_BUTTON_TYPE_STICK;
|
|
else if (strcmp ("unshade", str) == 0)
|
|
return META_BUTTON_TYPE_UNSHADE;
|
|
else if (strcmp ("unabove", str) == 0)
|
|
return META_BUTTON_TYPE_UNABOVE;
|
|
else if (strcmp ("unstick", str) == 0)
|
|
return META_BUTTON_TYPE_UNSTICK;
|
|
}
|
|
|
|
if (strcmp ("close", str) == 0)
|
|
return META_BUTTON_TYPE_CLOSE;
|
|
else if (strcmp ("maximize", str) == 0)
|
|
return META_BUTTON_TYPE_MAXIMIZE;
|
|
else if (strcmp ("minimize", str) == 0)
|
|
return META_BUTTON_TYPE_MINIMIZE;
|
|
else if (strcmp ("menu", str) == 0)
|
|
return META_BUTTON_TYPE_MENU;
|
|
else if (strcmp ("left_left_background", str) == 0)
|
|
return META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND;
|
|
else if (strcmp ("left_middle_background", str) == 0)
|
|
return META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND;
|
|
else if (strcmp ("left_right_background", str) == 0)
|
|
return META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND;
|
|
else if (strcmp ("right_left_background", str) == 0)
|
|
return META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND;
|
|
else if (strcmp ("right_middle_background", str) == 0)
|
|
return META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND;
|
|
else if (strcmp ("right_right_background", str) == 0)
|
|
return META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND;
|
|
else
|
|
return META_BUTTON_TYPE_LAST;
|
|
}
|
|
|
|
const char*
|
|
meta_button_type_to_string (MetaButtonType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case META_BUTTON_TYPE_CLOSE:
|
|
return "close";
|
|
case META_BUTTON_TYPE_MAXIMIZE:
|
|
return "maximize";
|
|
case META_BUTTON_TYPE_MINIMIZE:
|
|
return "minimize";
|
|
case META_BUTTON_TYPE_SHADE:
|
|
return "shade";
|
|
case META_BUTTON_TYPE_ABOVE:
|
|
return "above";
|
|
case META_BUTTON_TYPE_STICK:
|
|
return "stick";
|
|
case META_BUTTON_TYPE_UNSHADE:
|
|
return "unshade";
|
|
case META_BUTTON_TYPE_UNABOVE:
|
|
return "unabove";
|
|
case META_BUTTON_TYPE_UNSTICK:
|
|
return "unstick";
|
|
case META_BUTTON_TYPE_MENU:
|
|
return "menu";
|
|
case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND:
|
|
return "left_left_background";
|
|
case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND:
|
|
return "left_middle_background";
|
|
case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND:
|
|
return "left_right_background";
|
|
case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND:
|
|
return "right_left_background";
|
|
case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND:
|
|
return "right_middle_background";
|
|
case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND:
|
|
return "right_right_background";
|
|
case META_BUTTON_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
return "<unknown>";
|
|
}
|
|
|
|
MetaFramePiece
|
|
meta_frame_piece_from_string (const char *str)
|
|
{
|
|
if (strcmp ("entire_background", str) == 0)
|
|
return META_FRAME_PIECE_ENTIRE_BACKGROUND;
|
|
else if (strcmp ("titlebar", str) == 0)
|
|
return META_FRAME_PIECE_TITLEBAR;
|
|
else if (strcmp ("titlebar_middle", str) == 0)
|
|
return META_FRAME_PIECE_TITLEBAR_MIDDLE;
|
|
else if (strcmp ("left_titlebar_edge", str) == 0)
|
|
return META_FRAME_PIECE_LEFT_TITLEBAR_EDGE;
|
|
else if (strcmp ("right_titlebar_edge", str) == 0)
|
|
return META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE;
|
|
else if (strcmp ("top_titlebar_edge", str) == 0)
|
|
return META_FRAME_PIECE_TOP_TITLEBAR_EDGE;
|
|
else if (strcmp ("bottom_titlebar_edge", str) == 0)
|
|
return META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE;
|
|
else if (strcmp ("title", str) == 0)
|
|
return META_FRAME_PIECE_TITLE;
|
|
else if (strcmp ("left_edge", str) == 0)
|
|
return META_FRAME_PIECE_LEFT_EDGE;
|
|
else if (strcmp ("right_edge", str) == 0)
|
|
return META_FRAME_PIECE_RIGHT_EDGE;
|
|
else if (strcmp ("bottom_edge", str) == 0)
|
|
return META_FRAME_PIECE_BOTTOM_EDGE;
|
|
else if (strcmp ("overlay", str) == 0)
|
|
return META_FRAME_PIECE_OVERLAY;
|
|
else
|
|
return META_FRAME_PIECE_LAST;
|
|
}
|
|
|
|
const char*
|
|
meta_frame_piece_to_string (MetaFramePiece piece)
|
|
{
|
|
switch (piece)
|
|
{
|
|
case META_FRAME_PIECE_ENTIRE_BACKGROUND:
|
|
return "entire_background";
|
|
case META_FRAME_PIECE_TITLEBAR:
|
|
return "titlebar";
|
|
case META_FRAME_PIECE_TITLEBAR_MIDDLE:
|
|
return "titlebar_middle";
|
|
case META_FRAME_PIECE_LEFT_TITLEBAR_EDGE:
|
|
return "left_titlebar_edge";
|
|
case META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE:
|
|
return "right_titlebar_edge";
|
|
case META_FRAME_PIECE_TOP_TITLEBAR_EDGE:
|
|
return "top_titlebar_edge";
|
|
case META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE:
|
|
return "bottom_titlebar_edge";
|
|
case META_FRAME_PIECE_TITLE:
|
|
return "title";
|
|
case META_FRAME_PIECE_LEFT_EDGE:
|
|
return "left_edge";
|
|
case META_FRAME_PIECE_RIGHT_EDGE:
|
|
return "right_edge";
|
|
case META_FRAME_PIECE_BOTTOM_EDGE:
|
|
return "bottom_edge";
|
|
case META_FRAME_PIECE_OVERLAY:
|
|
return "overlay";
|
|
case META_FRAME_PIECE_LAST:
|
|
break;
|
|
}
|
|
|
|
return "<unknown>";
|
|
}
|
|
|
|
MetaFrameState
|
|
meta_frame_state_from_string (const char *str)
|
|
{
|
|
if (strcmp ("normal", str) == 0)
|
|
return META_FRAME_STATE_NORMAL;
|
|
else if (strcmp ("maximized", str) == 0)
|
|
return META_FRAME_STATE_MAXIMIZED;
|
|
else if (strcmp ("shaded", str) == 0)
|
|
return META_FRAME_STATE_SHADED;
|
|
else if (strcmp ("maximized_and_shaded", str) == 0)
|
|
return META_FRAME_STATE_MAXIMIZED_AND_SHADED;
|
|
else
|
|
return META_FRAME_STATE_LAST;
|
|
}
|
|
|
|
const char*
|
|
meta_frame_state_to_string (MetaFrameState state)
|
|
{
|
|
switch (state)
|
|
{
|
|
case META_FRAME_STATE_NORMAL:
|
|
return "normal";
|
|
case META_FRAME_STATE_MAXIMIZED:
|
|
return "maximized";
|
|
case META_FRAME_STATE_SHADED:
|
|
return "shaded";
|
|
case META_FRAME_STATE_MAXIMIZED_AND_SHADED:
|
|
return "maximized_and_shaded";
|
|
case META_FRAME_STATE_LAST:
|
|
break;
|
|
}
|
|
|
|
return "<unknown>";
|
|
}
|
|
|
|
MetaFrameResize
|
|
meta_frame_resize_from_string (const char *str)
|
|
{
|
|
if (strcmp ("none", str) == 0)
|
|
return META_FRAME_RESIZE_NONE;
|
|
else if (strcmp ("vertical", str) == 0)
|
|
return META_FRAME_RESIZE_VERTICAL;
|
|
else if (strcmp ("horizontal", str) == 0)
|
|
return META_FRAME_RESIZE_HORIZONTAL;
|
|
else if (strcmp ("both", str) == 0)
|
|
return META_FRAME_RESIZE_BOTH;
|
|
else
|
|
return META_FRAME_RESIZE_LAST;
|
|
}
|
|
|
|
const char*
|
|
meta_frame_resize_to_string (MetaFrameResize resize)
|
|
{
|
|
switch (resize)
|
|
{
|
|
case META_FRAME_RESIZE_NONE:
|
|
return "none";
|
|
case META_FRAME_RESIZE_VERTICAL:
|
|
return "vertical";
|
|
case META_FRAME_RESIZE_HORIZONTAL:
|
|
return "horizontal";
|
|
case META_FRAME_RESIZE_BOTH:
|
|
return "both";
|
|
case META_FRAME_RESIZE_LAST:
|
|
break;
|
|
}
|
|
|
|
return "<unknown>";
|
|
}
|
|
|
|
MetaFrameFocus
|
|
meta_frame_focus_from_string (const char *str)
|
|
{
|
|
if (strcmp ("no", str) == 0)
|
|
return META_FRAME_FOCUS_NO;
|
|
else if (strcmp ("yes", str) == 0)
|
|
return META_FRAME_FOCUS_YES;
|
|
else
|
|
return META_FRAME_FOCUS_LAST;
|
|
}
|
|
|
|
const char*
|
|
meta_frame_focus_to_string (MetaFrameFocus focus)
|
|
{
|
|
switch (focus)
|
|
{
|
|
case META_FRAME_FOCUS_NO:
|
|
return "no";
|
|
case META_FRAME_FOCUS_YES:
|
|
return "yes";
|
|
case META_FRAME_FOCUS_LAST:
|
|
break;
|
|
}
|
|
|
|
return "<unknown>";
|
|
}
|
|
|
|
MetaFrameType
|
|
meta_frame_type_from_string (const char *str)
|
|
{
|
|
if (strcmp ("normal", str) == 0)
|
|
return META_FRAME_TYPE_NORMAL;
|
|
else if (strcmp ("dialog", str) == 0)
|
|
return META_FRAME_TYPE_DIALOG;
|
|
else if (strcmp ("modal_dialog", str) == 0)
|
|
return META_FRAME_TYPE_MODAL_DIALOG;
|
|
else if (strcmp ("utility", str) == 0)
|
|
return META_FRAME_TYPE_UTILITY;
|
|
else if (strcmp ("menu", str) == 0)
|
|
return META_FRAME_TYPE_MENU;
|
|
else if (strcmp ("border", str) == 0)
|
|
return META_FRAME_TYPE_BORDER;
|
|
#if 0
|
|
else if (strcmp ("toolbar", str) == 0)
|
|
return META_FRAME_TYPE_TOOLBAR;
|
|
#endif
|
|
else
|
|
return META_FRAME_TYPE_LAST;
|
|
}
|
|
|
|
const char*
|
|
meta_frame_type_to_string (MetaFrameType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case META_FRAME_TYPE_NORMAL:
|
|
return "normal";
|
|
case META_FRAME_TYPE_DIALOG:
|
|
return "dialog";
|
|
case META_FRAME_TYPE_MODAL_DIALOG:
|
|
return "modal_dialog";
|
|
case META_FRAME_TYPE_UTILITY:
|
|
return "utility";
|
|
case META_FRAME_TYPE_MENU:
|
|
return "menu";
|
|
case META_FRAME_TYPE_BORDER:
|
|
return "border";
|
|
#if 0
|
|
case META_FRAME_TYPE_TOOLBAR:
|
|
return "toolbar";
|
|
#endif
|
|
case META_FRAME_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
return "<unknown>";
|
|
}
|
|
|
|
MetaGradientType
|
|
meta_gradient_type_from_string (const char *str)
|
|
{
|
|
if (strcmp ("vertical", str) == 0)
|
|
return META_GRADIENT_VERTICAL;
|
|
else if (strcmp ("horizontal", str) == 0)
|
|
return META_GRADIENT_HORIZONTAL;
|
|
else if (strcmp ("diagonal", str) == 0)
|
|
return META_GRADIENT_DIAGONAL;
|
|
else
|
|
return META_GRADIENT_LAST;
|
|
}
|
|
|
|
const char*
|
|
meta_gradient_type_to_string (MetaGradientType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case META_GRADIENT_VERTICAL:
|
|
return "vertical";
|
|
case META_GRADIENT_HORIZONTAL:
|
|
return "horizontal";
|
|
case META_GRADIENT_DIAGONAL:
|
|
return "diagonal";
|
|
case META_GRADIENT_LAST:
|
|
break;
|
|
}
|
|
|
|
return "<unknown>";
|
|
}
|
|
|
|
GtkStateType
|
|
meta_gtk_state_from_string (const char *str)
|
|
{
|
|
if (strcmp ("normal", str) == 0 || strcmp ("NORMAL", str) == 0)
|
|
return GTK_STATE_NORMAL;
|
|
else if (strcmp ("prelight", str) == 0 || strcmp ("PRELIGHT", str) == 0)
|
|
return GTK_STATE_PRELIGHT;
|
|
else if (strcmp ("active", str) == 0 || strcmp ("ACTIVE", str) == 0)
|
|
return GTK_STATE_ACTIVE;
|
|
else if (strcmp ("selected", str) == 0 || strcmp ("SELECTED", str) == 0)
|
|
return GTK_STATE_SELECTED;
|
|
else if (strcmp ("insensitive", str) == 0 || strcmp ("INSENSITIVE", str) == 0)
|
|
return GTK_STATE_INSENSITIVE;
|
|
else
|
|
return -1; /* hack */
|
|
}
|
|
|
|
const char*
|
|
meta_gtk_state_to_string (GtkStateType state)
|
|
{
|
|
switch (state)
|
|
{
|
|
case GTK_STATE_NORMAL:
|
|
return "NORMAL";
|
|
case GTK_STATE_PRELIGHT:
|
|
return "PRELIGHT";
|
|
case GTK_STATE_ACTIVE:
|
|
return "ACTIVE";
|
|
case GTK_STATE_SELECTED:
|
|
return "SELECTED";
|
|
case GTK_STATE_INSENSITIVE:
|
|
return "INSENSITIVE";
|
|
}
|
|
|
|
return "<unknown>";
|
|
}
|
|
|
|
GtkShadowType
|
|
meta_gtk_shadow_from_string (const char *str)
|
|
{
|
|
if (strcmp ("none", str) == 0)
|
|
return GTK_SHADOW_NONE;
|
|
else if (strcmp ("in", str) == 0)
|
|
return GTK_SHADOW_IN;
|
|
else if (strcmp ("out", str) == 0)
|
|
return GTK_SHADOW_OUT;
|
|
else if (strcmp ("etched_in", str) == 0)
|
|
return GTK_SHADOW_ETCHED_IN;
|
|
else if (strcmp ("etched_out", str) == 0)
|
|
return GTK_SHADOW_ETCHED_OUT;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
const char*
|
|
meta_gtk_shadow_to_string (GtkShadowType shadow)
|
|
{
|
|
switch (shadow)
|
|
{
|
|
case GTK_SHADOW_NONE:
|
|
return "none";
|
|
case GTK_SHADOW_IN:
|
|
return "in";
|
|
case GTK_SHADOW_OUT:
|
|
return "out";
|
|
case GTK_SHADOW_ETCHED_IN:
|
|
return "etched_in";
|
|
case GTK_SHADOW_ETCHED_OUT:
|
|
return "etched_out";
|
|
}
|
|
|
|
return "<unknown>";
|
|
}
|
|
|
|
GtkArrowType
|
|
meta_gtk_arrow_from_string (const char *str)
|
|
{
|
|
if (strcmp ("up", str) == 0)
|
|
return GTK_ARROW_UP;
|
|
else if (strcmp ("down", str) == 0)
|
|
return GTK_ARROW_DOWN;
|
|
else if (strcmp ("left", str) == 0)
|
|
return GTK_ARROW_LEFT;
|
|
else if (strcmp ("right", str) == 0)
|
|
return GTK_ARROW_RIGHT;
|
|
else if (strcmp ("none", str) == 0)
|
|
return GTK_ARROW_NONE;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
const char*
|
|
meta_gtk_arrow_to_string (GtkArrowType arrow)
|
|
{
|
|
switch (arrow)
|
|
{
|
|
case GTK_ARROW_UP:
|
|
return "up";
|
|
case GTK_ARROW_DOWN:
|
|
return "down";
|
|
case GTK_ARROW_LEFT:
|
|
return "left";
|
|
case GTK_ARROW_RIGHT:
|
|
return "right";
|
|
case GTK_ARROW_NONE:
|
|
return "none";
|
|
}
|
|
|
|
return "<unknown>";
|
|
}
|
|
|
|
MetaImageFillType
|
|
meta_image_fill_type_from_string (const char *str)
|
|
{
|
|
if (strcmp ("tile", str) == 0)
|
|
return META_IMAGE_FILL_TILE;
|
|
else if (strcmp ("scale", str) == 0)
|
|
return META_IMAGE_FILL_SCALE;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
const char*
|
|
meta_image_fill_type_to_string (MetaImageFillType fill_type)
|
|
{
|
|
switch (fill_type)
|
|
{
|
|
case META_IMAGE_FILL_TILE:
|
|
return "tile";
|
|
case META_IMAGE_FILL_SCALE:
|
|
return "scale";
|
|
}
|
|
|
|
return "<unknown>";
|
|
}
|
|
|
|
/* gtkstyle.c cut-and-pastage */
|
|
static void
|
|
gtk_style_shade (GdkColor *a,
|
|
GdkColor *b,
|
|
gdouble k)
|
|
{
|
|
gdouble red;
|
|
gdouble green;
|
|
gdouble blue;
|
|
|
|
red = (gdouble) a->red / 65535.0;
|
|
green = (gdouble) a->green / 65535.0;
|
|
blue = (gdouble) a->blue / 65535.0;
|
|
|
|
rgb_to_hls (&red, &green, &blue);
|
|
|
|
green *= k;
|
|
if (green > 1.0)
|
|
green = 1.0;
|
|
else if (green < 0.0)
|
|
green = 0.0;
|
|
|
|
blue *= k;
|
|
if (blue > 1.0)
|
|
blue = 1.0;
|
|
else if (blue < 0.0)
|
|
blue = 0.0;
|
|
|
|
hls_to_rgb (&red, &green, &blue);
|
|
|
|
b->red = red * 65535.0;
|
|
b->green = green * 65535.0;
|
|
b->blue = blue * 65535.0;
|
|
}
|
|
|
|
static void
|
|
rgb_to_hls (gdouble *r,
|
|
gdouble *g,
|
|
gdouble *b)
|
|
{
|
|
gdouble min;
|
|
gdouble max;
|
|
gdouble red;
|
|
gdouble green;
|
|
gdouble blue;
|
|
gdouble h, l, s;
|
|
gdouble delta;
|
|
|
|
red = *r;
|
|
green = *g;
|
|
blue = *b;
|
|
|
|
if (red > green)
|
|
{
|
|
if (red > blue)
|
|
max = red;
|
|
else
|
|
max = blue;
|
|
|
|
if (green < blue)
|
|
min = green;
|
|
else
|
|
min = blue;
|
|
}
|
|
else
|
|
{
|
|
if (green > blue)
|
|
max = green;
|
|
else
|
|
max = blue;
|
|
|
|
if (red < blue)
|
|
min = red;
|
|
else
|
|
min = blue;
|
|
}
|
|
|
|
l = (max + min) / 2;
|
|
s = 0;
|
|
h = 0;
|
|
|
|
if (max != min)
|
|
{
|
|
if (l <= 0.5)
|
|
s = (max - min) / (max + min);
|
|
else
|
|
s = (max - min) / (2 - max - min);
|
|
|
|
delta = max -min;
|
|
if (red == max)
|
|
h = (green - blue) / delta;
|
|
else if (green == max)
|
|
h = 2 + (blue - red) / delta;
|
|
else if (blue == max)
|
|
h = 4 + (red - green) / delta;
|
|
|
|
h *= 60;
|
|
if (h < 0.0)
|
|
h += 360;
|
|
}
|
|
|
|
*r = h;
|
|
*g = l;
|
|
*b = s;
|
|
}
|
|
|
|
static void
|
|
hls_to_rgb (gdouble *h,
|
|
gdouble *l,
|
|
gdouble *s)
|
|
{
|
|
gdouble hue;
|
|
gdouble lightness;
|
|
gdouble saturation;
|
|
gdouble m1, m2;
|
|
gdouble r, g, b;
|
|
|
|
lightness = *l;
|
|
saturation = *s;
|
|
|
|
if (lightness <= 0.5)
|
|
m2 = lightness * (1 + saturation);
|
|
else
|
|
m2 = lightness + saturation - lightness * saturation;
|
|
m1 = 2 * lightness - m2;
|
|
|
|
if (saturation == 0)
|
|
{
|
|
*h = lightness;
|
|
*l = lightness;
|
|
*s = lightness;
|
|
}
|
|
else
|
|
{
|
|
hue = *h + 120;
|
|
while (hue > 360)
|
|
hue -= 360;
|
|
while (hue < 0)
|
|
hue += 360;
|
|
|
|
if (hue < 60)
|
|
r = m1 + (m2 - m1) * hue / 60;
|
|
else if (hue < 180)
|
|
r = m2;
|
|
else if (hue < 240)
|
|
r = m1 + (m2 - m1) * (240 - hue) / 60;
|
|
else
|
|
r = m1;
|
|
|
|
hue = *h;
|
|
while (hue > 360)
|
|
hue -= 360;
|
|
while (hue < 0)
|
|
hue += 360;
|
|
|
|
if (hue < 60)
|
|
g = m1 + (m2 - m1) * hue / 60;
|
|
else if (hue < 180)
|
|
g = m2;
|
|
else if (hue < 240)
|
|
g = m1 + (m2 - m1) * (240 - hue) / 60;
|
|
else
|
|
g = m1;
|
|
|
|
hue = *h - 120;
|
|
while (hue > 360)
|
|
hue -= 360;
|
|
while (hue < 0)
|
|
hue += 360;
|
|
|
|
if (hue < 60)
|
|
b = m1 + (m2 - m1) * hue / 60;
|
|
else if (hue < 180)
|
|
b = m2;
|
|
else if (hue < 240)
|
|
b = m1 + (m2 - m1) * (240 - hue) / 60;
|
|
else
|
|
b = m1;
|
|
|
|
*h = r;
|
|
*l = g;
|
|
*s = b;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
/* These are some functions I'm saving to use in optimizing
|
|
* MetaDrawOpList, namely to pre-composite pixbufs on client side
|
|
* prior to rendering to the server
|
|
*/
|
|
static void
|
|
draw_bg_solid_composite (const MetaTextureSpec *bg,
|
|
const MetaTextureSpec *fg,
|
|
double alpha,
|
|
GtkWidget *widget,
|
|
GdkDrawable *drawable,
|
|
const GdkRectangle *clip,
|
|
MetaTextureDrawMode mode,
|
|
double xalign,
|
|
double yalign,
|
|
int x,
|
|
int y,
|
|
int width,
|
|
int height)
|
|
{
|
|
GdkColor bg_color;
|
|
|
|
g_assert (bg->type == META_TEXTURE_SOLID);
|
|
g_assert (fg->type != META_TEXTURE_COMPOSITE);
|
|
g_assert (fg->type != META_TEXTURE_SHAPE_LIST);
|
|
|
|
meta_color_spec_render (bg->data.solid.color_spec,
|
|
widget,
|
|
&bg_color);
|
|
|
|
switch (fg->type)
|
|
{
|
|
case META_TEXTURE_SOLID:
|
|
{
|
|
GdkColor fg_color;
|
|
|
|
meta_color_spec_render (fg->data.solid.color_spec,
|
|
widget,
|
|
&fg_color);
|
|
|
|
color_composite (&bg_color, &fg_color,
|
|
alpha, &fg_color);
|
|
|
|
draw_color_rectangle (widget, drawable, &fg_color, clip,
|
|
x, y, width, height);
|
|
}
|
|
break;
|
|
|
|
case META_TEXTURE_GRADIENT:
|
|
/* FIXME I think we could just composite all the colors in
|
|
* the gradient prior to generating the gradient?
|
|
*/
|
|
/* FALL THRU */
|
|
case META_TEXTURE_IMAGE:
|
|
{
|
|
GdkPixbuf *pixbuf;
|
|
GdkPixbuf *composited;
|
|
|
|
pixbuf = meta_texture_spec_render (fg, widget, mode, 255,
|
|
width, height);
|
|
|
|
if (pixbuf == NULL)
|
|
return;
|
|
|
|
composited = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
|
|
gdk_pixbuf_get_has_alpha (pixbuf), 8,
|
|
gdk_pixbuf_get_width (pixbuf),
|
|
gdk_pixbuf_get_height (pixbuf));
|
|
|
|
if (composited == NULL)
|
|
{
|
|
g_object_unref (G_OBJECT (pixbuf));
|
|
return;
|
|
}
|
|
|
|
gdk_pixbuf_composite_color (pixbuf,
|
|
composited,
|
|
0, 0,
|
|
gdk_pixbuf_get_width (pixbuf),
|
|
gdk_pixbuf_get_height (pixbuf),
|
|
0.0, 0.0, /* offsets */
|
|
1.0, 1.0, /* scale */
|
|
GDK_INTERP_BILINEAR,
|
|
255 * alpha,
|
|
0, 0, /* check offsets */
|
|
0, /* check size */
|
|
GDK_COLOR_RGB (bg_color),
|
|
GDK_COLOR_RGB (bg_color));
|
|
|
|
/* Need to draw background since pixbuf is not
|
|
* necessarily covering the whole thing
|
|
*/
|
|
draw_color_rectangle (widget, drawable, &bg_color, clip,
|
|
x, y, width, height);
|
|
|
|
render_pixbuf_aligned (drawable, clip, composited,
|
|
xalign, yalign,
|
|
x, y, width, height);
|
|
|
|
g_object_unref (G_OBJECT (pixbuf));
|
|
g_object_unref (G_OBJECT (composited));
|
|
}
|
|
break;
|
|
|
|
case META_TEXTURE_BLANK:
|
|
case META_TEXTURE_COMPOSITE:
|
|
case META_TEXTURE_SHAPE_LIST:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
draw_bg_gradient_composite (const MetaTextureSpec *bg,
|
|
const MetaTextureSpec *fg,
|
|
double alpha,
|
|
GtkWidget *widget,
|
|
GdkDrawable *drawable,
|
|
const GdkRectangle *clip,
|
|
MetaTextureDrawMode mode,
|
|
double xalign,
|
|
double yalign,
|
|
int x,
|
|
int y,
|
|
int width,
|
|
int height)
|
|
{
|
|
g_assert (bg->type == META_TEXTURE_GRADIENT);
|
|
g_assert (fg->type != META_TEXTURE_COMPOSITE);
|
|
g_assert (fg->type != META_TEXTURE_SHAPE_LIST);
|
|
|
|
switch (fg->type)
|
|
{
|
|
case META_TEXTURE_SOLID:
|
|
case META_TEXTURE_GRADIENT:
|
|
case META_TEXTURE_IMAGE:
|
|
{
|
|
GdkPixbuf *bg_pixbuf;
|
|
GdkPixbuf *fg_pixbuf;
|
|
GdkPixbuf *composited;
|
|
int fg_width, fg_height;
|
|
|
|
bg_pixbuf = meta_texture_spec_render (bg, widget, mode, 255,
|
|
width, height);
|
|
|
|
if (bg_pixbuf == NULL)
|
|
return;
|
|
|
|
fg_pixbuf = meta_texture_spec_render (fg, widget, mode, 255,
|
|
width, height);
|
|
|
|
if (fg_pixbuf == NULL)
|
|
{
|
|
g_object_unref (G_OBJECT (bg_pixbuf));
|
|
return;
|
|
}
|
|
|
|
/* gradients always fill the entire target area */
|
|
g_assert (gdk_pixbuf_get_width (bg_pixbuf) == width);
|
|
g_assert (gdk_pixbuf_get_height (bg_pixbuf) == height);
|
|
|
|
composited = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
|
|
gdk_pixbuf_get_has_alpha (bg_pixbuf), 8,
|
|
gdk_pixbuf_get_width (bg_pixbuf),
|
|
gdk_pixbuf_get_height (bg_pixbuf));
|
|
|
|
if (composited == NULL)
|
|
{
|
|
g_object_unref (G_OBJECT (bg_pixbuf));
|
|
g_object_unref (G_OBJECT (fg_pixbuf));
|
|
return;
|
|
}
|
|
|
|
fg_width = gdk_pixbuf_get_width (fg_pixbuf);
|
|
fg_height = gdk_pixbuf_get_height (fg_pixbuf);
|
|
|
|
/* If we wanted to be all cool we could deal with the
|
|
* offsets and try to composite only in the clip rectangle,
|
|
* but I just don't care enough to figure it out.
|
|
*/
|
|
|
|
gdk_pixbuf_composite (fg_pixbuf,
|
|
composited,
|
|
x + (width - fg_width) * xalign,
|
|
y + (height - fg_height) * yalign,
|
|
gdk_pixbuf_get_width (fg_pixbuf),
|
|
gdk_pixbuf_get_height (fg_pixbuf),
|
|
0.0, 0.0, /* offsets */
|
|
1.0, 1.0, /* scale */
|
|
GDK_INTERP_BILINEAR,
|
|
255 * alpha);
|
|
|
|
render_pixbuf (drawable, clip, composited, x, y);
|
|
|
|
g_object_unref (G_OBJECT (bg_pixbuf));
|
|
g_object_unref (G_OBJECT (fg_pixbuf));
|
|
g_object_unref (G_OBJECT (composited));
|
|
}
|
|
break;
|
|
|
|
case META_TEXTURE_BLANK:
|
|
case META_TEXTURE_SHAPE_LIST:
|
|
case META_TEXTURE_COMPOSITE:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
guint
|
|
meta_theme_earliest_version_with_button (MetaButtonType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case META_BUTTON_TYPE_CLOSE:
|
|
case META_BUTTON_TYPE_MAXIMIZE:
|
|
case META_BUTTON_TYPE_MINIMIZE:
|
|
case META_BUTTON_TYPE_MENU:
|
|
case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND:
|
|
case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND:
|
|
case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND:
|
|
case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND:
|
|
case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND:
|
|
case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND:
|
|
return 1;
|
|
|
|
case META_BUTTON_TYPE_SHADE:
|
|
case META_BUTTON_TYPE_ABOVE:
|
|
case META_BUTTON_TYPE_STICK:
|
|
case META_BUTTON_TYPE_UNSHADE:
|
|
case META_BUTTON_TYPE_UNABOVE:
|
|
case META_BUTTON_TYPE_UNSTICK:
|
|
return 2;
|
|
|
|
default:
|
|
meta_warning("Unknown button %d\n", type);
|
|
return 1;
|
|
}
|
|
}
|