/*
* Copyright (C) 2016 Alberts Muktupāvels
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "config.h"
#include
#include
#include "meta-enum-types.h"
#include "meta-frame-layout-private.h"
#include "meta-theme.h"
#include "meta-theme-gtk-private.h"
#include "meta-theme-impl-private.h"
#include "meta-theme-metacity-private.h"
#include "meta-style-info-private.h"
struct _MetaTheme
{
GObject parent;
MetaThemeType type;
MetaThemeImpl *impl;
gboolean composited;
PangoFontDescription *titlebar_font;
gulong gtk_theme_name_id;
gchar *gtk_theme_name;
GHashTable *variants;
PangoContext *context;
GHashTable *font_descs;
GHashTable *title_heights;
};
enum
{
PROP_0,
PROP_TYPE,
LAST_PROP
};
static GParamSpec *properties[LAST_PROP] = { NULL };
G_DEFINE_TYPE (MetaTheme, meta_theme, G_TYPE_OBJECT)
static MetaStyleInfo *
get_style_info (MetaTheme *theme,
const gchar *variant)
{
const gchar *key;
MetaStyleInfo *style_info;
key = variant;
if (variant == NULL)
key = "default";
style_info = g_hash_table_lookup (theme->variants, key);
if (style_info == NULL)
{
gint window_scale;
window_scale = get_window_scaling_factor ();
style_info = meta_style_info_new (theme->gtk_theme_name, variant,
theme->composited, window_scale);
g_hash_table_insert (theme->variants, g_strdup (key), style_info);
}
return style_info;
}
static MetaFrameStyle *
get_frame_style (MetaTheme *theme,
MetaFrameType type,
MetaFrameFlags flags)
{
MetaFrameState state;
MetaFrameResize resize;
MetaFrameFocus focus;
MetaFrameStyle *style;
MetaFrameStyleSet *style_set;
g_return_val_if_fail (type < META_FRAME_TYPE_LAST, NULL);
style_set = meta_theme_impl_get_style_set (theme->impl, type);
if (style_set == NULL && type == META_FRAME_TYPE_ATTACHED)
style_set = meta_theme_impl_get_style_set (theme->impl, 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 = meta_theme_impl_get_style_set (theme->impl, 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 = meta_frame_style_set_get_style (style_set, state, resize, focus);
return style;
}
static void
font_desc_apply_scale (PangoFontDescription *font_desc,
MetaTheme *theme,
MetaFrameType type,
MetaFrameFlags flags)
{
gint old_size;
MetaFrameStyle *style;
gdouble scale;
gint new_size;
old_size = pango_font_description_get_size (font_desc);
style = get_frame_style (theme, type, flags);
scale = get_window_scaling_factor ();
new_size = MAX (old_size * (style->layout->title_scale / scale), 1);
pango_font_description_set_size (font_desc, new_size);
}
static PangoFontDescription *
get_title_font_desc (MetaTheme *theme,
const gchar *variant,
MetaFrameType type,
MetaFrameFlags flags)
{
gchar *key;
PangoFontDescription *font_desc;
MetaStyleInfo *style_info;
GtkStyleContext *context;
key = g_strdup_printf ("%s_%d_%x", variant ? variant : "default", type, flags);
font_desc = g_hash_table_lookup (theme->font_descs, key);
if (font_desc != NULL)
{
g_free (key);
return font_desc;
}
style_info = get_style_info (theme, variant);
context = meta_style_info_get_style (style_info, META_STYLE_ELEMENT_TITLE);
gtk_style_context_save (context);
gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL);
gtk_style_context_get (context, GTK_STATE_FLAG_NORMAL,
"font", &font_desc, NULL);
gtk_style_context_restore (context);
if (theme->titlebar_font)
pango_font_description_merge (font_desc, theme->titlebar_font, TRUE);
font_desc_apply_scale (font_desc, theme, type, flags);
g_hash_table_insert (theme->font_descs, key, font_desc);
return font_desc;
}
static void
ensure_pango_context (MetaTheme *theme)
{
GdkScreen *screen;
PangoFontMap *fontmap;
PangoContext *context;
const cairo_font_options_t *options;
gdouble dpi;
if (theme->context != NULL)
return;
screen = gdk_screen_get_default ();
fontmap = pango_cairo_font_map_get_default ();
context = pango_font_map_create_context (fontmap);
options = gdk_screen_get_font_options (screen);
pango_cairo_context_set_font_options (context, options);
dpi = gdk_screen_get_resolution (screen);
pango_cairo_context_set_resolution (context, dpi);
theme->context = context;
}
static gint
get_title_height (MetaTheme *theme,
const gchar *variant,
MetaFrameType type,
MetaFrameFlags flags)
{
PangoFontDescription *description;
gpointer size;
gpointer height;
gint title_height;
description = get_title_font_desc (theme, variant, type, flags);
g_assert (description != NULL);
size = GINT_TO_POINTER (pango_font_description_get_size (description));
height = g_hash_table_lookup (theme->title_heights, size);
if (height != NULL)
{
title_height = GPOINTER_TO_INT (height);
}
else
{
PangoLanguage *lang;
PangoFontMetrics *metrics;
gint ascent;
gint descent;
gint scale;
ensure_pango_context (theme);
lang = pango_context_get_language (theme->context);
metrics = pango_context_get_metrics (theme->context, description, lang);
ascent = pango_font_metrics_get_ascent (metrics);
descent = pango_font_metrics_get_descent (metrics);
pango_font_metrics_unref (metrics);
title_height = PANGO_PIXELS (ascent + descent);
scale = get_window_scaling_factor ();
title_height *= scale;
height = GINT_TO_POINTER (title_height);
g_hash_table_insert (theme->title_heights, size, height);
}
return title_height;
}
static PangoLayout *
create_title_layout (MetaTheme *theme,
const gchar *variant,
MetaFrameType type,
MetaFrameFlags flags,
const gchar *title)
{
PangoLayout *layout;
PangoFontDescription *font_desc;
ensure_pango_context (theme);
layout = pango_layout_new (theme->context);
if (title)
pango_layout_set_text (layout, title, -1);
pango_layout_set_auto_dir (layout, FALSE);
pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END);
pango_layout_set_single_paragraph_mode (layout, TRUE);
font_desc = get_title_font_desc (theme, variant, type, flags);
pango_layout_set_font_description (layout, font_desc);
return layout;
}
static void
notify_gtk_theme_name_cb (GtkSettings *settings,
GParamSpec *pspec,
MetaTheme *theme)
{
g_free (theme->gtk_theme_name);
g_object_get (settings, "gtk-theme-name", &theme->gtk_theme_name, NULL);
meta_theme_invalidate (theme);
}
static void
meta_theme_constructed (GObject *object)
{
MetaTheme *theme;
G_OBJECT_CLASS (meta_theme_parent_class)->constructed (object);
theme = META_THEME (object);
if (theme->type == META_THEME_TYPE_GTK)
theme->impl = g_object_new (META_TYPE_THEME_GTK, NULL);
else if (theme->type == META_THEME_TYPE_METACITY)
theme->impl = g_object_new (META_TYPE_THEME_METACITY, NULL);
else
g_assert_not_reached ();
meta_theme_impl_set_composited (theme->impl, theme->composited);
}
static void
meta_theme_dispose (GObject *object)
{
MetaTheme *theme;
theme = META_THEME (object);
g_clear_object (&theme->impl);
if (theme->gtk_theme_name_id > 0)
{
GtkSettings *settings;
settings = gtk_settings_get_default ();
g_signal_handler_disconnect (settings, theme->gtk_theme_name_id);
theme->gtk_theme_name_id = 0;
}
g_clear_pointer (&theme->variants, g_hash_table_destroy);
g_clear_object (&theme->context);
g_clear_pointer (&theme->font_descs, g_hash_table_destroy);
g_clear_pointer (&theme->title_heights, g_hash_table_destroy);
G_OBJECT_CLASS (meta_theme_parent_class)->dispose (object);
}
static void
meta_theme_finalize (GObject *object)
{
MetaTheme *theme;
theme = META_THEME (object);
if (theme->titlebar_font)
{
pango_font_description_free (theme->titlebar_font);
theme->titlebar_font = NULL;
}
g_free (theme->gtk_theme_name);
G_OBJECT_CLASS (meta_theme_parent_class)->finalize (object);
}
static void
meta_theme_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
MetaTheme *theme;
theme = META_THEME (object);
switch (property_id)
{
case PROP_TYPE:
theme->type = g_value_get_enum (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
meta_theme_install_properties (GObjectClass *object_class)
{
properties[PROP_TYPE] =
g_param_spec_enum ("type", "type", "type",
META_TYPE_THEME_TYPE, META_THEME_TYPE_GTK,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, LAST_PROP, properties);
}
static void
meta_theme_class_init (MetaThemeClass *theme_class)
{
GObjectClass *object_class;
object_class = G_OBJECT_CLASS (theme_class);
object_class->constructed = meta_theme_constructed;
object_class->dispose = meta_theme_dispose;
object_class->finalize = meta_theme_finalize;
object_class->set_property = meta_theme_set_property;
meta_theme_install_properties (object_class);
}
static void
meta_theme_init (MetaTheme *theme)
{
theme->composited = TRUE;
theme->variants = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, g_object_unref);
theme->font_descs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
(GDestroyNotify) pango_font_description_free);
theme->title_heights = g_hash_table_new (NULL, NULL);
}
/**
* meta_theme_error_quark:
*
* Domain for #MetaThemeError errors.
*
* Returns: the #GQuark identifying the #MetaThemeError domain.
*/
GQuark
meta_theme_error_quark (void)
{
return g_quark_from_static_string ("meta-theme-error-quark");
}
MetaTheme *
meta_theme_new (MetaThemeType type)
{
return g_object_new (META_TYPE_THEME, "type", type, NULL);
}
gboolean
meta_theme_load (MetaTheme *theme,
const gchar *name,
GError **error)
{
if (theme->type == META_THEME_TYPE_GTK)
{
g_free (theme->gtk_theme_name);
theme->gtk_theme_name = g_strdup (name);
}
else if (theme->type == META_THEME_TYPE_METACITY)
{
GtkSettings *settings;
settings = gtk_settings_get_default ();
g_free (theme->gtk_theme_name);
g_object_get (settings, "gtk-theme-name", &theme->gtk_theme_name, NULL);
if (theme->gtk_theme_name_id == 0)
{
theme->gtk_theme_name_id =
g_signal_connect (settings, "notify::gtk-theme-name",
G_CALLBACK (notify_gtk_theme_name_cb), theme);
}
}
else
{
g_assert_not_reached ();
}
return META_THEME_IMPL_GET_CLASS (theme->impl)->load (theme->impl, name,
error);
}
void
meta_theme_invalidate (MetaTheme *theme)
{
g_hash_table_remove_all (theme->variants);
g_clear_object (&theme->context);
g_hash_table_remove_all (theme->font_descs);
g_hash_table_remove_all (theme->title_heights);
}
void
meta_theme_set_composited (MetaTheme *theme,
gboolean composited)
{
if (theme->composited == composited)
return;
theme->composited = composited;
meta_theme_impl_set_composited (theme->impl, composited);
meta_theme_invalidate (theme);
}
void
meta_theme_set_titlebar_font (MetaTheme *theme,
const PangoFontDescription *titlebar_font)
{
pango_font_description_free (theme->titlebar_font);
theme->titlebar_font = pango_font_description_copy (titlebar_font);
g_hash_table_remove_all (theme->font_descs);
g_hash_table_remove_all (theme->title_heights);
}
void
meta_theme_get_frame_borders (MetaTheme *theme,
const gchar *variant,
MetaFrameType type,
MetaFrameFlags flags,
MetaFrameBorders *borders)
{
MetaFrameStyle *style;
MetaThemeImplClass *impl_class;
MetaStyleInfo *style_info;
gint title_height;
g_return_if_fail (type < META_FRAME_TYPE_LAST);
meta_frame_borders_clear (borders);
style = get_frame_style (theme, type, flags);
/* Parser is not supposed to allow this currently */
if (style == NULL)
return;
impl_class = META_THEME_IMPL_GET_CLASS (theme->impl);
style_info = get_style_info (theme, variant);
title_height = get_title_height (theme, variant, type, flags);
impl_class->get_frame_borders (theme->impl, style->layout, style_info,
title_height, flags, type, borders);
}
void
meta_theme_calc_geometry (MetaTheme *theme,
const gchar *variant,
MetaFrameType type,
MetaFrameFlags flags,
gint client_width,
gint client_height,
const MetaButtonLayout *button_layout,
MetaFrameGeometry *fgeom)
{
MetaFrameStyle *style;
MetaThemeImplClass *impl_class;
MetaStyleInfo *style_info;
gint title_height;
g_return_if_fail (type < META_FRAME_TYPE_LAST);
style = get_frame_style (theme, type, flags);
/* Parser is not supposed to allow this currently */
if (style == NULL)
return;
impl_class = META_THEME_IMPL_GET_CLASS (theme->impl);
style_info = get_style_info (theme, variant);
title_height = get_title_height (theme, variant, type, flags);
impl_class->calc_geometry (theme->impl, style->layout, style_info,
title_height, flags, client_width, client_height,
button_layout, type, fgeom);
}
void
meta_theme_draw_frame (MetaTheme *theme,
const gchar *variant,
cairo_t *cr,
MetaFrameType type,
MetaFrameFlags flags,
gint client_width,
gint client_height,
const gchar *title,
const MetaButtonLayout *button_layout,
MetaButtonState button_states[META_BUTTON_TYPE_LAST],
GdkPixbuf *mini_icon,
GdkPixbuf *icon)
{
MetaFrameStyle *style;
MetaThemeImplClass *impl_class;
MetaStyleInfo *style_info;
gint title_height;
PangoLayout *title_layout;
MetaFrameGeometry fgeom;
g_return_if_fail (type < META_FRAME_TYPE_LAST);
style = get_frame_style (theme, type, flags);
/* Parser is not supposed to allow this currently */
if (style == NULL)
return;
impl_class = META_THEME_IMPL_GET_CLASS (theme->impl);
style_info = get_style_info (theme, variant);
title_height = get_title_height (theme, variant, type, flags);
title_layout = create_title_layout (theme, variant, type, flags, title);
impl_class->calc_geometry (theme->impl, style->layout, style_info,
title_height, flags, client_width, client_height,
button_layout, type, &fgeom);
impl_class->draw_frame (theme->impl, style, style_info, cr, &fgeom,
title_layout, flags, button_states, mini_icon, icon);
g_object_unref (title_layout);
}