mirror of
https://github.com/brl/mutter.git
synced 2024-12-25 12:32:05 +00:00
e377e82cfd
This isn't happening anytime soon.
6562 lines
186 KiB
C
6562 lines
186 KiB
C
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
|
|
|
/*
|
|
* 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/*
|
|
* SECTION:theme
|
|
* @title: MetaTheme
|
|
* @short_description: Metacity Theme Rendering
|
|
*
|
|
* The window decorations drawn by Metacity are described by files on disk
|
|
* known internally as "themes" (externally as "window border themes" on
|
|
* http://art.gnome.org/themes/metacity/ or "Metacity themes"). This file
|
|
* contains most of the code necessary to support themes; it does not
|
|
* contain the XML parser, which is in theme-parser.c.
|
|
*/
|
|
|
|
/*
|
|
* FIXME: This is a big file with lots of different subsystems, which might
|
|
* be better split out into separate files.
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include "theme-private.h"
|
|
#include "frames.h" /* for META_TYPE_FRAMES */
|
|
#include "util-private.h"
|
|
#include <meta/gradient.h>
|
|
#include <meta/prefs.h>
|
|
#include <gtk/gtk.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
|
|
#define GDK_COLOR_RGBA(color) \
|
|
((guint32) (0xff | \
|
|
((int)((color).red * 255) << 24) | \
|
|
((int)((color).green * 255) << 16) | \
|
|
((int)((color).blue * 255) << 8)))
|
|
|
|
#define GDK_COLOR_RGB(color) \
|
|
((guint32) (((int)((color).red * 255) << 16) | \
|
|
((int)((color).green * 255) << 8) | \
|
|
((int)((color).blue * 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 (GdkRGBA *a,
|
|
GdkRGBA *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);
|
|
|
|
/*
|
|
* The current theme. (Themes are singleton.)
|
|
*/
|
|
static MetaTheme *meta_current_theme = NULL;
|
|
|
|
static GdkPixbuf *
|
|
colorize_pixbuf (GdkPixbuf *orig,
|
|
GdkRGBA *new_color)
|
|
{
|
|
GdkPixbuf *pixbuf;
|
|
double intensity;
|
|
int x, y;
|
|
const guchar *src;
|
|
guchar *dest;
|
|
int orig_rowstride;
|
|
int dest_rowstride;
|
|
int width, height;
|
|
gboolean has_alpha;
|
|
const guchar *src_pixels;
|
|
guchar *dest_pixels;
|
|
|
|
pixbuf = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (orig), gdk_pixbuf_get_has_alpha (orig),
|
|
gdk_pixbuf_get_bits_per_sample (orig),
|
|
gdk_pixbuf_get_width (orig), gdk_pixbuf_get_height (orig));
|
|
|
|
if (pixbuf == NULL)
|
|
return NULL;
|
|
|
|
orig_rowstride = gdk_pixbuf_get_rowstride (orig);
|
|
dest_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
|
|
width = gdk_pixbuf_get_width (pixbuf);
|
|
height = gdk_pixbuf_get_height (pixbuf);
|
|
has_alpha = gdk_pixbuf_get_has_alpha (orig);
|
|
src_pixels = gdk_pixbuf_get_pixels (orig);
|
|
dest_pixels = gdk_pixbuf_get_pixels (pixbuf);
|
|
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
src = src_pixels + y * orig_rowstride;
|
|
dest = dest_pixels + y * dest_rowstride;
|
|
|
|
for (x = 0; x < width; x++)
|
|
{
|
|
double dr, dg, db;
|
|
|
|
intensity = INTENSITY (src[0], src[1], src[2]) / 255.0;
|
|
|
|
if (intensity <= 0.5)
|
|
{
|
|
/* Go from black at intensity = 0.0 to new_color at intensity = 0.5 */
|
|
dr = new_color->red * intensity * 2.0;
|
|
dg = new_color->green * intensity * 2.0;
|
|
db = new_color->blue * intensity * 2.0;
|
|
}
|
|
else
|
|
{
|
|
/* Go from new_color at intensity = 0.5 to white at intensity = 1.0 */
|
|
dr = new_color->red + (1.0 - new_color->red) * (intensity - 0.5) * 2.0;
|
|
dg = new_color->green + (1.0 - new_color->green) * (intensity - 0.5) * 2.0;
|
|
db = new_color->blue + (1.0 - new_color->blue) * (intensity - 0.5) * 2.0;
|
|
}
|
|
|
|
dest[0] = CLAMP_UCHAR (255 * dr);
|
|
dest[1] = CLAMP_UCHAR (255 * dg);
|
|
dest[2] = CLAMP_UCHAR (255 * db);
|
|
|
|
if (has_alpha)
|
|
{
|
|
dest[3] = src[3];
|
|
src += 4;
|
|
dest += 4;
|
|
}
|
|
else
|
|
{
|
|
src += 3;
|
|
dest += 3;
|
|
}
|
|
}
|
|
}
|
|
|
|
return pixbuf;
|
|
}
|
|
|
|
static void
|
|
color_composite (const GdkRGBA *bg,
|
|
const GdkRGBA *fg,
|
|
double alpha,
|
|
GdkRGBA *color)
|
|
{
|
|
*color = *bg;
|
|
color->red = color->red + (fg->red - color->red) * alpha;
|
|
color->green = color->green + (fg->green - color->green) * alpha;
|
|
color->blue = color->blue + (fg->blue - color->blue) * alpha;
|
|
color->alpha = color->alpha + (fg->alpha - color->alpha) * alpha;
|
|
}
|
|
|
|
/**
|
|
* init_border:
|
|
* @border: The border whose fields should be reset.
|
|
*
|
|
* Sets all the fields of a border to dummy values.
|
|
*/
|
|
static void
|
|
init_border (GtkBorder *border)
|
|
{
|
|
border->top = -1;
|
|
border->bottom = -1;
|
|
border->left = -1;
|
|
border->right = -1;
|
|
}
|
|
|
|
/**
|
|
* meta_frame_layout_new: (skip)
|
|
*
|
|
* Creates a new, empty MetaFrameLayout. The fields will be set to dummy
|
|
* values.
|
|
*
|
|
* Returns: The newly created MetaFrameLayout.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* validate_geometry_value:
|
|
* @val: The value to check
|
|
* @name: The name to use in the error message
|
|
* @error: (out): Set to an error if val was not initialised
|
|
*
|
|
* Ensures that the theme supplied a particular dimension. When a
|
|
* #MetaFrameLayout is created, all its integer fields are set to -1
|
|
* by meta_frame_layout_new(). After an instance of this type
|
|
* should have been initialised, this function checks that
|
|
* a given field is not still at -1. It is never called directly, but
|
|
* rather via the %CHECK_GEOMETRY_VALUE and %CHECK_GEOMETRY_BORDER
|
|
* macros.
|
|
*/
|
|
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,
|
|
MetaFrameType type,
|
|
MetaFrameBorders *borders)
|
|
{
|
|
int buttons_height, title_height, draggable_borders;
|
|
|
|
meta_frame_borders_clear (borders);
|
|
|
|
/* For a full-screen window, we don't have any borders, visible or not. */
|
|
if (flags & META_FRAME_FULLSCREEN)
|
|
return;
|
|
|
|
g_return_if_fail (layout != 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;
|
|
|
|
borders->visible.top = MAX (buttons_height, title_height);
|
|
borders->visible.left = layout->left_width;
|
|
borders->visible.right = layout->right_width;
|
|
borders->visible.bottom = layout->bottom_height;
|
|
|
|
draggable_borders = meta_prefs_get_draggable_border_width ();
|
|
|
|
if (flags & META_FRAME_ALLOWS_HORIZONTAL_RESIZE)
|
|
{
|
|
borders->invisible.left = MAX (0, draggable_borders - borders->visible.left);
|
|
borders->invisible.right = MAX (0, draggable_borders - borders->visible.right);
|
|
}
|
|
|
|
if (flags & META_FRAME_ALLOWS_VERTICAL_RESIZE)
|
|
{
|
|
borders->invisible.bottom = MAX (0, draggable_borders - borders->visible.bottom);
|
|
|
|
/* borders.visible.top is the height of the *title bar*. We can't do the same
|
|
* algorithm here, titlebars are expectedly much bigger. Just subtract a couple
|
|
* pixels to get a proper feel. */
|
|
if (type != META_FRAME_TYPE_ATTACHED)
|
|
borders->invisible.top = MAX (0, draggable_borders - 2);
|
|
}
|
|
|
|
borders->total.left = borders->invisible.left + borders->visible.left;
|
|
borders->total.right = borders->invisible.right + borders->visible.right;
|
|
borders->total.bottom = borders->invisible.bottom + borders->visible.bottom;
|
|
borders->total.top = borders->invisible.top + borders->visible.top;
|
|
}
|
|
|
|
static MetaButtonType
|
|
map_button_function_to_type (MetaButtonFunction function)
|
|
{
|
|
switch (function)
|
|
{
|
|
case META_BUTTON_FUNCTION_SHADE:
|
|
return META_BUTTON_TYPE_SHADE;
|
|
case META_BUTTON_FUNCTION_ABOVE:
|
|
return META_BUTTON_TYPE_ABOVE;
|
|
case META_BUTTON_FUNCTION_STICK:
|
|
return META_BUTTON_TYPE_STICK;
|
|
case META_BUTTON_FUNCTION_UNSHADE:
|
|
return META_BUTTON_TYPE_UNSHADE;
|
|
case META_BUTTON_FUNCTION_UNABOVE:
|
|
return META_BUTTON_TYPE_UNABOVE;
|
|
case META_BUTTON_FUNCTION_UNSTICK:
|
|
return META_BUTTON_TYPE_UNSTICK;
|
|
case META_BUTTON_FUNCTION_MENU:
|
|
return META_BUTTON_TYPE_MENU;
|
|
case META_BUTTON_FUNCTION_APPMENU:
|
|
return META_BUTTON_TYPE_APPMENU;
|
|
case META_BUTTON_FUNCTION_MINIMIZE:
|
|
return META_BUTTON_TYPE_MINIMIZE;
|
|
case META_BUTTON_FUNCTION_MAXIMIZE:
|
|
return META_BUTTON_TYPE_MAXIMIZE;
|
|
case META_BUTTON_FUNCTION_CLOSE:
|
|
return META_BUTTON_TYPE_CLOSE;
|
|
case META_BUTTON_FUNCTION_LAST:
|
|
return META_BUTTON_TYPE_LAST;
|
|
}
|
|
|
|
return META_BUTTON_TYPE_LAST;
|
|
}
|
|
|
|
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_APPMENU:
|
|
if (flags & META_FRAME_ALLOWS_APPMENU)
|
|
return &fgeom->appmenu_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 */
|
|
}
|
|
|
|
static void
|
|
meta_frame_layout_calc_geometry (const MetaFrameLayout *layout,
|
|
int text_height,
|
|
MetaFrameFlags flags,
|
|
int client_width,
|
|
int client_height,
|
|
const MetaButtonLayout *button_layout,
|
|
MetaFrameType type,
|
|
MetaFrameGeometry *fgeom,
|
|
MetaTheme *theme)
|
|
{
|
|
int i, n_left, n_right, n_left_spacers, n_right_spacers;
|
|
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];
|
|
gboolean left_buttons_has_spacer[MAX_BUTTONS_PER_CORNER];
|
|
GdkRectangle *right_bg_rects[MAX_BUTTONS_PER_CORNER];
|
|
gboolean right_buttons_has_spacer[MAX_BUTTONS_PER_CORNER];
|
|
|
|
MetaFrameBorders borders;
|
|
|
|
meta_frame_layout_get_borders (layout, text_height,
|
|
flags, type,
|
|
&borders);
|
|
|
|
fgeom->borders = borders;
|
|
|
|
width = client_width + borders.total.left + borders.total.right;
|
|
|
|
height = borders.total.top + borders.total.bottom;
|
|
if (!(flags & META_FRAME_SHADED))
|
|
height += client_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 = borders.visible.top - 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;
|
|
n_left_spacers = 0;
|
|
n_right_spacers = 0;
|
|
|
|
if (!layout->hide_buttons)
|
|
{
|
|
/* Try to fill in rects */
|
|
for (i = 0; i < MAX_BUTTONS_PER_CORNER && button_layout->left_buttons[i] != META_BUTTON_FUNCTION_LAST; i++)
|
|
{
|
|
left_func_rects[n_left] = rect_for_function (fgeom, flags,
|
|
button_layout->left_buttons[i],
|
|
theme);
|
|
if (left_func_rects[n_left] != NULL)
|
|
{
|
|
left_buttons_has_spacer[n_left] = button_layout->left_buttons_has_spacer[i];
|
|
if (button_layout->left_buttons_has_spacer[i])
|
|
++n_left_spacers;
|
|
|
|
++n_left;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < MAX_BUTTONS_PER_CORNER && button_layout->right_buttons[i] != META_BUTTON_FUNCTION_LAST; i++)
|
|
{
|
|
right_func_rects[n_right] = rect_for_function (fgeom, flags,
|
|
button_layout->right_buttons[i],
|
|
theme);
|
|
if (right_func_rects[n_right] != NULL)
|
|
{
|
|
right_buttons_has_spacer[n_right] = button_layout->right_buttons_has_spacer[i];
|
|
if (button_layout->right_buttons_has_spacer[i])
|
|
++n_right_spacers;
|
|
|
|
++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 (n_left == 1)
|
|
left_bg_rects[i] = &fgeom->left_single_background;
|
|
else if (i == 0)
|
|
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++)
|
|
{
|
|
if (n_right == 1)
|
|
right_bg_rects[i] = &fgeom->right_single_background;
|
|
else 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 += (button_width * 0.75) * n_left_spacers;
|
|
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 += (button_width * 0.75) * n_right_spacers;
|
|
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 */
|
|
|
|
/* First try to remove separators */
|
|
if (n_left_spacers > 0)
|
|
{
|
|
left_buttons_has_spacer[--n_left_spacers] = FALSE;
|
|
continue;
|
|
}
|
|
else if (n_right_spacers > 0)
|
|
{
|
|
right_buttons_has_spacer[--n_right_spacers] = FALSE;
|
|
continue;
|
|
}
|
|
|
|
/* 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 if (strip_button (right_func_rects, right_bg_rects,
|
|
&n_right, &fgeom->appmenu_rect))
|
|
continue;
|
|
else if (strip_button (left_func_rects, left_bg_rects,
|
|
&n_left, &fgeom->appmenu_rect))
|
|
continue;
|
|
else
|
|
{
|
|
meta_bug ("Could not find a button to strip. n_left = %d n_right = %d\n",
|
|
n_left, n_right);
|
|
}
|
|
}
|
|
|
|
/* Save the button layout */
|
|
fgeom->button_layout = *button_layout;
|
|
fgeom->n_left_buttons = n_left;
|
|
fgeom->n_right_buttons = n_right;
|
|
|
|
/* center buttons vertically */
|
|
button_y = (borders.visible.top -
|
|
(button_height + layout->button_border.top + layout->button_border.bottom)) / 2 + layout->button_border.top + borders.invisible.top;
|
|
|
|
/* right edge of farthest-right button */
|
|
x = width - layout->right_titlebar_edge - borders.invisible.right;
|
|
|
|
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;
|
|
if (right_buttons_has_spacer[i])
|
|
rect->visible.x -= (button_width * 0.75);
|
|
|
|
rect->visible.y = button_y;
|
|
rect->visible.width = button_width;
|
|
rect->visible.height = button_height;
|
|
|
|
if (flags & META_FRAME_MAXIMIZED ||
|
|
flags & META_FRAME_TILED_LEFT ||
|
|
flags & META_FRAME_TILED_RIGHT)
|
|
{
|
|
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 + borders.invisible.left;
|
|
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;
|
|
if (left_buttons_has_spacer[i])
|
|
x += (button_width * 0.75);
|
|
|
|
*(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 + borders.invisible.top;
|
|
fgeom->title_rect.width = title_right_edge - fgeom->title_rect.x;
|
|
fgeom->title_rect.height = borders.visible.top - 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 (borders.visible.top + borders.visible.left >= min_size_for_rounding)
|
|
fgeom->top_left_corner_rounded_radius = layout->top_left_corner_rounded_radius;
|
|
if (borders.visible.top + borders.visible.right >= min_size_for_rounding)
|
|
fgeom->top_right_corner_rounded_radius = layout->top_right_corner_rounded_radius;
|
|
|
|
if (borders.visible.bottom + borders.visible.left >= min_size_for_rounding)
|
|
fgeom->bottom_left_corner_rounded_radius = layout->bottom_left_corner_rounded_radius;
|
|
if (borders.visible.bottom + borders.visible.right >= min_size_for_rounding)
|
|
fgeom->bottom_right_corner_rounded_radius = layout->bottom_right_corner_rounded_radius;
|
|
}
|
|
|
|
/**
|
|
* meta_gradient_spec_new: (skip)
|
|
*
|
|
*/
|
|
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,
|
|
GtkStyleContext *style,
|
|
int width,
|
|
int height)
|
|
{
|
|
int n_colors;
|
|
GdkRGBA *colors;
|
|
GSList *tmp;
|
|
int i;
|
|
GdkPixbuf *pixbuf;
|
|
|
|
n_colors = g_slist_length (spec->color_specs);
|
|
|
|
if (n_colors == 0)
|
|
return NULL;
|
|
|
|
colors = g_new (GdkRGBA, n_colors);
|
|
|
|
i = 0;
|
|
tmp = spec->color_specs;
|
|
while (tmp != NULL)
|
|
{
|
|
meta_color_spec_render (tmp->data, style, &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;
|
|
}
|
|
|
|
/**
|
|
* meta_alpha_gradient_spec_new: (skip)
|
|
*
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* meta_color_spec_new: (skip)
|
|
*
|
|
*/
|
|
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_GTK_CUSTOM:
|
|
size += sizeof (dummy.data.gtkcustom);
|
|
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_GTK_CUSTOM:
|
|
if (spec->data.gtkcustom.color_name)
|
|
g_free (spec->data.gtkcustom.color_name);
|
|
if (spec->data.gtkcustom.fallback)
|
|
meta_color_spec_free (spec->data.gtkcustom.fallback);
|
|
DEBUG_FILL_STRUCT (&spec->data.gtkcustom);
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* meta_color_spec_new_from_string: (skip)
|
|
*
|
|
*/
|
|
MetaColorSpec*
|
|
meta_color_spec_new_from_string (const char *str,
|
|
GError **err)
|
|
{
|
|
MetaColorSpec *spec;
|
|
|
|
spec = NULL;
|
|
|
|
if (strncmp (str, "gtk:custom", 10) == 0)
|
|
{
|
|
const char *color_name_start, *fallback_str_start, *end;
|
|
char *color_name;
|
|
MetaColorSpec *fallback = NULL;
|
|
static gboolean debug, debug_set = FALSE;
|
|
|
|
if (!debug_set)
|
|
{
|
|
debug = g_getenv ("MUTTER_DISABLE_FALLBACK_COLOR") != NULL;
|
|
debug_set = TRUE;
|
|
}
|
|
|
|
if (str[10] != '(')
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("GTK custom color specification must have color name and fallback in parentheses, e.g. gtk:custom(foo,bar); could not parse \"%s\""),
|
|
str);
|
|
return NULL;
|
|
}
|
|
|
|
color_name_start = str + 11;
|
|
|
|
fallback_str_start = color_name_start;
|
|
while (*fallback_str_start && *fallback_str_start != ',')
|
|
{
|
|
if (!(g_ascii_isalnum (*fallback_str_start)
|
|
|| *fallback_str_start == '-'
|
|
|| *fallback_str_start == '_'))
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Invalid character '%c' in color_name parameter of gtk:custom, only A-Za-z0-9-_ are valid"),
|
|
*fallback_str_start);
|
|
return NULL;
|
|
}
|
|
fallback_str_start++;
|
|
}
|
|
fallback_str_start++;
|
|
|
|
end = strrchr (str, ')');
|
|
|
|
if (color_name_start == NULL || fallback_str_start == NULL || end == NULL)
|
|
{
|
|
g_set_error (err, META_THEME_ERROR,
|
|
META_THEME_ERROR_FAILED,
|
|
_("Gtk:custom format is \"gtk:custom(color_name,fallback)\", \"%s\" does not fit the format"),
|
|
str);
|
|
return NULL;
|
|
}
|
|
|
|
if (!debug)
|
|
{
|
|
char *fallback_str;
|
|
fallback_str = g_strndup (fallback_str_start,
|
|
end - fallback_str_start);
|
|
fallback = meta_color_spec_new_from_string (fallback_str, err);
|
|
g_free (fallback_str);
|
|
}
|
|
else
|
|
{
|
|
fallback = meta_color_spec_new_from_string ("pink", err);
|
|
}
|
|
|
|
if (fallback == NULL)
|
|
return NULL;
|
|
|
|
color_name = g_strndup (color_name_start,
|
|
fallback_str_start - color_name_start - 1);
|
|
|
|
spec = meta_color_spec_new (META_COLOR_SPEC_GTK_CUSTOM);
|
|
spec->data.gtkcustom.color_name = color_name;
|
|
spec->data.gtkcustom.fallback = fallback;
|
|
}
|
|
else if (strncmp (str, "gtk:", 4) == 0)
|
|
{
|
|
/* GTK color */
|
|
const char *bracket;
|
|
const char *end_bracket;
|
|
char *tmp;
|
|
GtkStateFlags 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.component < META_GTK_COLOR_LAST);
|
|
}
|
|
else if (strncmp (str, "blend/", 6) == 0)
|
|
{
|
|
/* 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 (strncmp (str, "shade/", 6) == 0)
|
|
{
|
|
/* 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_rgba_parse (&spec->data.basic.color, str))
|
|
{
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* meta_color_spec_new_gtk: (skip)
|
|
*
|
|
*/
|
|
MetaColorSpec*
|
|
meta_color_spec_new_gtk (MetaGtkColorComponent component,
|
|
GtkStateFlags state)
|
|
{
|
|
MetaColorSpec *spec;
|
|
|
|
spec = meta_color_spec_new (META_COLOR_SPEC_GTK);
|
|
|
|
spec->data.gtk.component = component;
|
|
spec->data.gtk.state = state;
|
|
|
|
return spec;
|
|
}
|
|
|
|
/* Based on set_color() in gtkstyle.c */
|
|
#define LIGHTNESS_MULT 1.3
|
|
#define DARKNESS_MULT 0.7
|
|
void
|
|
meta_gtk_style_get_light_color (GtkStyleContext *style,
|
|
GtkStateFlags state,
|
|
GdkRGBA *color)
|
|
{
|
|
gtk_style_context_get_background_color (style, state, color);
|
|
gtk_style_shade (color, color, LIGHTNESS_MULT);
|
|
}
|
|
|
|
void
|
|
meta_gtk_style_get_dark_color (GtkStyleContext *style,
|
|
GtkStateFlags state,
|
|
GdkRGBA *color)
|
|
{
|
|
gtk_style_context_get_background_color (style, state, color);
|
|
gtk_style_shade (color, color, DARKNESS_MULT);
|
|
}
|
|
|
|
static void
|
|
meta_set_color_from_style (GdkRGBA *color,
|
|
GtkStyleContext *context,
|
|
GtkStateFlags state,
|
|
MetaGtkColorComponent component)
|
|
{
|
|
GdkRGBA other;
|
|
|
|
switch (component)
|
|
{
|
|
case META_GTK_COLOR_BG:
|
|
case META_GTK_COLOR_BASE:
|
|
gtk_style_context_get_background_color (context, state, color);
|
|
break;
|
|
case META_GTK_COLOR_FG:
|
|
case META_GTK_COLOR_TEXT:
|
|
gtk_style_context_get_color (context, state, color);
|
|
break;
|
|
case META_GTK_COLOR_TEXT_AA:
|
|
gtk_style_context_get_color (context, state, color);
|
|
meta_set_color_from_style (&other, context, state, META_GTK_COLOR_BASE);
|
|
|
|
color->red = (color->red + other.red) / 2;
|
|
color->green = (color->green + other.green) / 2;
|
|
color->blue = (color->blue + other.blue) / 2;
|
|
break;
|
|
case META_GTK_COLOR_MID:
|
|
meta_gtk_style_get_light_color (context, state, color);
|
|
meta_gtk_style_get_dark_color (context, state, &other);
|
|
|
|
color->red = (color->red + other.red) / 2;
|
|
color->green = (color->green + other.green) / 2;
|
|
color->blue = (color->blue + other.blue) / 2;
|
|
break;
|
|
case META_GTK_COLOR_LIGHT:
|
|
meta_gtk_style_get_light_color (context, state, color);
|
|
break;
|
|
case META_GTK_COLOR_DARK:
|
|
meta_gtk_style_get_dark_color (context, state, color);
|
|
break;
|
|
case META_GTK_COLOR_LAST:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
meta_set_custom_color_from_style (GdkRGBA *color,
|
|
GtkStyleContext *context,
|
|
char *color_name,
|
|
MetaColorSpec *fallback)
|
|
{
|
|
if (!gtk_style_context_lookup_color (context, color_name, color))
|
|
meta_color_spec_render (fallback, context, color);
|
|
}
|
|
|
|
void
|
|
meta_color_spec_render (MetaColorSpec *spec,
|
|
GtkStyleContext *context,
|
|
GdkRGBA *color)
|
|
{
|
|
g_return_if_fail (spec != NULL);
|
|
g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
|
|
|
|
switch (spec->type)
|
|
{
|
|
case META_COLOR_SPEC_BASIC:
|
|
*color = spec->data.basic.color;
|
|
break;
|
|
|
|
case META_COLOR_SPEC_GTK:
|
|
meta_set_color_from_style (color,
|
|
context,
|
|
spec->data.gtk.state,
|
|
spec->data.gtk.component);
|
|
break;
|
|
|
|
case META_COLOR_SPEC_GTK_CUSTOM:
|
|
meta_set_custom_color_from_style (color,
|
|
context,
|
|
spec->data.gtkcustom.color_name,
|
|
spec->data.gtkcustom.fallback);
|
|
break;
|
|
|
|
case META_COLOR_SPEC_BLEND:
|
|
{
|
|
GdkRGBA bg, fg;
|
|
|
|
meta_color_spec_render (spec->data.blend.background, context, &bg);
|
|
meta_color_spec_render (spec->data.blend.foreground, context, &fg);
|
|
|
|
color_composite (&bg, &fg, spec->data.blend.alpha,
|
|
&spec->data.blend.color);
|
|
|
|
*color = spec->data.blend.color;
|
|
}
|
|
break;
|
|
|
|
case META_COLOR_SPEC_SHADE:
|
|
{
|
|
meta_color_spec_render (spec->data.shade.base, context,
|
|
&spec->data.shade.color);
|
|
|
|
gtk_style_shade (&spec->data.shade.color,
|
|
&spec->data.shade.color, spec->data.shade.factor);
|
|
|
|
*color = spec->data.shade.color;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* op_name:
|
|
* @type: an operation, such as addition
|
|
*
|
|
* Represents an operation as a string.
|
|
*
|
|
* Returns: a string, such as "+"
|
|
*/
|
|
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>";
|
|
}
|
|
|
|
/**
|
|
* op_from_string:
|
|
* @p: a pointer into a string representing an operation; part of an
|
|
* expression somewhere, so not null-terminated
|
|
* @len: set to the length of the string found. Set to 0 if none is.
|
|
*
|
|
* Parses a string and returns an operation.
|
|
*
|
|
* Returns: the operation found. If none was, returns %POS_OP_NONE.
|
|
*/
|
|
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 (strncmp (p, "`max`", 5) == 0)
|
|
{
|
|
*len = 5;
|
|
return POS_OP_MAX;
|
|
}
|
|
else if (strncmp (p, "`min`", 5) == 0)
|
|
{
|
|
*len = 5;
|
|
return POS_OP_MIN;
|
|
}
|
|
}
|
|
|
|
return POS_OP_NONE;
|
|
}
|
|
|
|
/**
|
|
* free_tokens:
|
|
* @tokens: an array of tokens to be freed
|
|
* @n_tokens: how many tokens are in the array.
|
|
*
|
|
* Frees an array of tokens. All the tokens and their associated memory
|
|
* will be freed.
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* parse_number:
|
|
* @p: a pointer into a string representing an operation; part of an
|
|
* expression somewhere, so not null-terminated
|
|
* @end_return: set to a pointer to the end of the number found; but
|
|
* not updated if no number was found at all
|
|
* @next: set to either an integer or a float token
|
|
* @err: (out): set to the problem if there was a problem
|
|
*
|
|
* Tokenises a number in an expression.
|
|
*
|
|
* FIXME: The "while (*start)..." part: what's wrong with strchr-ish things?
|
|
* FIXME: The name is wrong: it doesn't parse anything.
|
|
*
|
|
* Returns: %TRUE if a valid number was found, FALSE otherwise (and "err" will
|
|
* have been set)
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Whether a variable can validly appear as part of the name of a variable.
|
|
*/
|
|
#define IS_VARIABLE_CHAR(c) (g_ascii_isalpha ((c)) || (c) == '_')
|
|
|
|
#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
|
|
|
|
/**
|
|
* pos_tokenize:
|
|
* @expr: The expression
|
|
* @tokens_p: (out): The resulting tokens
|
|
* @n_tokens_p: (out): The number of resulting tokens
|
|
* @err: (out): set to the problem if there was a problem
|
|
|
|
* Tokenises an expression.
|
|
*
|
|
* Returns: %TRUE if the expression was successfully tokenised; %FALSE otherwise.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* PosExprType:
|
|
*
|
|
* The type of a PosExpr: either integer, double, or an operation.
|
|
*/
|
|
typedef enum
|
|
{
|
|
POS_EXPR_INT,
|
|
POS_EXPR_DOUBLE,
|
|
POS_EXPR_OPERATOR
|
|
} PosExprType;
|
|
|
|
/**
|
|
* PosExpr:
|
|
*
|
|
* Type and value of an expression in a parsed sequence. We don't
|
|
* keep expressions in a tree; if this is of type %POS_EXPR_OPERATOR,
|
|
* the arguments of the operator will be in the array positions
|
|
* immediately preceding and following this operator; they cannot
|
|
* themselves be operators.
|
|
*
|
|
* FIXME: operator is #gchar; it should really be of #PosOperatorType.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* pos_eval_get_variable:
|
|
* @t: The token representing a variable
|
|
* @result: (out): The value of that variable; not set if the token did
|
|
* not represent a known variable
|
|
* @env: The environment within which t should be evaluated
|
|
* @err: (out): set to the problem if there was a problem
|
|
*
|
|
* There is a predefined set of variables which can appear in an expression.
|
|
* Here we take a token representing a variable, and return the current value
|
|
* of that variable in a particular environment.
|
|
* (The value is always an integer.)
|
|
*
|
|
* There are supposedly some circumstances in which this function can be
|
|
* called from outside Metacity, in which case env->theme will be %NULL, and
|
|
* therefore we can't use it to find out quark values, so we do the comparison
|
|
* using strcmp(), which is slower.
|
|
*
|
|
* FIXME: shouldn't @t be const?
|
|
* FIXME: we should perhaps consider some sort of lookup arrangement into an
|
|
* array; also, the duplication of code is unlovely; perhaps using glib
|
|
* string hashes instead of quarks would fix both problems?
|
|
*
|
|
* Returns: %TRUE if we found the variable asked for, %FALSE if we didn't
|
|
*/
|
|
static gboolean
|
|
pos_eval_get_variable (PosToken *t,
|
|
int *result,
|
|
const MetaPositionExprEnv *env,
|
|
GError **err)
|
|
{
|
|
if (env->theme)
|
|
{
|
|
if (t->d.v.name_quark == env->theme->quark_width)
|
|
*result = env->rect.width;
|
|
else if (t->d.v.name_quark == env->theme->quark_height)
|
|
*result = env->rect.height;
|
|
else if (env->object_width >= 0 &&
|
|
t->d.v.name_quark == env->theme->quark_object_width)
|
|
*result = env->object_width;
|
|
else if (env->object_height >= 0 &&
|
|
t->d.v.name_quark == env->theme->quark_object_height)
|
|
*result = env->object_height;
|
|
else if (t->d.v.name_quark == env->theme->quark_left_width)
|
|
*result = env->left_width;
|
|
else if (t->d.v.name_quark == env->theme->quark_right_width)
|
|
*result = env->right_width;
|
|
else if (t->d.v.name_quark == env->theme->quark_top_height)
|
|
*result = env->top_height;
|
|
else if (t->d.v.name_quark == env->theme->quark_bottom_height)
|
|
*result = env->bottom_height;
|
|
else if (t->d.v.name_quark == env->theme->quark_mini_icon_width)
|
|
*result = env->mini_icon_width;
|
|
else if (t->d.v.name_quark == env->theme->quark_mini_icon_height)
|
|
*result = env->mini_icon_height;
|
|
else if (t->d.v.name_quark == env->theme->quark_icon_width)
|
|
*result = env->icon_width;
|
|
else if (t->d.v.name_quark == env->theme->quark_icon_height)
|
|
*result = env->icon_height;
|
|
else if (t->d.v.name_quark == env->theme->quark_title_width)
|
|
*result = env->title_width;
|
|
else if (t->d.v.name_quark == env->theme->quark_title_height)
|
|
*result = env->title_height;
|
|
else if (t->d.v.name_quark == env->theme->quark_frame_x_center)
|
|
*result = env->frame_x_center;
|
|
else if (t->d.v.name_quark == env->theme->quark_frame_y_center)
|
|
*result = env->frame_y_center;
|
|
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;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (strcmp (t->d.v.name, "width") == 0)
|
|
*result = env->rect.width;
|
|
else if (strcmp (t->d.v.name, "height") == 0)
|
|
*result = env->rect.height;
|
|
else if (env->object_width >= 0 &&
|
|
strcmp (t->d.v.name, "object_width") == 0)
|
|
*result = env->object_width;
|
|
else if (env->object_height >= 0 &&
|
|
strcmp (t->d.v.name, "object_height") == 0)
|
|
*result = env->object_height;
|
|
else if (strcmp (t->d.v.name, "left_width") == 0)
|
|
*result = env->left_width;
|
|
else if (strcmp (t->d.v.name, "right_width") == 0)
|
|
*result = env->right_width;
|
|
else if (strcmp (t->d.v.name, "top_height") == 0)
|
|
*result = env->top_height;
|
|
else if (strcmp (t->d.v.name, "bottom_height") == 0)
|
|
*result = env->bottom_height;
|
|
else if (strcmp (t->d.v.name, "mini_icon_width") == 0)
|
|
*result = env->mini_icon_width;
|
|
else if (strcmp (t->d.v.name, "mini_icon_height") == 0)
|
|
*result = env->mini_icon_height;
|
|
else if (strcmp (t->d.v.name, "icon_width") == 0)
|
|
*result = env->icon_width;
|
|
else if (strcmp (t->d.v.name, "icon_height") == 0)
|
|
*result = env->icon_height;
|
|
else if (strcmp (t->d.v.name, "title_width") == 0)
|
|
*result = env->title_width;
|
|
else if (strcmp (t->d.v.name, "title_height") == 0)
|
|
*result = env->title_height;
|
|
else if (strcmp (t->d.v.name, "frame_x_center") == 0)
|
|
*result = env->frame_x_center;
|
|
else if (strcmp (t->d.v.name, "frame_y_center") == 0)
|
|
*result = env->frame_y_center;
|
|
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;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* pos_eval_helper:
|
|
* @tokens: A list of tokens to evaluate.
|
|
* @n_tokens: How many tokens are in the list.
|
|
* @env: The environment context in which to evaluate the expression.
|
|
* @result: (out): The current value of the expression
|
|
*
|
|
* Evaluates a sequence of tokens within a particular environment context,
|
|
* and returns the current value. May recur if parantheses are found.
|
|
*
|
|
* FIXME: Yes, we really do reparse the expression every time it's evaluated.
|
|
* We should keep the parse tree around all the time and just
|
|
* run the new values through it.
|
|
*/
|
|
static gboolean
|
|
pos_eval_helper (PosToken *tokens,
|
|
int n_tokens,
|
|
const MetaPositionExprEnv *env,
|
|
PosExpr *result,
|
|
GError **err)
|
|
{
|
|
/* Lazy-ass hardcoded limit on number of terms in expression */
|
|
#define MAX_EXPRS 32
|
|
int paren_level;
|
|
int first_paren;
|
|
int i;
|
|
PosExpr exprs[MAX_EXPRS];
|
|
int n_exprs;
|
|
int precedence;
|
|
|
|
/* 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 (!pos_eval_get_variable (t, &exprs[n_exprs].d.int_val, env, err))
|
|
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.
|
|
*/
|
|
|
|
/**
|
|
* pos_eval:
|
|
* @spec: The expression to evaluate.
|
|
* @env: The environment context to evaluate the expression in.
|
|
* @val_p: (out): The integer value of the expression; if the expression
|
|
* is of type float, this will be rounded. If we return
|
|
* %FALSE because the expression is invalid, this will be
|
|
* zero.
|
|
* @err: (out): The error, if anything went wrong.
|
|
*
|
|
* Evaluates an expression.
|
|
*
|
|
* FIXME: Shouldn't @spec be const?
|
|
*
|
|
* Returns: %TRUE if we evaluated the expression successfully; %FALSE otherwise.
|
|
*/
|
|
static gboolean
|
|
pos_eval (MetaDrawSpec *spec,
|
|
const MetaPositionExprEnv *env,
|
|
int *val_p,
|
|
GError **err)
|
|
{
|
|
PosExpr expr;
|
|
|
|
*val_p = 0;
|
|
|
|
if (pos_eval_helper (spec->tokens, spec->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.
|
|
*/
|
|
|
|
/**
|
|
* meta_parse_position_expression: (skip)
|
|
*
|
|
*/
|
|
gboolean
|
|
meta_parse_position_expression (MetaDrawSpec *spec,
|
|
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.
|
|
*/
|
|
int val;
|
|
|
|
if (spec->constant)
|
|
val = spec->value;
|
|
else
|
|
{
|
|
if (pos_eval (spec, env, &spec->value, err) == FALSE)
|
|
{
|
|
g_assert (err == NULL || *err != NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
val = spec->value;
|
|
}
|
|
|
|
if (x_return)
|
|
*x_return = env->rect.x + val;
|
|
if (y_return)
|
|
*y_return = env->rect.y + val;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/**
|
|
* meta_parse_size_expression: (skip)
|
|
*
|
|
*/
|
|
gboolean
|
|
meta_parse_size_expression (MetaDrawSpec *spec,
|
|
const MetaPositionExprEnv *env,
|
|
int *val_return,
|
|
GError **err)
|
|
{
|
|
int val;
|
|
|
|
if (spec->constant)
|
|
val = spec->value;
|
|
else
|
|
{
|
|
if (pos_eval (spec, env, &spec->value, err) == FALSE)
|
|
{
|
|
g_assert (err == NULL || *err != NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
val = spec->value;
|
|
}
|
|
|
|
if (val_return)
|
|
*val_return = MAX (val, 1); /* require that sizes be at least 1x1 */
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* 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.
|
|
*/
|
|
gboolean
|
|
meta_theme_replace_constants (MetaTheme *theme,
|
|
PosToken *tokens,
|
|
int n_tokens,
|
|
GError **err)
|
|
{
|
|
int i;
|
|
double dval;
|
|
int ival;
|
|
gboolean is_constant = TRUE;
|
|
|
|
/* Loop through tokenized string looking for variables to replace */
|
|
for (i = 0; i < n_tokens; i++)
|
|
{
|
|
PosToken *t = &tokens[i];
|
|
|
|
if (t->type == POS_TOKEN_VARIABLE)
|
|
{
|
|
if (meta_theme_lookup_int_constant (theme, t->d.v.name, &ival))
|
|
{
|
|
g_free (t->d.v.name);
|
|
t->type = POS_TOKEN_INT;
|
|
t->d.i.val = ival;
|
|
}
|
|
else if (meta_theme_lookup_float_constant (theme, t->d.v.name, &dval))
|
|
{
|
|
g_free (t->d.v.name);
|
|
t->type = POS_TOKEN_DOUBLE;
|
|
t->d.d.val = dval;
|
|
}
|
|
else
|
|
{
|
|
/* If we've found a variable that cannot be replaced then the
|
|
expression is not a constant expression and we want to
|
|
replace it with a GQuark */
|
|
|
|
t->d.v.name_quark = g_quark_from_string (t->d.v.name);
|
|
is_constant = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return is_constant;
|
|
}
|
|
|
|
static int
|
|
parse_x_position_unchecked (MetaDrawSpec *spec,
|
|
const MetaPositionExprEnv *env)
|
|
{
|
|
int retval;
|
|
GError *error;
|
|
|
|
retval = 0;
|
|
error = NULL;
|
|
if (!meta_parse_position_expression (spec, env, &retval, NULL, &error))
|
|
{
|
|
meta_warning (_("Theme contained an expression that resulted in an error: %s\n"),
|
|
error->message);
|
|
|
|
g_error_free (error);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int
|
|
parse_y_position_unchecked (MetaDrawSpec *spec,
|
|
const MetaPositionExprEnv *env)
|
|
{
|
|
int retval;
|
|
GError *error;
|
|
|
|
retval = 0;
|
|
error = NULL;
|
|
if (!meta_parse_position_expression (spec, env, NULL, &retval, &error))
|
|
{
|
|
meta_warning (_("Theme contained an expression that resulted in an error: %s\n"),
|
|
error->message);
|
|
|
|
g_error_free (error);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int
|
|
parse_size_unchecked (MetaDrawSpec *spec,
|
|
MetaPositionExprEnv *env)
|
|
{
|
|
int retval;
|
|
GError *error;
|
|
|
|
retval = 0;
|
|
error = NULL;
|
|
if (!meta_parse_size_expression (spec, env, &retval, &error))
|
|
{
|
|
meta_warning (_("Theme contained an expression that resulted in an error: %s\n"),
|
|
error->message);
|
|
|
|
g_error_free (error);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
void
|
|
meta_draw_spec_free (MetaDrawSpec *spec)
|
|
{
|
|
if (!spec) return;
|
|
free_tokens (spec->tokens, spec->n_tokens);
|
|
g_slice_free (MetaDrawSpec, spec);
|
|
}
|
|
|
|
/**
|
|
* meta_draw_spec_new: (skip)
|
|
*
|
|
*/
|
|
MetaDrawSpec *
|
|
meta_draw_spec_new (MetaTheme *theme,
|
|
const char *expr,
|
|
GError **error)
|
|
{
|
|
MetaDrawSpec *spec;
|
|
|
|
spec = g_slice_new0 (MetaDrawSpec);
|
|
|
|
pos_tokenize (expr, &spec->tokens, &spec->n_tokens, NULL);
|
|
|
|
spec->constant = meta_theme_replace_constants (theme, spec->tokens,
|
|
spec->n_tokens, NULL);
|
|
if (spec->constant)
|
|
{
|
|
gboolean result;
|
|
|
|
result = pos_eval (spec, NULL, &spec->value, error);
|
|
if (result == FALSE)
|
|
{
|
|
meta_draw_spec_free (spec);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return spec;
|
|
}
|
|
|
|
/**
|
|
* meta_draw_op_new: (skip)
|
|
*
|
|
*/
|
|
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);
|
|
|
|
meta_draw_spec_free (op->data.line.x1);
|
|
meta_draw_spec_free (op->data.line.y1);
|
|
meta_draw_spec_free (op->data.line.x2);
|
|
meta_draw_spec_free (op->data.line.y2);
|
|
break;
|
|
|
|
case META_DRAW_RECTANGLE:
|
|
if (op->data.rectangle.color_spec)
|
|
g_free (op->data.rectangle.color_spec);
|
|
|
|
meta_draw_spec_free (op->data.rectangle.x);
|
|
meta_draw_spec_free (op->data.rectangle.y);
|
|
meta_draw_spec_free (op->data.rectangle.width);
|
|
meta_draw_spec_free (op->data.rectangle.height);
|
|
break;
|
|
|
|
case META_DRAW_ARC:
|
|
if (op->data.arc.color_spec)
|
|
g_free (op->data.arc.color_spec);
|
|
|
|
meta_draw_spec_free (op->data.arc.x);
|
|
meta_draw_spec_free (op->data.arc.y);
|
|
meta_draw_spec_free (op->data.arc.width);
|
|
meta_draw_spec_free (op->data.arc.height);
|
|
break;
|
|
|
|
case META_DRAW_CLIP:
|
|
meta_draw_spec_free (op->data.clip.x);
|
|
meta_draw_spec_free (op->data.clip.y);
|
|
meta_draw_spec_free (op->data.clip.width);
|
|
meta_draw_spec_free (op->data.clip.height);
|
|
break;
|
|
|
|
case META_DRAW_TINT:
|
|
if (op->data.tint.color_spec)
|
|
meta_color_spec_free (op->data.tint.color_spec);
|
|
|
|
if (op->data.tint.alpha_spec)
|
|
meta_alpha_gradient_spec_free (op->data.tint.alpha_spec);
|
|
|
|
meta_draw_spec_free (op->data.tint.x);
|
|
meta_draw_spec_free (op->data.tint.y);
|
|
meta_draw_spec_free (op->data.tint.width);
|
|
meta_draw_spec_free (op->data.tint.height);
|
|
break;
|
|
|
|
case META_DRAW_GRADIENT:
|
|
if (op->data.gradient.gradient_spec)
|
|
meta_gradient_spec_free (op->data.gradient.gradient_spec);
|
|
|
|
if (op->data.gradient.alpha_spec)
|
|
meta_alpha_gradient_spec_free (op->data.gradient.alpha_spec);
|
|
|
|
meta_draw_spec_free (op->data.gradient.x);
|
|
meta_draw_spec_free (op->data.gradient.y);
|
|
meta_draw_spec_free (op->data.gradient.width);
|
|
meta_draw_spec_free (op->data.gradient.height);
|
|
break;
|
|
|
|
case META_DRAW_IMAGE:
|
|
if (op->data.image.alpha_spec)
|
|
meta_alpha_gradient_spec_free (op->data.image.alpha_spec);
|
|
|
|
if (op->data.image.pixbuf)
|
|
g_object_unref (G_OBJECT (op->data.image.pixbuf));
|
|
|
|
if (op->data.image.colorize_spec)
|
|
meta_color_spec_free (op->data.image.colorize_spec);
|
|
|
|
if (op->data.image.colorize_cache_pixbuf)
|
|
g_object_unref (G_OBJECT (op->data.image.colorize_cache_pixbuf));
|
|
|
|
meta_draw_spec_free (op->data.image.x);
|
|
meta_draw_spec_free (op->data.image.y);
|
|
meta_draw_spec_free (op->data.image.width);
|
|
meta_draw_spec_free (op->data.image.height);
|
|
break;
|
|
|
|
case META_DRAW_GTK_ARROW:
|
|
meta_draw_spec_free (op->data.gtk_arrow.x);
|
|
meta_draw_spec_free (op->data.gtk_arrow.y);
|
|
meta_draw_spec_free (op->data.gtk_arrow.width);
|
|
meta_draw_spec_free (op->data.gtk_arrow.height);
|
|
break;
|
|
|
|
case META_DRAW_GTK_BOX:
|
|
meta_draw_spec_free (op->data.gtk_box.x);
|
|
meta_draw_spec_free (op->data.gtk_box.y);
|
|
meta_draw_spec_free (op->data.gtk_box.width);
|
|
meta_draw_spec_free (op->data.gtk_box.height);
|
|
break;
|
|
|
|
case META_DRAW_GTK_VLINE:
|
|
meta_draw_spec_free (op->data.gtk_vline.x);
|
|
meta_draw_spec_free (op->data.gtk_vline.y1);
|
|
meta_draw_spec_free (op->data.gtk_vline.y2);
|
|
break;
|
|
|
|
case META_DRAW_ICON:
|
|
if (op->data.icon.alpha_spec)
|
|
meta_alpha_gradient_spec_free (op->data.icon.alpha_spec);
|
|
|
|
meta_draw_spec_free (op->data.icon.x);
|
|
meta_draw_spec_free (op->data.icon.y);
|
|
meta_draw_spec_free (op->data.icon.width);
|
|
meta_draw_spec_free (op->data.icon.height);
|
|
break;
|
|
|
|
case META_DRAW_TITLE:
|
|
if (op->data.title.color_spec)
|
|
meta_color_spec_free (op->data.title.color_spec);
|
|
|
|
meta_draw_spec_free (op->data.title.x);
|
|
meta_draw_spec_free (op->data.title.y);
|
|
if (op->data.title.ellipsize_width)
|
|
meta_draw_spec_free (op->data.title.ellipsize_width);
|
|
break;
|
|
|
|
case META_DRAW_OP_LIST:
|
|
if (op->data.op_list.op_list)
|
|
meta_draw_op_list_unref (op->data.op_list.op_list);
|
|
|
|
meta_draw_spec_free (op->data.op_list.x);
|
|
meta_draw_spec_free (op->data.op_list.y);
|
|
meta_draw_spec_free (op->data.op_list.width);
|
|
meta_draw_spec_free (op->data.op_list.height);
|
|
break;
|
|
|
|
case META_DRAW_TILE:
|
|
if (op->data.tile.op_list)
|
|
meta_draw_op_list_unref (op->data.tile.op_list);
|
|
|
|
meta_draw_spec_free (op->data.tile.x);
|
|
meta_draw_spec_free (op->data.tile.y);
|
|
meta_draw_spec_free (op->data.tile.width);
|
|
meta_draw_spec_free (op->data.tile.height);
|
|
meta_draw_spec_free (op->data.tile.tile_xoffset);
|
|
meta_draw_spec_free (op->data.tile.tile_yoffset);
|
|
meta_draw_spec_free (op->data.tile.tile_width);
|
|
meta_draw_spec_free (op->data.tile.tile_height);
|
|
break;
|
|
}
|
|
|
|
g_free (op);
|
|
}
|
|
|
|
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 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,
|
|
GtkStyleContext *context,
|
|
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_TINT:
|
|
{
|
|
GdkRGBA color;
|
|
guint32 rgba;
|
|
gboolean has_alpha;
|
|
|
|
meta_color_spec_render (op->data.rectangle.color_spec,
|
|
context,
|
|
&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,
|
|
context, width, height);
|
|
|
|
pixbuf = apply_alpha (pixbuf,
|
|
op->data.gradient.alpha_spec,
|
|
FALSE);
|
|
}
|
|
break;
|
|
|
|
case META_DRAW_IMAGE:
|
|
{
|
|
if (op->data.image.colorize_spec)
|
|
{
|
|
GdkRGBA color;
|
|
|
|
meta_color_spec_render (op->data.image.colorize_spec,
|
|
context, &color);
|
|
|
|
if (op->data.image.colorize_cache_pixbuf == NULL ||
|
|
op->data.image.colorize_cache_pixel != GDK_COLOR_RGB (color))
|
|
{
|
|
if (op->data.image.colorize_cache_pixbuf)
|
|
g_object_unref (G_OBJECT (op->data.image.colorize_cache_pixbuf));
|
|
|
|
/* const cast here */
|
|
((MetaDrawOp*)op)->data.image.colorize_cache_pixbuf =
|
|
colorize_pixbuf (op->data.image.pixbuf,
|
|
&color);
|
|
((MetaDrawOp*)op)->data.image.colorize_cache_pixel =
|
|
GDK_COLOR_RGB (color);
|
|
}
|
|
|
|
if (op->data.image.colorize_cache_pixbuf)
|
|
{
|
|
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_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_LINE:
|
|
case META_DRAW_RECTANGLE:
|
|
case META_DRAW_ARC:
|
|
case META_DRAW_CLIP:
|
|
case META_DRAW_GTK_ARROW:
|
|
case META_DRAW_GTK_BOX:
|
|
case META_DRAW_GTK_VLINE:
|
|
case META_DRAW_TITLE:
|
|
case META_DRAW_OP_LIST:
|
|
case META_DRAW_TILE:
|
|
break;
|
|
}
|
|
|
|
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->borders.visible.left;
|
|
env->right_width = info->fgeom->borders.visible.right;
|
|
env->top_height = info->fgeom->borders.visible.top;
|
|
env->bottom_height = info->fgeom->borders.visible.bottom;
|
|
env->frame_x_center = info->fgeom->width / 2 - logical_region.x;
|
|
env->frame_y_center = info->fgeom->height / 2 - logical_region.y;
|
|
}
|
|
else
|
|
{
|
|
env->left_width = 0;
|
|
env->right_width = 0;
|
|
env->top_height = 0;
|
|
env->bottom_height = 0;
|
|
env->frame_x_center = 0;
|
|
env->frame_y_center = 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 = meta_current_theme;
|
|
}
|
|
|
|
|
|
/* This code was originally rendering anti-aliased using X primitives, and
|
|
* now has been switched to draw anti-aliased using cairo. In general, the
|
|
* closest correspondence between X rendering and cairo rendering is given
|
|
* by offsetting the geometry by 0.5 pixels in both directions before rendering
|
|
* with cairo. This is because X samples at the upper left corner of the
|
|
* pixel while cairo averages over the entire pixel. However, in the cases
|
|
* where the X rendering was an exact rectangle with no "jaggies"
|
|
* we need to be a bit careful about applying the offset. We want to produce
|
|
* the exact same pixel-aligned rectangle, rather than a rectangle with
|
|
* fuzz around the edges.
|
|
*/
|
|
static void
|
|
meta_draw_op_draw_with_env (const MetaDrawOp *op,
|
|
GtkStyleContext *style_gtk,
|
|
cairo_t *cr,
|
|
const MetaDrawInfo *info,
|
|
MetaRectangle rect,
|
|
MetaPositionExprEnv *env)
|
|
{
|
|
GdkRGBA color;
|
|
|
|
cairo_save (cr);
|
|
gtk_style_context_save (style_gtk);
|
|
|
|
cairo_set_line_width (cr, 1.0);
|
|
|
|
switch (op->type)
|
|
{
|
|
case META_DRAW_LINE:
|
|
{
|
|
int x1, x2, y1, y2;
|
|
|
|
meta_color_spec_render (op->data.line.color_spec, style_gtk, &color);
|
|
gdk_cairo_set_source_rgba (cr, &color);
|
|
|
|
if (op->data.line.width > 0)
|
|
cairo_set_line_width (cr, op->data.line.width);
|
|
|
|
if (op->data.line.dash_on_length > 0 &&
|
|
op->data.line.dash_off_length > 0)
|
|
{
|
|
double dash_list[2];
|
|
dash_list[0] = op->data.line.dash_on_length;
|
|
dash_list[1] = op->data.line.dash_off_length;
|
|
cairo_set_dash (cr, dash_list, 2, 0);
|
|
}
|
|
|
|
x1 = parse_x_position_unchecked (op->data.line.x1, env);
|
|
y1 = parse_y_position_unchecked (op->data.line.y1, env);
|
|
|
|
if (!op->data.line.x2 &&
|
|
!op->data.line.y2 &&
|
|
op->data.line.width==0)
|
|
{
|
|
cairo_rectangle (cr, x1, y1, 1, 1);
|
|
cairo_fill (cr);
|
|
}
|
|
else
|
|
{
|
|
if (op->data.line.x2)
|
|
x2 = parse_x_position_unchecked (op->data.line.x2, env);
|
|
else
|
|
x2 = x1;
|
|
|
|
if (op->data.line.y2)
|
|
y2 = parse_y_position_unchecked (op->data.line.y2, env);
|
|
else
|
|
y2 = y1;
|
|
|
|
/* This is one of the cases where we are matching the exact
|
|
* pixel aligned rectangle produced by X; for zero-width lines
|
|
* the generic algorithm produces the right result so we don't
|
|
* need to handle them here.
|
|
*/
|
|
if ((y1 == y2 || x1 == x2) && op->data.line.width != 0)
|
|
{
|
|
double offset = op->data.line.width % 2 ? .5 : 0;
|
|
|
|
if (y1 == y2)
|
|
{
|
|
cairo_move_to (cr, x1, y1 + offset);
|
|
cairo_line_to (cr, x2, y2 + offset);
|
|
}
|
|
else
|
|
{
|
|
cairo_move_to (cr, x1 + offset, y1);
|
|
cairo_line_to (cr, x2 + offset, y2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* zero-width lines include both end-points in X, unlike wide lines */
|
|
if (op->data.line.width == 0)
|
|
cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
|
|
|
|
cairo_move_to (cr, x1 + .5, y1 + .5);
|
|
cairo_line_to (cr, x2 + .5, y2 + .5);
|
|
}
|
|
cairo_stroke (cr);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case META_DRAW_RECTANGLE:
|
|
{
|
|
int rx, ry, rwidth, rheight;
|
|
|
|
meta_color_spec_render (op->data.rectangle.color_spec,
|
|
style_gtk, &color);
|
|
gdk_cairo_set_source_rgba (cr, &color);
|
|
|
|
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);
|
|
|
|
/* Filled and stroked rectangles are the other cases
|
|
* we pixel-align to X rasterization
|
|
*/
|
|
if (op->data.rectangle.filled)
|
|
{
|
|
cairo_rectangle (cr, rx, ry, rwidth, rheight);
|
|
cairo_fill (cr);
|
|
}
|
|
else
|
|
{
|
|
cairo_rectangle (cr, rx + .5, ry + .5, rwidth, rheight);
|
|
cairo_stroke (cr);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case META_DRAW_ARC:
|
|
{
|
|
int rx, ry, rwidth, rheight;
|
|
double start_angle, end_angle;
|
|
double center_x, center_y;
|
|
|
|
meta_color_spec_render (op->data.arc.color_spec, style_gtk, &color);
|
|
gdk_cairo_set_source_rgba (cr, &color);
|
|
|
|
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);
|
|
|
|
start_angle = op->data.arc.start_angle * (M_PI / 180.)
|
|
- (.5 * M_PI); /* start at 12 instead of 3 oclock */
|
|
end_angle = start_angle + op->data.arc.extent_angle * (M_PI / 180.);
|
|
center_x = rx + (double)rwidth / 2. + .5;
|
|
center_y = ry + (double)rheight / 2. + .5;
|
|
|
|
cairo_save (cr);
|
|
|
|
cairo_translate (cr, center_x, center_y);
|
|
cairo_scale (cr, (double)rwidth / 2., (double)rheight / 2.);
|
|
|
|
if (op->data.arc.extent_angle >= 0)
|
|
cairo_arc (cr, 0, 0, 1, start_angle, end_angle);
|
|
else
|
|
cairo_arc_negative (cr, 0, 0, 1, start_angle, end_angle);
|
|
|
|
cairo_restore (cr);
|
|
|
|
if (op->data.arc.filled)
|
|
{
|
|
cairo_line_to (cr, center_x, center_y);
|
|
cairo_fill (cr);
|
|
}
|
|
else
|
|
cairo_stroke (cr);
|
|
}
|
|
break;
|
|
|
|
case META_DRAW_CLIP:
|
|
break;
|
|
|
|
case META_DRAW_TINT:
|
|
{
|
|
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)
|
|
{
|
|
meta_color_spec_render (op->data.tint.color_spec,
|
|
style_gtk, &color);
|
|
gdk_cairo_set_source_rgba (cr, &color);
|
|
|
|
cairo_rectangle (cr, rx, ry, rwidth, rheight);
|
|
cairo_fill (cr);
|
|
}
|
|
else
|
|
{
|
|
GdkPixbuf *pixbuf;
|
|
|
|
pixbuf = draw_op_as_pixbuf (op, style_gtk, info,
|
|
rwidth, rheight);
|
|
|
|
if (pixbuf)
|
|
{
|
|
gdk_cairo_set_source_pixbuf (cr, pixbuf, rx, ry);
|
|
cairo_paint (cr);
|
|
|
|
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, style_gtk, info,
|
|
rwidth, rheight);
|
|
|
|
if (pixbuf)
|
|
{
|
|
gdk_cairo_set_source_pixbuf (cr, pixbuf, rx, ry);
|
|
cairo_paint (cr);
|
|
|
|
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, style_gtk, 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);
|
|
|
|
gdk_cairo_set_source_pixbuf (cr, pixbuf, rx, ry);
|
|
cairo_paint (cr);
|
|
|
|
g_object_unref (G_OBJECT (pixbuf));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case META_DRAW_GTK_ARROW:
|
|
{
|
|
int rx, ry, rwidth, rheight;
|
|
double angle = 0, size;
|
|
|
|
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);
|
|
|
|
size = MAX(rwidth, rheight);
|
|
|
|
switch (op->data.gtk_arrow.arrow)
|
|
{
|
|
case GTK_ARROW_UP:
|
|
angle = 0;
|
|
break;
|
|
case GTK_ARROW_RIGHT:
|
|
angle = M_PI / 2;
|
|
break;
|
|
case GTK_ARROW_DOWN:
|
|
angle = M_PI;
|
|
break;
|
|
case GTK_ARROW_LEFT:
|
|
angle = 3 * M_PI / 2;
|
|
break;
|
|
case GTK_ARROW_NONE:
|
|
return;
|
|
}
|
|
|
|
gtk_style_context_set_state (style_gtk, op->data.gtk_arrow.state);
|
|
gtk_render_arrow (style_gtk, cr, angle, rx, ry, size);
|
|
}
|
|
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_style_context_set_state (style_gtk, op->data.gtk_box.state);
|
|
gtk_render_background (style_gtk, cr, rx, ry, rwidth, rheight);
|
|
gtk_render_frame (style_gtk, cr, 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_style_context_set_state (style_gtk, op->data.gtk_vline.state);
|
|
gtk_render_line (style_gtk, cr, rx, ry1, rx, ry2);
|
|
}
|
|
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, style_gtk, 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);
|
|
|
|
gdk_cairo_set_source_pixbuf (cr, pixbuf, rx, ry);
|
|
cairo_paint (cr);
|
|
|
|
g_object_unref (G_OBJECT (pixbuf));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case META_DRAW_TITLE:
|
|
if (info->title_layout)
|
|
{
|
|
int rx, ry;
|
|
PangoRectangle ink_rect, logical_rect;
|
|
|
|
meta_color_spec_render (op->data.title.color_spec,
|
|
style_gtk, &color);
|
|
gdk_cairo_set_source_rgba (cr, &color);
|
|
|
|
rx = parse_x_position_unchecked (op->data.title.x, env);
|
|
ry = parse_y_position_unchecked (op->data.title.y, env);
|
|
|
|
if (op->data.title.ellipsize_width)
|
|
{
|
|
int ellipsize_width;
|
|
int right_bearing;
|
|
|
|
ellipsize_width = parse_x_position_unchecked (op->data.title.ellipsize_width, env);
|
|
/* HACK: parse_x_position_unchecked adds in env->rect.x, subtract out again */
|
|
ellipsize_width -= env->rect.x;
|
|
|
|
pango_layout_set_width (info->title_layout, -1);
|
|
pango_layout_get_pixel_extents (info->title_layout,
|
|
&ink_rect, &logical_rect);
|
|
|
|
/* Pango's idea of ellipsization is with respect to the logical rect.
|
|
* correct for this, by reducing the ellipsization width by the overflow
|
|
* of the un-ellipsized text on the right... it's always the visual
|
|
* right we want regardless of bidi, since since the X we pass in to
|
|
* cairo_move_to() is always the left edge of the line.
|
|
*/
|
|
right_bearing = (ink_rect.x + ink_rect.width) - (logical_rect.x + logical_rect.width);
|
|
right_bearing = MAX (right_bearing, 0);
|
|
|
|
ellipsize_width -= right_bearing;
|
|
ellipsize_width = MAX (ellipsize_width, 0);
|
|
|
|
/* Only ellipsizing when necessary is a performance optimization -
|
|
* pango_layout_set_width() will force a relayout if it isn't the
|
|
* same as the current width of -1.
|
|
*/
|
|
if (ellipsize_width < logical_rect.width)
|
|
pango_layout_set_width (info->title_layout, PANGO_SCALE * ellipsize_width);
|
|
}
|
|
|
|
cairo_move_to (cr, rx, ry);
|
|
pango_cairo_show_layout (cr, info->title_layout);
|
|
|
|
/* Remove any ellipsization we might have set; will short-circuit
|
|
* if the width is already -1 */
|
|
pango_layout_set_width (info->title_layout, -1);
|
|
}
|
|
break;
|
|
|
|
case META_DRAW_OP_LIST:
|
|
{
|
|
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_with_style (op->data.op_list.op_list,
|
|
style_gtk, cr, info, d_rect);
|
|
}
|
|
break;
|
|
|
|
case META_DRAW_TILE:
|
|
{
|
|
int rx, ry, rwidth, rheight;
|
|
int tile_xoffset, tile_yoffset;
|
|
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);
|
|
|
|
cairo_save (cr);
|
|
|
|
cairo_rectangle (cr, rx, ry, rwidth, rheight);
|
|
cairo_clip (cr);
|
|
|
|
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_with_style (op->data.tile.op_list,
|
|
style_gtk, cr, info, tile);
|
|
|
|
tile.y += tile.height;
|
|
}
|
|
|
|
tile.x += tile.width;
|
|
}
|
|
cairo_restore (cr);
|
|
|
|
}
|
|
break;
|
|
}
|
|
|
|
cairo_restore (cr);
|
|
gtk_style_context_restore (style_gtk);
|
|
}
|
|
|
|
void
|
|
meta_draw_op_draw_with_style (const MetaDrawOp *op,
|
|
GtkStyleContext *style_gtk,
|
|
cairo_t *cr,
|
|
const MetaDrawInfo *info,
|
|
MetaRectangle logical_region)
|
|
{
|
|
MetaPositionExprEnv env;
|
|
|
|
fill_env (&env, info, logical_region);
|
|
|
|
meta_draw_op_draw_with_env (op, style_gtk, cr,
|
|
info, logical_region,
|
|
&env);
|
|
|
|
}
|
|
|
|
/**
|
|
* meta_draw_op_list_new: (skip)
|
|
*
|
|
*/
|
|
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_with_style (const MetaDrawOpList *op_list,
|
|
GtkStyleContext *style_gtk,
|
|
cairo_t *cr,
|
|
const MetaDrawInfo *info,
|
|
MetaRectangle rect)
|
|
{
|
|
int i;
|
|
MetaPositionExprEnv env;
|
|
|
|
if (op_list->n_ops == 0)
|
|
return;
|
|
|
|
fill_env (&env, info, rect);
|
|
|
|
cairo_save (cr);
|
|
|
|
for (i = 0; i < op_list->n_ops; i++)
|
|
{
|
|
MetaDrawOp *op = op_list->ops[i];
|
|
|
|
if (op->type == META_DRAW_CLIP)
|
|
{
|
|
cairo_restore (cr);
|
|
|
|
cairo_rectangle (cr,
|
|
parse_x_position_unchecked (op->data.clip.x, &env),
|
|
parse_y_position_unchecked (op->data.clip.y, &env),
|
|
parse_size_unchecked (op->data.clip.width, &env),
|
|
parse_size_unchecked (op->data.clip.height, &env));
|
|
cairo_clip (cr);
|
|
|
|
cairo_save (cr);
|
|
}
|
|
else if (gdk_cairo_get_clip_rectangle (cr, NULL))
|
|
{
|
|
meta_draw_op_draw_with_env (op,
|
|
style_gtk, cr, info,
|
|
rect,
|
|
&env);
|
|
}
|
|
}
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
void
|
|
meta_draw_op_list_append (MetaDrawOpList *op_list,
|
|
MetaDrawOp *op)
|
|
{
|
|
if (op_list->n_ops == op_list->n_allocated)
|
|
{
|
|
op_list->n_allocated *= 2;
|
|
op_list->ops = g_renew (MetaDrawOp*, op_list->ops, op_list->n_allocated);
|
|
}
|
|
|
|
op_list->ops[op_list->n_ops] = op;
|
|
op_list->n_ops += 1;
|
|
}
|
|
|
|
gboolean
|
|
meta_draw_op_list_validate (MetaDrawOpList *op_list,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (op_list != NULL, FALSE);
|
|
|
|
/* empty lists are OK, nothing else to check really */
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* This is not done in validate, since we wouldn't know the name
|
|
* of the list to report the error. It might be nice to
|
|
* store names inside the list sometime.
|
|
*/
|
|
gboolean
|
|
meta_draw_op_list_contains (MetaDrawOpList *op_list,
|
|
MetaDrawOpList *child)
|
|
{
|
|
int i;
|
|
|
|
/* mmm, huge tree recursion */
|
|
|
|
for (i = 0; i < op_list->n_ops; i++)
|
|
{
|
|
if (op_list->ops[i]->type == META_DRAW_OP_LIST)
|
|
{
|
|
if (op_list->ops[i]->data.op_list.op_list == child)
|
|
return TRUE;
|
|
|
|
if (meta_draw_op_list_contains (op_list->ops[i]->data.op_list.op_list,
|
|
child))
|
|
return TRUE;
|
|
}
|
|
else if (op_list->ops[i]->type == META_DRAW_TILE)
|
|
{
|
|
if (op_list->ops[i]->data.tile.op_list == child)
|
|
return TRUE;
|
|
|
|
if (meta_draw_op_list_contains (op_list->ops[i]->data.tile.op_list,
|
|
child))
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* meta_frame_style_new:
|
|
* @parent: The parent style. Data not filled in here will be
|
|
* looked for in the parent style, and in its parent
|
|
* style, and so on.
|
|
*
|
|
* Constructor for a MetaFrameStyle.
|
|
*
|
|
* Returns: (transfer full): The newly-constructed style.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* meta_frame_style_ref:
|
|
* @style: The style.
|
|
*
|
|
* Increases the reference count of a frame 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 MetaButtonState
|
|
map_button_state (MetaButtonType button_type,
|
|
const MetaFrameGeometry *fgeom,
|
|
int middle_bg_offset,
|
|
MetaButtonState button_states[META_BUTTON_TYPE_LAST])
|
|
{
|
|
MetaButtonFunction function = META_BUTTON_FUNCTION_LAST;
|
|
|
|
switch (button_type)
|
|
{
|
|
/* First handle functions, which map directly */
|
|
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:
|
|
case META_BUTTON_TYPE_MENU:
|
|
case META_BUTTON_TYPE_APPMENU:
|
|
case META_BUTTON_TYPE_MINIMIZE:
|
|
case META_BUTTON_TYPE_MAXIMIZE:
|
|
case META_BUTTON_TYPE_CLOSE:
|
|
return button_states[button_type];
|
|
|
|
/* Map position buttons to the corresponding function */
|
|
case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND:
|
|
case META_BUTTON_TYPE_RIGHT_SINGLE_BACKGROUND:
|
|
if (fgeom->n_right_buttons > 0)
|
|
function = fgeom->button_layout.right_buttons[0];
|
|
break;
|
|
case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND:
|
|
if (fgeom->n_right_buttons > 0)
|
|
function = fgeom->button_layout.right_buttons[fgeom->n_right_buttons - 1];
|
|
break;
|
|
case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND:
|
|
if (middle_bg_offset + 1 < fgeom->n_right_buttons)
|
|
function = fgeom->button_layout.right_buttons[middle_bg_offset + 1];
|
|
break;
|
|
case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND:
|
|
case META_BUTTON_TYPE_LEFT_SINGLE_BACKGROUND:
|
|
if (fgeom->n_left_buttons > 0)
|
|
function = fgeom->button_layout.left_buttons[0];
|
|
break;
|
|
case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND:
|
|
if (fgeom->n_left_buttons > 0)
|
|
function = fgeom->button_layout.left_buttons[fgeom->n_left_buttons - 1];
|
|
break;
|
|
case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND:
|
|
if (middle_bg_offset + 1 < fgeom->n_left_buttons)
|
|
function = fgeom->button_layout.left_buttons[middle_bg_offset + 1];
|
|
break;
|
|
case META_BUTTON_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
if (function != META_BUTTON_FUNCTION_LAST)
|
|
return button_states[map_button_function_to_type (function)];
|
|
|
|
return META_BUTTON_STATE_LAST;
|
|
}
|
|
|
|
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 the side buttons if we don't have
|
|
* single button backgrounds, and to middle button
|
|
* backgrounds if we don't have the ones on the sides
|
|
*/
|
|
|
|
if (op_list == NULL &&
|
|
type == META_BUTTON_TYPE_LEFT_SINGLE_BACKGROUND)
|
|
return get_button (style, META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND, state);
|
|
|
|
if (op_list == NULL &&
|
|
type == META_BUTTON_TYPE_RIGHT_SINGLE_BACKGROUND)
|
|
return get_button (style, META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND, state);
|
|
|
|
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);
|
|
|
|
/* 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);
|
|
|
|
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_LEFT_SINGLE_BACKGROUND:
|
|
*rect = fgeom->left_single_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_RIGHT_SINGLE_BACKGROUND:
|
|
*rect = fgeom->right_single_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_APPMENU:
|
|
*rect = fgeom->appmenu_rect.visible;
|
|
break;
|
|
|
|
case META_BUTTON_TYPE_LAST:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
meta_frame_style_draw_with_style (MetaFrameStyle *style,
|
|
GtkStyleContext *style_gtk,
|
|
cairo_t *cr,
|
|
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 visible_rect;
|
|
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 logical_rect;
|
|
MetaDrawInfo draw_info;
|
|
const MetaFrameBorders *borders;
|
|
|
|
borders = &fgeom->borders;
|
|
|
|
visible_rect.x = borders->invisible.left;
|
|
visible_rect.y = borders->invisible.top;
|
|
visible_rect.width = fgeom->width - borders->invisible.left - borders->invisible.right;
|
|
visible_rect.height = fgeom->height - borders->invisible.top - borders->invisible.bottom;
|
|
|
|
titlebar_rect.x = visible_rect.x;
|
|
titlebar_rect.y = visible_rect.y;
|
|
titlebar_rect.width = visible_rect.width;
|
|
titlebar_rect.height = borders->visible.top;
|
|
|
|
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 = visible_rect.x;
|
|
left_edge.y = visible_rect.y + borders->visible.top;
|
|
left_edge.width = borders->visible.left;
|
|
left_edge.height = visible_rect.height - borders->visible.top - borders->visible.bottom;
|
|
|
|
right_edge.x = visible_rect.x + visible_rect.width - borders->visible.right;
|
|
right_edge.y = visible_rect.y + borders->visible.top;
|
|
right_edge.width = borders->visible.right;
|
|
right_edge.height = visible_rect.height - borders->visible.top - borders->visible.bottom;
|
|
|
|
bottom_edge.x = visible_rect.x;
|
|
bottom_edge.y = visible_rect.y + visible_rect.height - borders->visible.bottom;
|
|
bottom_edge.width = visible_rect.width;
|
|
bottom_edge.height = borders->visible.bottom;
|
|
|
|
if (title_layout)
|
|
pango_layout_get_pixel_extents (title_layout,
|
|
NULL, &logical_rect);
|
|
|
|
draw_info.mini_icon = mini_icon;
|
|
draw_info.icon = icon;
|
|
draw_info.title_layout = title_layout;
|
|
draw_info.title_layout_width = title_layout ? logical_rect.width : 0;
|
|
draw_info.title_layout_height = title_layout ? logical_rect.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;
|
|
|
|
switch ((MetaFramePiece) i)
|
|
{
|
|
case META_FRAME_PIECE_ENTIRE_BACKGROUND:
|
|
rect = visible_rect;
|
|
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 = visible_rect;
|
|
break;
|
|
|
|
case META_FRAME_PIECE_LAST:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
cairo_save (cr);
|
|
|
|
gdk_cairo_rectangle (cr, &rect);
|
|
cairo_clip (cr);
|
|
|
|
if (gdk_cairo_get_clip_rectangle (cr, NULL))
|
|
{
|
|
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_with_style (op_list,
|
|
style_gtk,
|
|
cr,
|
|
&draw_info,
|
|
m_rect);
|
|
}
|
|
}
|
|
|
|
cairo_restore (cr);
|
|
|
|
/* Draw buttons just before overlay */
|
|
if ((i + 1) == META_FRAME_PIECE_OVERLAY)
|
|
{
|
|
MetaDrawOpList *op_list;
|
|
int middle_bg_offset;
|
|
|
|
middle_bg_offset = 0;
|
|
j = 0;
|
|
while (j < META_BUTTON_TYPE_LAST)
|
|
{
|
|
MetaButtonState button_state;
|
|
|
|
button_rect (j, fgeom, middle_bg_offset, &rect);
|
|
|
|
button_state = map_button_state (j, fgeom, middle_bg_offset, button_states);
|
|
|
|
op_list = get_button (style, j, button_state);
|
|
|
|
if (op_list)
|
|
{
|
|
cairo_save (cr);
|
|
gdk_cairo_rectangle (cr, &rect);
|
|
cairo_clip (cr);
|
|
|
|
if (gdk_cairo_get_clip_rectangle (cr, NULL))
|
|
{
|
|
MetaRectangle m_rect;
|
|
|
|
m_rect = meta_rect (rect.x, rect.y,
|
|
rect.width, rect.height);
|
|
|
|
meta_draw_op_list_draw_with_style (op_list,
|
|
style_gtk,
|
|
cr,
|
|
&draw_info,
|
|
m_rect);
|
|
}
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
/* 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->tiled_left_styles);
|
|
free_focus_styles (style_set->tiled_right_styles);
|
|
free_focus_styles (style_set->maximized_and_shaded_styles);
|
|
free_focus_styles (style_set->tiled_left_and_shaded_styles);
|
|
free_focus_styles (style_set->tiled_right_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_TILED_LEFT:
|
|
styles = style_set->tiled_left_styles;
|
|
break;
|
|
case META_FRAME_STATE_TILED_RIGHT:
|
|
styles = style_set->tiled_right_styles;
|
|
break;
|
|
case META_FRAME_STATE_MAXIMIZED_AND_SHADED:
|
|
styles = style_set->maximized_and_shaded_styles;
|
|
break;
|
|
case META_FRAME_STATE_TILED_LEFT_AND_SHADED:
|
|
styles = style_set->tiled_left_and_shaded_styles;
|
|
break;
|
|
case META_FRAME_STATE_TILED_RIGHT_AND_SHADED:
|
|
styles = style_set->tiled_right_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];
|
|
|
|
/* Tiled states are optional, try falling back to non-tiled states */
|
|
if (style == NULL)
|
|
{
|
|
if (state == META_FRAME_STATE_TILED_LEFT ||
|
|
state == META_FRAME_STATE_TILED_RIGHT)
|
|
style = get_style (style_set, META_FRAME_STATE_NORMAL,
|
|
resize, focus);
|
|
else if (state == META_FRAME_STATE_TILED_LEFT_AND_SHADED ||
|
|
state == META_FRAME_STATE_TILED_RIGHT_AND_SHADED)
|
|
style = get_style (style_set, META_FRAME_STATE_SHADED,
|
|
resize, 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)
|
|
{
|
|
/* Translators: This error occurs when a <frame> tag is missing
|
|
* in theme XML. The "<frame ...>" is intended as a noun phrase,
|
|
* and the "missing" qualifies it. You should translate "whatever".
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* meta_theme_get_current: (skip)
|
|
*
|
|
*/
|
|
MetaTheme*
|
|
meta_theme_get_current (void)
|
|
{
|
|
return meta_current_theme;
|
|
}
|
|
|
|
void
|
|
meta_theme_set_current (const char *name)
|
|
{
|
|
MetaTheme *new_theme;
|
|
GError *err;
|
|
|
|
meta_topic (META_DEBUG_THEMES, "Setting current theme to \"%s\"\n", name);
|
|
|
|
if (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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* meta_theme_new: (skip)
|
|
*
|
|
*/
|
|
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);
|
|
|
|
/* Create our variable quarks so we can look up variables without
|
|
having to strcmp for the names */
|
|
theme->quark_width = g_quark_from_static_string ("width");
|
|
theme->quark_height = g_quark_from_static_string ("height");
|
|
theme->quark_object_width = g_quark_from_static_string ("object_width");
|
|
theme->quark_object_height = g_quark_from_static_string ("object_height");
|
|
theme->quark_left_width = g_quark_from_static_string ("left_width");
|
|
theme->quark_right_width = g_quark_from_static_string ("right_width");
|
|
theme->quark_top_height = g_quark_from_static_string ("top_height");
|
|
theme->quark_bottom_height = g_quark_from_static_string ("bottom_height");
|
|
theme->quark_mini_icon_width = g_quark_from_static_string ("mini_icon_width");
|
|
theme->quark_mini_icon_height = g_quark_from_static_string ("mini_icon_height");
|
|
theme->quark_icon_width = g_quark_from_static_string ("icon_width");
|
|
theme->quark_icon_height = g_quark_from_static_string ("icon_height");
|
|
theme->quark_title_width = g_quark_from_static_string ("title_width");
|
|
theme->quark_title_height = g_quark_from_static_string ("title_height");
|
|
theme->quark_frame_x_center = g_quark_from_static_string ("frame_x_center");
|
|
theme->quark_frame_y_center = g_quark_from_static_string ("frame_y_center");
|
|
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)
|
|
{
|
|
/* Translators: This error means that a necessary XML tag (whose name
|
|
* is given in angle brackets) was not found in a given theme (whose
|
|
* name is given second, in quotation marks).
|
|
*/
|
|
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 (i != (int)META_FRAME_TYPE_ATTACHED && 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;
|
|
}
|
|
|
|
/**
|
|
* meta_theme_load_image: (skip)
|
|
*
|
|
*/
|
|
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];
|
|
|
|
if (style_set == NULL && type == META_FRAME_TYPE_ATTACHED)
|
|
style_set = theme->style_sets_by_type[META_FRAME_TYPE_BORDER];
|
|
|
|
/* Right now the parser forces a style set for all other 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 |
|
|
META_FRAME_TILED_LEFT | META_FRAME_TILED_RIGHT))
|
|
{
|
|
case 0:
|
|
state = META_FRAME_STATE_NORMAL;
|
|
break;
|
|
case META_FRAME_MAXIMIZED:
|
|
state = META_FRAME_STATE_MAXIMIZED;
|
|
break;
|
|
case META_FRAME_TILED_LEFT:
|
|
state = META_FRAME_STATE_TILED_LEFT;
|
|
break;
|
|
case META_FRAME_TILED_RIGHT:
|
|
state = META_FRAME_STATE_TILED_RIGHT;
|
|
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;
|
|
case (META_FRAME_TILED_LEFT | META_FRAME_SHADED):
|
|
state = META_FRAME_STATE_TILED_LEFT_AND_SHADED;
|
|
break;
|
|
case (META_FRAME_TILED_RIGHT | META_FRAME_SHADED):
|
|
state = META_FRAME_STATE_TILED_RIGHT_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;
|
|
}
|
|
|
|
GtkStyleContext *
|
|
meta_theme_create_style_context (GdkScreen *screen,
|
|
const gchar *variant)
|
|
{
|
|
GtkWidgetPath *path;
|
|
GtkStyleContext *style;
|
|
char *theme_name;
|
|
|
|
g_object_get (gtk_settings_get_for_screen (screen),
|
|
"gtk-theme-name", &theme_name,
|
|
NULL);
|
|
|
|
style = gtk_style_context_new ();
|
|
path = gtk_widget_path_new ();
|
|
gtk_widget_path_append_type (path, META_TYPE_FRAMES);
|
|
gtk_widget_path_iter_add_class (path, -1, GTK_STYLE_CLASS_BACKGROUND);
|
|
gtk_style_context_set_path (style, path);
|
|
gtk_widget_path_unref (path);
|
|
|
|
if (theme_name && *theme_name)
|
|
{
|
|
GtkCssProvider *provider;
|
|
|
|
provider = gtk_css_provider_get_named (theme_name, variant);
|
|
gtk_style_context_add_provider (style,
|
|
GTK_STYLE_PROVIDER (provider),
|
|
GTK_STYLE_PROVIDER_PRIORITY_SETTINGS);
|
|
}
|
|
|
|
g_free (theme_name);
|
|
|
|
return style;
|
|
}
|
|
|
|
void
|
|
meta_theme_draw_frame (MetaTheme *theme,
|
|
GtkStyleContext *style_gtk,
|
|
cairo_t *cr,
|
|
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,
|
|
type,
|
|
&fgeom,
|
|
theme);
|
|
|
|
meta_frame_style_draw_with_style (style,
|
|
style_gtk,
|
|
cr,
|
|
&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,
|
|
MetaFrameBorders *borders)
|
|
{
|
|
MetaFrameStyle *style;
|
|
|
|
g_return_if_fail (type < META_FRAME_TYPE_LAST);
|
|
|
|
style = theme_get_style (theme, type, flags);
|
|
|
|
meta_frame_borders_clear (borders);
|
|
|
|
/* Parser is not supposed to allow this currently */
|
|
if (style == NULL)
|
|
return;
|
|
|
|
meta_frame_layout_get_borders (style->layout,
|
|
text_height,
|
|
flags, type,
|
|
borders);
|
|
}
|
|
|
|
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,
|
|
type,
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* meta_theme_lookup_color_constant:
|
|
* @theme: the theme containing the constant
|
|
* @name: the name of the constant
|
|
* @value: (out): the string representation of the colour, or %NULL if it
|
|
* doesn't exist
|
|
*
|
|
* Looks up a colour constant.
|
|
*
|
|
* Returns: %TRUE if it exists, %FALSE otherwise
|
|
*/
|
|
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)
|
|
{
|
|
GtkStyleContext *style;
|
|
PangoFontDescription *font_desc;
|
|
|
|
g_return_val_if_fail (gtk_widget_get_realized (widget), NULL);
|
|
|
|
style = gtk_widget_get_style_context (widget);
|
|
gtk_style_context_get (style, GTK_STATE_FLAG_NORMAL, "font", &font_desc, NULL);
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* meta_pango_font_desc_get_text_height:
|
|
* @font_desc: the font
|
|
* @context: the context of the font
|
|
*
|
|
* Returns the height of the letters in a particular font.
|
|
*
|
|
* Returns: the height of the letters
|
|
*/
|
|
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;
|
|
}
|
|
|
|
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 ("appmenu", str) == 0)
|
|
return META_BUTTON_TYPE_APPMENU;
|
|
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 ("left_single_background", str) == 0)
|
|
return META_BUTTON_TYPE_LEFT_SINGLE_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 if (strcmp ("right_single_background", str) == 0)
|
|
return META_BUTTON_TYPE_RIGHT_SINGLE_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_APPMENU:
|
|
return "appmenu";
|
|
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_LEFT_SINGLE_BACKGROUND:
|
|
return "left_single_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_RIGHT_SINGLE_BACKGROUND:
|
|
return "right_single_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;
|
|
}
|
|
|
|
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 ("tiled_left", str) == 0)
|
|
return META_FRAME_STATE_TILED_LEFT;
|
|
else if (strcmp ("tiled_right", str) == 0)
|
|
return META_FRAME_STATE_TILED_RIGHT;
|
|
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 if (strcmp ("tiled_left_and_shaded", str) == 0)
|
|
return META_FRAME_STATE_TILED_LEFT_AND_SHADED;
|
|
else if (strcmp ("tiled_right_and_shaded", str) == 0)
|
|
return META_FRAME_STATE_TILED_RIGHT_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_TILED_LEFT:
|
|
return "tiled_left";
|
|
case META_FRAME_STATE_TILED_RIGHT:
|
|
return "tiled_right";
|
|
case META_FRAME_STATE_SHADED:
|
|
return "shaded";
|
|
case META_FRAME_STATE_MAXIMIZED_AND_SHADED:
|
|
return "maximized_and_shaded";
|
|
case META_FRAME_STATE_TILED_LEFT_AND_SHADED:
|
|
return "tiled_left_and_shaded";
|
|
case META_FRAME_STATE_TILED_RIGHT_AND_SHADED:
|
|
return "tiled_right_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;
|
|
else if (strcmp ("attached", str) == 0)
|
|
return META_FRAME_TYPE_ATTACHED;
|
|
#if 0
|
|
else if (strcmp ("toolbar", str) == 0)
|
|
return META_FRAME_TYPE_TOOLBAR;
|
|
#endif
|
|
else
|
|
return META_FRAME_TYPE_LAST;
|
|
}
|
|
|
|
/**
|
|
* meta_frame_type_to_string:
|
|
* @type: a #MetaFrameType
|
|
*
|
|
* Converts a frame type enum value to the name string that would
|
|
* appear in the theme definition file.
|
|
*
|
|
* Return value: the string value
|
|
*/
|
|
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";
|
|
case META_FRAME_TYPE_ATTACHED:
|
|
return "attached";
|
|
#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;
|
|
}
|
|
|
|
GtkStateFlags
|
|
meta_gtk_state_from_string (const char *str)
|
|
{
|
|
if (g_ascii_strcasecmp ("normal", str) == 0)
|
|
return GTK_STATE_FLAG_NORMAL;
|
|
else if (g_ascii_strcasecmp ("prelight", str) == 0)
|
|
return GTK_STATE_FLAG_PRELIGHT;
|
|
else if (g_ascii_strcasecmp ("active", str) == 0)
|
|
return GTK_STATE_FLAG_ACTIVE;
|
|
else if (g_ascii_strcasecmp ("selected", str) == 0)
|
|
return GTK_STATE_FLAG_SELECTED;
|
|
else if (g_ascii_strcasecmp ("insensitive", str) == 0)
|
|
return GTK_STATE_FLAG_INSENSITIVE;
|
|
else if (g_ascii_strcasecmp ("inconsistent", str) == 0)
|
|
return GTK_STATE_FLAG_INCONSISTENT;
|
|
else if (g_ascii_strcasecmp ("focused", str) == 0)
|
|
return GTK_STATE_FLAG_FOCUSED;
|
|
else if (g_ascii_strcasecmp ("backdrop", str) == 0)
|
|
return GTK_STATE_FLAG_BACKDROP;
|
|
else
|
|
return -1; /* hack */
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* meta_image_fill_type_from_string:
|
|
* @str: a string representing a fill_type
|
|
*
|
|
* Returns a fill_type from a string. The inverse of
|
|
* meta_image_fill_type_to_string().
|
|
*
|
|
* Returns: the fill type, or -1 if it represents no fill type.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* gtk_style_shade:
|
|
* @a: the starting colour
|
|
* @b: (out): the resulting colour
|
|
* @k: amount to scale lightness and saturation by
|
|
*
|
|
* Takes a colour "a", scales the lightness and saturation by a certain amount,
|
|
* and sets "b" to the resulting colour.
|
|
* gtkstyle.c cut-and-pastage.
|
|
*/
|
|
static void
|
|
gtk_style_shade (GdkRGBA *a,
|
|
GdkRGBA *b,
|
|
gdouble k)
|
|
{
|
|
gdouble red;
|
|
gdouble green;
|
|
gdouble blue;
|
|
|
|
red = a->red;
|
|
green = a->green;
|
|
blue = a->blue;
|
|
|
|
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;
|
|
b->green = green;
|
|
b->blue = blue;
|
|
b->alpha = a->alpha;
|
|
}
|
|
|
|
/**
|
|
* rgb_to_hls:
|
|
* @r: on input, red; on output, hue
|
|
* @g: on input, green; on output, lightness
|
|
* @b: on input, blue; on output, saturation
|
|
*
|
|
* Converts a red/green/blue triplet to a hue/lightness/saturation triplet.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* hls_to_rgb:
|
|
* @h: on input, hue; on output, red
|
|
* @l: on input, lightness; on output, green
|
|
* @s: on input, saturation; on output, blue
|
|
*
|
|
* Converts a hue/lightness/saturation triplet to a red/green/blue triplet.
|
|
*/
|
|
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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* meta_theme_earliest_version_with_button:
|
|
* @type: the button type
|
|
*
|
|
* Returns the earliest version of the theme format which required support
|
|
* for a particular button. (For example, "shade" first appeared in v2, and
|
|
* "close" in v1.)
|
|
*
|
|
* Returns: the number of the theme format
|
|
*/
|
|
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 1000;
|
|
|
|
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 2000;
|
|
|
|
case META_BUTTON_TYPE_LEFT_SINGLE_BACKGROUND:
|
|
case META_BUTTON_TYPE_RIGHT_SINGLE_BACKGROUND:
|
|
return 3003;
|
|
|
|
case META_BUTTON_TYPE_APPMENU:
|
|
return 3005;
|
|
|
|
default:
|
|
meta_warning("Unknown button %d\n", type);
|
|
return 1000;
|
|
}
|
|
}
|