Gimp/app/widgets/gimpdevicestatus.c
Jehan 7de118051f app: the Device Status dockable now shows context-sensitive colors.
I moved the function to select a valid contextual RGBA format as a core API
gimp_context_get_rgba_format(). This same API is now used in context actions, as
well as in the GimpDeviceStatus widget.

The latter now shows per-device fg/bg colors relatively to the active image's
format. The tooltip will also display contextual information (i.e. either the
image's name whose space is used, or sRGB).
Of course, this all updates when switching images by connecting to the
image-changed signal.
2024-02-11 23:28:02 +01:00

647 lines
23 KiB
C

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
*
* gimpdevicestatus.c
* Copyright (C) 2003 Michael Natterer <mitch@gimp.org>
*
* 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 <gegl.h>
#include <gtk/gtk.h>
#include "libgimpbase/gimpbase.h"
#include "libgimpcolor/gimpcolor.h"
#include "libgimpwidgets/gimpwidgets.h"
#include "widgets-types.h"
#include "config/gimpguiconfig.h"
#include "core/gimp.h"
#include "core/gimpbrush.h"
#include "core/gimpcontext.h"
#include "core/gimpdatafactory.h"
#include "core/gimpgradient.h"
#include "core/gimpimage.h"
#include "core/gimplist.h"
#include "core/gimppattern.h"
#include "core/gimptoolinfo.h"
#include "gimpdnd.h"
#include "gimpdeviceinfo.h"
#include "gimpdevicemanager.h"
#include "gimpdevices.h"
#include "gimpdevicestatus.h"
#include "gimpdialogfactory.h"
#include "gimppropwidgets.h"
#include "gimpview.h"
#include "gimpwidgets-utils.h"
#include "gimpwindowstrategy.h"
#include "gimp-intl.h"
#define CELL_SIZE 20 /* The size of the view cells */
enum
{
PROP_0,
PROP_GIMP
};
struct _GimpDeviceStatusEntry
{
GimpDeviceInfo *device_info;
GimpContext *context;
GimpToolOptions *tool_options;
GtkWidget *ebox;
GtkWidget *options_hbox;
GtkWidget *tool;
GtkWidget *foreground;
GtkWidget *foreground_none;
GtkWidget *background;
GtkWidget *background_none;
GtkWidget *brush;
GtkWidget *brush_none;
GtkWidget *pattern;
GtkWidget *pattern_none;
GtkWidget *gradient;
GtkWidget *gradient_none;
};
static void gimp_device_status_constructed (GObject *object);
static void gimp_device_status_dispose (GObject *object);
static void gimp_device_status_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void gimp_device_status_device_add (GimpContainer *devices,
GimpDeviceInfo *device_info,
GimpDeviceStatus *status);
static void gimp_device_status_device_remove (GimpContainer *devices,
GimpDeviceInfo *device_info,
GimpDeviceStatus *status);
static void gimp_device_status_notify_device (GimpDeviceManager *manager,
const GParamSpec *pspec,
GimpDeviceStatus *status);
static void gimp_device_status_config_notify (GimpGuiConfig *config,
const GParamSpec *pspec,
GimpDeviceStatus *status);
static void gimp_device_status_image_changed (GimpContext *user_context,
GimpImage *image,
GimpDeviceStatus *status);
static void gimp_device_status_notify_info (GimpDeviceInfo *device_info,
const GParamSpec *pspec,
GimpDeviceStatusEntry *entry);
static void gimp_device_status_save_clicked (GtkWidget *button,
GimpDeviceStatus *status);
static void gimp_device_status_view_clicked (GtkWidget *widget,
GdkModifierType state,
const gchar *identifier);
static void
gimp_device_status_set_color_help_data (GimpDeviceStatusEntry *entry,
GimpContext *user_context);
G_DEFINE_TYPE (GimpDeviceStatus, gimp_device_status, GIMP_TYPE_EDITOR)
#define parent_class gimp_device_status_parent_class
static void
gimp_device_status_class_init (GimpDeviceStatusClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructed = gimp_device_status_constructed;
object_class->dispose = gimp_device_status_dispose;
object_class->set_property = gimp_device_status_set_property;
g_object_class_install_property (object_class, PROP_GIMP,
g_param_spec_object ("gimp", NULL, NULL,
GIMP_TYPE_GIMP,
GIMP_PARAM_WRITABLE |
G_PARAM_CONSTRUCT_ONLY));
}
static void
gimp_device_status_init (GimpDeviceStatus *status)
{
status->gimp = NULL;
status->current_device = NULL;
status->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_container_set_border_width (GTK_CONTAINER (status->vbox), 2);
gtk_box_pack_start (GTK_BOX (status), status->vbox, TRUE, TRUE, 0);
gtk_widget_show (status->vbox);
status->save_button =
gimp_editor_add_button (GIMP_EDITOR (status), GIMP_ICON_DOCUMENT_SAVE,
_("Save device status"), NULL,
G_CALLBACK (gimp_device_status_save_clicked),
NULL,
G_OBJECT (status));
}
static void
gimp_device_status_constructed (GObject *object)
{
GimpDeviceStatus *status = GIMP_DEVICE_STATUS (object);
GimpContainer *devices;
GList *list;
G_OBJECT_CLASS (parent_class)->constructed (object);
gimp_assert (GIMP_IS_GIMP (status->gimp));
devices = GIMP_CONTAINER (gimp_devices_get_manager (status->gimp));
for (list = GIMP_LIST (devices)->queue->head; list; list = list->next)
gimp_device_status_device_add (devices, list->data, status);
g_signal_connect_object (devices, "add",
G_CALLBACK (gimp_device_status_device_add),
status, 0);
g_signal_connect_object (devices, "remove",
G_CALLBACK (gimp_device_status_device_remove),
status, 0);
g_signal_connect (devices, "notify::current-device",
G_CALLBACK (gimp_device_status_notify_device),
status);
gimp_device_status_notify_device (GIMP_DEVICE_MANAGER (devices), NULL, status);
g_signal_connect_object (status->gimp->config, "notify::devices-share-tool",
G_CALLBACK (gimp_device_status_config_notify),
status, 0);
gimp_device_status_config_notify (GIMP_GUI_CONFIG (status->gimp->config),
NULL, status);
g_signal_connect_object (gimp_get_user_context (status->gimp), "image-changed",
G_CALLBACK (gimp_device_status_image_changed),
status, 0);
}
static void
gimp_device_status_dispose (GObject *object)
{
GimpDeviceStatus *status = GIMP_DEVICE_STATUS (object);
if (status->devices)
{
GList *list;
for (list = status->devices; list; list = list->next)
{
GimpDeviceStatusEntry *entry = list->data;
g_signal_handlers_disconnect_by_func (entry->device_info,
gimp_device_status_notify_info,
entry);
g_object_unref (entry->context);
g_slice_free (GimpDeviceStatusEntry, entry);
}
g_list_free (status->devices);
status->devices = NULL;
g_signal_handlers_disconnect_by_func (gimp_devices_get_manager (status->gimp),
gimp_device_status_notify_device,
status);
}
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
gimp_device_status_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GimpDeviceStatus *status = GIMP_DEVICE_STATUS (object);
switch (property_id)
{
case PROP_GIMP:
status->gimp = g_value_get_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pack_prop_widget (GtkBox *hbox,
GtkWidget *widget,
GtkWidget **none_widget)
{
GtkSizeGroup *size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
gtk_size_group_add_widget (size_group, widget);
gtk_widget_show (widget);
*none_widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_box_pack_start (GTK_BOX (hbox), *none_widget, FALSE, FALSE, 0);
gtk_size_group_add_widget (size_group, *none_widget);
g_object_unref (size_group);
}
static void
gimp_device_status_device_add (GimpContainer *devices,
GimpDeviceInfo *device_info,
GimpDeviceStatus *status)
{
GimpDeviceStatusEntry *entry;
GdkDisplay *display;
GClosure *closure;
GParamSpec *pspec;
GtkWidget *vbox;
GtkWidget *hbox;
GtkWidget *label;
gchar *name;
entry = g_slice_new0 (GimpDeviceStatusEntry);
status->devices = g_list_prepend (status->devices, entry);
entry->device_info = device_info;
entry->context = gimp_context_new (GIMP_TOOL_PRESET (device_info)->gimp,
gimp_object_get_name (device_info),
NULL);
gimp_context_define_properties (entry->context,
GIMP_CONTEXT_PROP_MASK_TOOL |
GIMP_CONTEXT_PROP_MASK_FOREGROUND |
GIMP_CONTEXT_PROP_MASK_BACKGROUND |
GIMP_CONTEXT_PROP_MASK_BRUSH |
GIMP_CONTEXT_PROP_MASK_PATTERN |
GIMP_CONTEXT_PROP_MASK_GRADIENT,
FALSE);
closure = g_cclosure_new (G_CALLBACK (gimp_device_status_notify_info),
entry, NULL);
g_object_watch_closure (G_OBJECT (status), closure);
g_signal_connect_closure (device_info, "notify", closure,
FALSE);
entry->ebox = gtk_event_box_new ();
gtk_box_pack_start (GTK_BOX (status->vbox), entry->ebox,
FALSE, FALSE, 0);
gtk_widget_show (entry->ebox);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4);
gtk_container_set_border_width (GTK_CONTAINER (vbox), 4);
gtk_container_add (GTK_CONTAINER (entry->ebox), vbox);
gtk_widget_show (vbox);
/* the device name */
gimp_device_info_get_device (device_info, &display);
if (display == NULL || display == gdk_display_get_default ())
name = g_strdup (gimp_object_get_name (device_info));
else
name = g_strdup_printf ("%s (%s)",
gimp_object_get_name (device_info),
gdk_display_get_name (display));
label = gtk_label_new (name);
g_free (name);
gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
gimp_label_set_attributes (GTK_LABEL (label),
PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD,
-1);
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
gtk_widget_show (label);
/* the row of properties */
hbox = entry->options_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
gtk_widget_show (hbox);
/* the tool */
entry->tool = gimp_prop_view_new (G_OBJECT (entry->context), "tool",
entry->context, CELL_SIZE);
gtk_box_pack_start (GTK_BOX (hbox), entry->tool, FALSE, FALSE, 0);
/* the foreground color */
entry->foreground = gimp_prop_color_area_new (G_OBJECT (entry->context),
"foreground",
CELL_SIZE, CELL_SIZE,
GIMP_COLOR_AREA_FLAT);
gtk_widget_add_events (entry->foreground,
GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
pack_prop_widget (GTK_BOX (hbox), entry->foreground, &entry->foreground_none);
/* the background color */
entry->background = gimp_prop_color_area_new (G_OBJECT (entry->context),
"background",
CELL_SIZE, CELL_SIZE,
GIMP_COLOR_AREA_FLAT);
gtk_widget_add_events (entry->background,
GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
pack_prop_widget (GTK_BOX (hbox), entry->background, &entry->background_none);
/* the brush */
entry->brush = gimp_prop_view_new (G_OBJECT (entry->context), "brush",
entry->context, CELL_SIZE);
GIMP_VIEW (entry->brush)->clickable = TRUE;
GIMP_VIEW (entry->brush)->show_popup = TRUE;
pack_prop_widget (GTK_BOX (hbox), entry->brush, &entry->brush_none);
g_signal_connect (entry->brush, "clicked",
G_CALLBACK (gimp_device_status_view_clicked),
"gimp-brush-grid|gimp-brush-list");
/* the pattern */
entry->pattern = gimp_prop_view_new (G_OBJECT (entry->context), "pattern",
entry->context, CELL_SIZE);
GIMP_VIEW (entry->pattern)->clickable = TRUE;
GIMP_VIEW (entry->pattern)->show_popup = TRUE;
pack_prop_widget (GTK_BOX (hbox), entry->pattern, &entry->pattern_none);
g_signal_connect (entry->pattern, "clicked",
G_CALLBACK (gimp_device_status_view_clicked),
"gimp-pattern-grid|gimp-pattern-list");
/* the gradient */
entry->gradient = gimp_prop_view_new (G_OBJECT (entry->context), "gradient",
entry->context, 2 * CELL_SIZE);
GIMP_VIEW (entry->gradient)->clickable = TRUE;
GIMP_VIEW (entry->gradient)->show_popup = TRUE;
pack_prop_widget (GTK_BOX (hbox), entry->gradient, &entry->gradient_none);
g_signal_connect (entry->gradient, "clicked",
G_CALLBACK (gimp_device_status_view_clicked),
"gimp-gradient-list|gimp-gradient-grid");
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (device_info),
"tool-options");
gimp_device_status_notify_info (device_info, pspec, entry);
}
static void
gimp_device_status_device_remove (GimpContainer *devices,
GimpDeviceInfo *device_info,
GimpDeviceStatus *status)
{
GList *list;
for (list = status->devices; list; list = list->next)
{
GimpDeviceStatusEntry *entry = list->data;
if (entry->device_info == device_info)
{
status->devices = g_list_remove (status->devices, entry);
g_signal_handlers_disconnect_by_func (entry->device_info,
gimp_device_status_notify_info,
entry);
g_object_unref (entry->context);
g_slice_free (GimpDeviceStatusEntry, entry);
return;
}
}
}
GtkWidget *
gimp_device_status_new (Gimp *gimp)
{
g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
return g_object_new (GIMP_TYPE_DEVICE_STATUS,
"gimp", gimp,
NULL);
}
/* private functions */
static void
gimp_device_status_notify_device (GimpDeviceManager *manager,
const GParamSpec *pspec,
GimpDeviceStatus *status)
{
GList *list;
status->current_device = gimp_device_manager_get_current_device (manager);
for (list = status->devices; list; list = list->next)
{
GimpDeviceStatusEntry *entry = list->data;
GtkWidget *widget = entry->ebox;
GtkStyleContext *style = gtk_widget_get_style_context (widget);
if (entry->device_info == status->current_device)
{
gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_SELECTED, TRUE);
gtk_style_context_add_class (style, GTK_STYLE_CLASS_VIEW);
}
else
{
gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_NORMAL, TRUE);
gtk_style_context_remove_class (style, GTK_STYLE_CLASS_VIEW);
}
}
}
static void
gimp_device_status_config_notify (GimpGuiConfig *config,
const GParamSpec *pspec,
GimpDeviceStatus *status)
{
gboolean show_options;
GList *list;
show_options = ! GIMP_GUI_CONFIG (status->gimp->config)->devices_share_tool;
for (list = status->devices; list; list = list->next)
{
GimpDeviceStatusEntry *entry = list->data;
gtk_widget_set_visible (entry->options_hbox, show_options);
}
}
static void
gimp_device_status_image_changed (GimpContext *user_context,
GimpImage *image,
GimpDeviceStatus *status)
{
GList *list;
for (list = status->devices; list; list = list->next)
{
GimpDeviceStatusEntry *entry = list->data;
gimp_device_status_set_color_help_data (entry, user_context);
}
}
static void
toggle_prop_visible (GtkWidget *widget,
GtkWidget *widget_none,
gboolean available)
{
gtk_widget_set_visible (widget, available);
gtk_widget_set_visible (widget_none, ! available);
}
static void
gimp_device_status_notify_info (GimpDeviceInfo *device_info,
const GParamSpec *pspec,
GimpDeviceStatusEntry *entry)
{
GimpToolOptions *tool_options = GIMP_TOOL_PRESET (device_info)->tool_options;
if (tool_options != entry->tool_options)
{
GimpContextPropMask serialize_props;
entry->tool_options = tool_options;
gimp_context_set_parent (entry->context, GIMP_CONTEXT (tool_options));
serialize_props =
gimp_context_get_serialize_properties (GIMP_CONTEXT (tool_options));
toggle_prop_visible (entry->foreground,
entry->foreground_none,
serialize_props & GIMP_CONTEXT_PROP_MASK_FOREGROUND);
toggle_prop_visible (entry->background,
entry->background_none,
serialize_props & GIMP_CONTEXT_PROP_MASK_BACKGROUND);
toggle_prop_visible (entry->brush,
entry->brush_none,
serialize_props & GIMP_CONTEXT_PROP_MASK_BRUSH);
toggle_prop_visible (entry->pattern,
entry->pattern_none,
serialize_props & GIMP_CONTEXT_PROP_MASK_PATTERN);
toggle_prop_visible (entry->gradient,
entry->gradient_none,
serialize_props & GIMP_CONTEXT_PROP_MASK_GRADIENT);
}
if (! gimp_device_info_get_device (device_info, NULL) ||
gimp_device_info_get_mode (device_info) == GDK_MODE_DISABLED)
{
gtk_widget_hide (entry->ebox);
}
else
{
gtk_widget_show (entry->ebox);
}
if (! strcmp (pspec->name, "tool-options"))
gimp_device_status_set_color_help_data (entry,
gimp_get_user_context (entry->context->gimp));
}
static void
gimp_device_status_save_clicked (GtkWidget *button,
GimpDeviceStatus *status)
{
gimp_devices_save (status->gimp, TRUE);
}
static void
gimp_device_status_view_clicked (GtkWidget *widget,
GdkModifierType state,
const gchar *identifier)
{
GimpDeviceStatus *status;
GimpDialogFactory *dialog_factory;
status = GIMP_DEVICE_STATUS (gtk_widget_get_ancestor (widget,
GIMP_TYPE_DEVICE_STATUS));
dialog_factory = gimp_dialog_factory_get_singleton ();
gimp_window_strategy_show_dockable_dialog (GIMP_WINDOW_STRATEGY (gimp_get_window_strategy (status->gimp)),
status->gimp,
dialog_factory,
gimp_widget_get_monitor (widget),
identifier);
}
static void
gimp_device_status_set_color_help_data (GimpDeviceStatusEntry *entry,
GimpContext *user_context)
{
const Babl *format;
GimpImage *image = NULL;
GeglColor *color;
guchar rgb[4];
gchar *buf;
/* Note: we don't set the GeglColor on purpose. Whatever is the specific
* color space is too disconnected to this GUI usage and showing info in
* this space would only be confusing.
*/
format = gimp_context_get_rgba_format (user_context, NULL, "u8", &image);
color = gimp_context_get_foreground (entry->context);
gegl_color_get_pixel (color, format, rgb);
if (image != NULL)
buf = g_strdup_printf (_("Foreground: %d, %d, %d (in color space of \"%s\")"),
rgb[0], rgb[1], rgb[2],
gimp_image_get_display_name (image));
else
buf = g_strdup_printf (_("Foreground: %d, %d, %d (in sRGB)"), rgb[0], rgb[1], rgb[2]);
gimp_help_set_help_data (entry->foreground, buf, NULL);
g_free (buf);
color = gimp_context_get_background (entry->context);
gegl_color_get_pixel (color, format, rgb);
if (image != NULL)
buf = g_strdup_printf (_("Background: %d, %d, %d (in color space of \"%s\")"),
rgb[0], rgb[1], rgb[2],
gimp_image_get_display_name (image));
else
buf = g_strdup_printf (_("Background: %d, %d, %d (in sRGB)"), rgb[0], rgb[1], rgb[2]);
gimp_help_set_help_data (entry->background, buf, NULL);
g_free (buf);
}