/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* 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 3 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
#ifdef GDK_DISABLE_DEPRECATED
#undef GDK_DISABLE_DEPRECATED
#endif
#include
#include "libgimpbase/gimpbase.h"
#include "libgimpconfig/gimpconfig.h"
#include "gui-types.h"
#include "config/gimpguiconfig.h"
#include "core/gimp.h"
#include "themes.h"
#include "gimp-intl.h"
/* local function prototypes */
static void themes_write_style (GimpGuiConfig *config,
GOutputStream *output,
GError **error);
static void themes_apply_theme (Gimp *gimp,
GimpGuiConfig *config);
static void themes_list_themes_foreach (gpointer key,
gpointer value,
gpointer data);
static gint themes_name_compare (const void *p1,
const void *p2);
static void themes_theme_change_notify (GimpGuiConfig *config,
GParamSpec *pspec,
Gimp *gimp);
static void themes_fix_pixbuf_style (void);
static void themes_draw_pixbuf_layout (GtkStyle *style,
GdkWindow *window,
GtkStateType state_type,
gboolean use_text,
GdkRectangle *area,
GtkWidget *widget,
const gchar *detail,
gint x,
gint y,
PangoLayout *layout);
/* private variables */
static GHashTable *themes_hash = NULL;
static GtkStyleClass *pixbuf_style_class = NULL;
/* public functions */
void
themes_init (Gimp *gimp)
{
GimpGuiConfig *config;
gchar *themerc;
g_return_if_fail (GIMP_IS_GIMP (gimp));
config = GIMP_GUI_CONFIG (gimp->config);
themes_hash = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
g_object_unref);
if (config->theme_path)
{
GList *path;
GList *list;
path = gimp_config_path_expand_to_files (config->theme_path, NULL);
for (list = path; list; list = g_list_next (list))
{
GFile *dir = list->data;
GFileEnumerator *enumerator;
enumerator =
g_file_enumerate_children (dir,
G_FILE_ATTRIBUTE_STANDARD_NAME ","
G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
G_FILE_ATTRIBUTE_STANDARD_TYPE,
G_FILE_QUERY_INFO_NONE,
NULL, NULL);
if (enumerator)
{
GFileInfo *info;
while ((info = g_file_enumerator_next_file (enumerator,
NULL, NULL)))
{
if (! g_file_info_get_is_hidden (info) &&
g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
{
GFile *file;
const gchar *name;
gchar *basename;
file = g_file_enumerator_get_child (enumerator, info);
name = gimp_file_get_utf8_name (file);
basename = g_path_get_basename (name);
if (gimp->be_verbose)
g_print ("Adding theme '%s' (%s)\n",
basename, name);
g_hash_table_insert (themes_hash, basename, file);
}
g_object_unref (info);
}
g_object_unref (enumerator);
}
}
g_list_free_full (path, (GDestroyNotify) g_object_unref);
}
themes_apply_theme (gimp, config);
themerc = gimp_personal_rc_file ("themerc");
gtk_rc_parse (themerc);
g_free (themerc);
themes_fix_pixbuf_style ();
g_signal_connect (config, "notify::theme",
G_CALLBACK (themes_theme_change_notify),
gimp);
g_signal_connect (config, "notify::compact-sliders",
G_CALLBACK (themes_theme_change_notify),
gimp);
}
void
themes_exit (Gimp *gimp)
{
g_return_if_fail (GIMP_IS_GIMP (gimp));
if (themes_hash)
{
g_signal_handlers_disconnect_by_func (gimp->config,
themes_theme_change_notify,
gimp);
g_hash_table_destroy (themes_hash);
themes_hash = NULL;
}
g_clear_pointer (&pixbuf_style_class, g_type_class_unref);
}
gchar **
themes_list_themes (Gimp *gimp,
gint *n_themes)
{
g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
g_return_val_if_fail (n_themes != NULL, NULL);
*n_themes = g_hash_table_size (themes_hash);
if (*n_themes > 0)
{
gchar **themes;
gchar **index;
themes = g_new0 (gchar *, *n_themes + 1);
index = themes;
g_hash_table_foreach (themes_hash, themes_list_themes_foreach, &index);
qsort (themes, *n_themes, sizeof (gchar *), themes_name_compare);
return themes;
}
return NULL;
}
GFile *
themes_get_theme_dir (Gimp *gimp,
const gchar *theme_name)
{
g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
if (! theme_name)
theme_name = GIMP_CONFIG_DEFAULT_THEME;
return g_hash_table_lookup (themes_hash, theme_name);
}
GFile *
themes_get_theme_file (Gimp *gimp,
const gchar *first_component,
...)
{
GimpGuiConfig *gui_config;
GFile *file;
const gchar *component;
va_list args;
g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
g_return_val_if_fail (first_component != NULL, NULL);
gui_config = GIMP_GUI_CONFIG (gimp->config);
file = g_object_ref (themes_get_theme_dir (gimp, gui_config->theme));
component = first_component;
va_start (args, first_component);
do
{
GFile *tmp = g_file_get_child (file, component);
g_object_unref (file);
file = tmp;
}
while ((component = va_arg (args, gchar *)));
va_end (args);
if (! g_file_query_exists (file, NULL))
{
g_object_unref (file);
file = g_object_ref (themes_get_theme_dir (gimp, NULL));
component = first_component;
va_start (args, first_component);
do
{
GFile *tmp = g_file_get_child (file, component);
g_object_unref (file);
file = tmp;
}
while ((component = va_arg (args, gchar *)));
va_end (args);
}
return file;
}
/* private functions */
static void
themes_write_style (GimpGuiConfig *config,
GOutputStream *output,
GError **error)
{
if (! *error)
{
g_output_stream_printf (
output, NULL, NULL, error,
"style \"gimp-spin-scale-style\"\n"
"{\n"
" GimpSpinScale::compact = %d\n"
"}\n"
"\n"
"class \"GimpSpinScale\" style \"gimp-spin-scale-style\"\n"
"\n",
config->compact_sliders);
}
}
static void
themes_apply_theme (Gimp *gimp,
GimpGuiConfig *config)
{
GFile *themerc;
GOutputStream *output;
GError *error = NULL;
g_return_if_fail (GIMP_IS_GIMP (gimp));
themerc = gimp_directory_file ("themerc", NULL);
if (gimp->be_verbose)
g_print ("Writing '%s'\n", gimp_file_get_utf8_name (themerc));
output = G_OUTPUT_STREAM (g_file_replace (themerc,
NULL, FALSE, G_FILE_CREATE_NONE,
NULL, &error));
if (! output)
{
gimp_message_literal (gimp, NULL, GIMP_MESSAGE_ERROR, error->message);
g_clear_error (&error);
}
else
{
GFile *theme_dir = themes_get_theme_dir (gimp, config->theme);
GFile *gtkrc_user;
GSList *gtkrc_files = NULL;
GSList *iter;
if (theme_dir)
{
gtkrc_files = g_slist_prepend (
gtkrc_files,
g_file_get_child (theme_dir, "gtkrc"));
}
else
{
/* get the hardcoded default theme gtkrc */
gtkrc_files = g_slist_prepend (
gtkrc_files,
g_file_new_for_path (gimp_gtkrc ()));
}
gtkrc_files = g_slist_prepend (
gtkrc_files,
gimp_sysconf_directory_file ("gtkrc", NULL));
gtkrc_user = gimp_directory_file ("gtkrc", NULL);
gtkrc_files = g_slist_prepend (
gtkrc_files,
gtkrc_user);
gtkrc_files = g_slist_reverse (gtkrc_files);
g_output_stream_printf (
output, NULL, NULL, &error,
"# GIMP themerc\n"
"#\n"
"# This file is written on GIMP startup and on every theme change.\n"
"# It is NOT supposed to be edited manually. Edit your personal\n"
"# gtkrc file instead (%s).\n"
"\n",
gimp_file_get_utf8_name (gtkrc_user));
themes_write_style (config, output, &error);
for (iter = gtkrc_files; ! error && iter; iter = g_slist_next (iter))
{
GFile *file = iter->data;
if (g_file_query_exists (file, NULL))
{
gchar *path;
gchar *esc_path;
path = g_file_get_path (file);
esc_path = g_strescape (path, NULL);
g_free (path);
g_output_stream_printf (
output, NULL, NULL, &error,
"include \"%s\"\n",
esc_path);
g_free (esc_path);
}
}
if (! error)
{
g_output_stream_printf (
output, NULL, NULL, &error,
"\n"
"# end of themerc\n");
}
if (error)
{
GCancellable *cancellable = g_cancellable_new ();
gimp_message (gimp, NULL, GIMP_MESSAGE_ERROR,
_("Error writing '%s': %s"),
gimp_file_get_utf8_name (themerc), error->message);
g_clear_error (&error);
/* Cancel the overwrite initiated by g_file_replace(). */
g_cancellable_cancel (cancellable);
g_output_stream_close (output, cancellable, NULL);
g_object_unref (cancellable);
}
else if (! g_output_stream_close (output, NULL, &error))
{
gimp_message (gimp, NULL, GIMP_MESSAGE_ERROR,
_("Error closing '%s': %s"),
gimp_file_get_utf8_name (themerc), error->message);
g_clear_error (&error);
}
g_slist_free_full (gtkrc_files, g_object_unref);
g_object_unref (output);
}
g_object_unref (themerc);
}
static void
themes_list_themes_foreach (gpointer key,
gpointer value,
gpointer data)
{
gchar ***index = data;
**index = g_strdup ((gchar *) key);
(*index)++;
}
static gint
themes_name_compare (const void *p1,
const void *p2)
{
return strcmp (* (char **) p1, * (char **) p2);
}
static void
themes_theme_change_notify (GimpGuiConfig *config,
GParamSpec *pspec,
Gimp *gimp)
{
themes_apply_theme (gimp, config);
gtk_rc_reparse_all ();
themes_fix_pixbuf_style ();
}
static void
themes_fix_pixbuf_style (void)
{
/* This is a "quick'n dirty" trick to get appropriate colors for
* themes in GTK+2, and in particular dark themes which would display
* insensitive items with a barely readable layout.
*
* This piece of code partly duplicates code from GTK+2 (slightly
* modified to get readable insensitive items) and will likely have to
* be removed for GIMP 3.
*
* See https://bugzilla.gnome.org/show_bug.cgi?id=770424
*/
if (! pixbuf_style_class)
{
GType type = g_type_from_name ("PixbufStyle");
if (type)
{
pixbuf_style_class = g_type_class_ref (type);
if (pixbuf_style_class)
pixbuf_style_class->draw_layout = themes_draw_pixbuf_layout;
}
}
}
static void
themes_draw_pixbuf_layout (GtkStyle *style,
GdkWindow *window,
GtkStateType state_type,
gboolean use_text,
GdkRectangle *area,
GtkWidget *widget,
const gchar *detail,
gint x,
gint y,
PangoLayout *layout)
{
GdkGC *gc;
gc = use_text ? style->text_gc[state_type] : style->fg_gc[state_type];
if (area)
gdk_gc_set_clip_rectangle (gc, area);
if (state_type == GTK_STATE_INSENSITIVE)
{
GdkGC *copy = gdk_gc_new (window);
GdkGCValues orig;
GdkColor fore;
guint16 r, g, b;
gdk_gc_copy (copy, gc);
gdk_gc_get_values (gc, &orig);
r = 0x40 + (((orig.foreground.pixel >> 16) & 0xff) >> 1);
g = 0x40 + (((orig.foreground.pixel >> 8) & 0xff) >> 1);
b = 0x40 + (((orig.foreground.pixel >> 0) & 0xff) >> 1);
fore.pixel = (r << 16) | (g << 8) | b;
fore.red = r * 257;
fore.green = g * 257;
fore.blue = b * 257;
gdk_gc_set_foreground (copy, &fore);
gdk_draw_layout (window, copy, x, y, layout);
g_object_unref (copy);
}
else
{
gdk_draw_layout (window, gc, x, y, layout);
}
if (area)
gdk_gc_set_clip_rectangle (gc, NULL);
}