summaryrefslogtreecommitdiffstats
path: root/app/gui/themes.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/gui/themes.c')
-rw-r--r--app/gui/themes.c535
1 files changed, 535 insertions, 0 deletions
diff --git a/app/gui/themes.c b/app/gui/themes.c
new file mode 100644
index 0000000..0307094
--- /dev/null
+++ b/app/gui/themes.c
@@ -0,0 +1,535 @@
+/* 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <gegl.h>
+#ifdef GDK_DISABLE_DEPRECATED
+#undef GDK_DISABLE_DEPRECATED
+#endif
+#include <gtk/gtk.h>
+
+#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);
+}