/* * 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); }