2015-05-06 22:15:30 +02:00
|
|
|
/* LIBGIMP - The GIMP Library
|
|
|
|
* Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
|
|
|
|
*
|
|
|
|
* gimpwidgets.c
|
|
|
|
* Copyright (C) 2000 Michael Natterer <mitch@gimp.org>
|
|
|
|
*
|
|
|
|
* This library is free software: you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 3 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library 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
|
|
|
|
* Library General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library. If not, see
|
2018-07-11 23:27:07 +02:00
|
|
|
* <https://www.gnu.org/licenses/>.
|
2015-05-06 22:15:30 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
2015-05-08 22:53:49 -04:00
|
|
|
#include <lcms2.h>
|
|
|
|
|
|
|
|
#include <gegl.h>
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
|
2015-05-06 22:15:30 +02:00
|
|
|
#ifdef G_OS_WIN32
|
2021-12-17 21:58:03 +00:00
|
|
|
#ifdef _WIN32_WINNT
|
|
|
|
#undef _WIN32_WINNT
|
|
|
|
#endif
|
|
|
|
#define _WIN32_WINNT 0x0600
|
2015-05-06 22:15:30 +02:00
|
|
|
#include <windows.h>
|
2021-12-17 21:58:03 +00:00
|
|
|
#include <icm.h>
|
2015-05-06 22:15:30 +02:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef GDK_WINDOWING_QUARTZ
|
|
|
|
#include <Carbon/Carbon.h>
|
|
|
|
#include <ApplicationServices/ApplicationServices.h>
|
|
|
|
#include <CoreServices/CoreServices.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "libgimpcolor/gimpcolor.h"
|
2015-05-09 01:20:50 +02:00
|
|
|
#include "libgimpconfig/gimpconfig.h"
|
2015-05-06 22:15:30 +02:00
|
|
|
|
|
|
|
#include "gimpwidgetstypes.h"
|
|
|
|
|
|
|
|
#include "gimpsizeentry.h"
|
|
|
|
#include "gimpwidgetsutils.h"
|
|
|
|
|
|
|
|
#include "libgimp/libgimp-intl.h"
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* SECTION: gimpwidgetsutils
|
|
|
|
* @title: GimpWidgetsUtils
|
|
|
|
* @short_description: A collection of helper functions.
|
|
|
|
*
|
|
|
|
* A collection of helper functions.
|
|
|
|
**/
|
|
|
|
|
|
|
|
|
|
|
|
static GtkWidget *
|
|
|
|
find_mnemonic_widget (GtkWidget *widget,
|
|
|
|
gint level)
|
|
|
|
{
|
|
|
|
gboolean can_focus;
|
|
|
|
|
|
|
|
g_object_get (widget, "can-focus", &can_focus, NULL);
|
|
|
|
|
|
|
|
if (GTK_WIDGET_GET_CLASS (widget)->activate_signal ||
|
|
|
|
can_focus ||
|
|
|
|
GTK_WIDGET_GET_CLASS (widget)->mnemonic_activate !=
|
|
|
|
GTK_WIDGET_CLASS (g_type_class_peek (GTK_TYPE_WIDGET))->mnemonic_activate)
|
|
|
|
{
|
|
|
|
return widget;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (GIMP_IS_SIZE_ENTRY (widget))
|
|
|
|
{
|
2018-05-25 04:08:59 +02:00
|
|
|
GimpSizeEntry *entry = GIMP_SIZE_ENTRY (widget);
|
|
|
|
gint n_fields = gimp_size_entry_get_n_fields (entry);
|
2015-05-06 22:15:30 +02:00
|
|
|
|
2018-05-25 04:08:59 +02:00
|
|
|
return gimp_size_entry_get_help_widget (entry, n_fields - 1);
|
2015-05-06 22:15:30 +02:00
|
|
|
}
|
|
|
|
else if (GTK_IS_CONTAINER (widget))
|
|
|
|
{
|
|
|
|
GtkWidget *mnemonic_widget = NULL;
|
|
|
|
GList *children;
|
|
|
|
GList *list;
|
|
|
|
|
|
|
|
children = gtk_container_get_children (GTK_CONTAINER (widget));
|
|
|
|
|
|
|
|
for (list = children; list; list = g_list_next (list))
|
|
|
|
{
|
|
|
|
mnemonic_widget = find_mnemonic_widget (list->data, level + 1);
|
|
|
|
|
|
|
|
if (mnemonic_widget)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_list_free (children);
|
|
|
|
|
|
|
|
return mnemonic_widget;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
app, libgimpwidgets: new gimp_event_triggers_context_menu() and use it…
… for the container tree view contextual menu.
A very annoying point of contextual menus is that they happen on button
press whereas menu item selection happens on button release. When the
menu corner is positionned on the click position, nothing bad happens;
yet when place is missing on screen, the menu might get positionned over
the pointer position. And worse, the mouse position might be just over
an activatable menu item. So we end up in this weird situation where a
click implies: press, menu opens, release, random item (whatever is
below the pointer) is selected and menu closes.
To get rid of this weird case, let's have our contextual menu happen on
button release. In reality, I don't think anyone cares that it happens
on press or release, you just "click". But what you certainly don't want
is to click random menu items!
2021-06-19 00:31:40 +02:00
|
|
|
/**
|
|
|
|
* gimp_event_triggers_context_menu:
|
|
|
|
* @event: The #GdkEvent to verify.
|
|
|
|
* @on_release: Whether a menu is triggered on a button release event
|
|
|
|
* instead of a press event.
|
|
|
|
*
|
|
|
|
* Alternative of gdk_event_triggers_context_menu() with the additional
|
|
|
|
* feature of allowing a menu triggering to happen on a button release
|
|
|
|
* event. All the other rules on whether @event should trigger a
|
|
|
|
* contextual menu are exactly the same. Only the swapping to release
|
|
|
|
* state as additional feature is different.
|
|
|
|
*
|
|
|
|
* Returns: %TRUE if the event should trigger a context menu.
|
|
|
|
*
|
|
|
|
* Since: 3.0
|
|
|
|
**/
|
|
|
|
gboolean
|
|
|
|
gimp_event_triggers_context_menu (const GdkEvent *event,
|
|
|
|
gboolean on_release)
|
|
|
|
{
|
|
|
|
GdkEvent *copy_event;
|
|
|
|
gboolean trigger = FALSE;
|
|
|
|
|
|
|
|
g_return_val_if_fail (event != NULL, FALSE);
|
|
|
|
|
|
|
|
copy_event = gdk_event_copy (event);
|
|
|
|
|
|
|
|
/* It's an ugly trick because GDK only considers press events as
|
|
|
|
* menu-triggering events. We want to use the same per-platform
|
|
|
|
* conventions as set in GTK/GDK, except for this small point.
|
|
|
|
* So when we want a menu-triggering on release events, we just
|
|
|
|
* temporary simulate the event to be a PRESS event and pass it to
|
|
|
|
* the usual function.
|
|
|
|
*/
|
|
|
|
if (on_release)
|
|
|
|
{
|
|
|
|
if (event->type == GDK_BUTTON_RELEASE)
|
|
|
|
copy_event->type = GDK_BUTTON_PRESS;
|
|
|
|
else if (event->type == GDK_BUTTON_PRESS)
|
|
|
|
copy_event->type = GDK_BUTTON_RELEASE;
|
|
|
|
}
|
|
|
|
trigger = gdk_event_triggers_context_menu (copy_event);
|
|
|
|
|
|
|
|
gdk_event_free (copy_event);
|
|
|
|
|
|
|
|
return trigger;
|
|
|
|
}
|
|
|
|
|
2018-04-29 02:15:48 +02:00
|
|
|
/**
|
|
|
|
* gimp_grid_attach_aligned:
|
|
|
|
* @grid: The #GtkGrid the widgets will be attached to.
|
|
|
|
* @left: The column to start with.
|
|
|
|
* @top: The row to attach the widgets.
|
|
|
|
* @label_text: The text for the #GtkLabel which will be attached left of
|
|
|
|
* the widget.
|
|
|
|
* @xalign: The horizontal alignment of the #GtkLabel.
|
|
|
|
* @yalign: The vertical alignment of the #GtkLabel.
|
|
|
|
* @widget: The #GtkWidget to attach right of the label.
|
|
|
|
* @columns: The number of columns the widget will use.
|
|
|
|
*
|
|
|
|
* Note that the @label_text can be %NULL and that the widget will be
|
|
|
|
* attached starting at (@column + 1) in this case, too.
|
|
|
|
*
|
2019-08-01 13:06:35 +02:00
|
|
|
* Returns: (transfer none): The created #GtkLabel.
|
2018-04-29 02:15:48 +02:00
|
|
|
**/
|
|
|
|
GtkWidget *
|
|
|
|
gimp_grid_attach_aligned (GtkGrid *grid,
|
|
|
|
gint left,
|
|
|
|
gint top,
|
|
|
|
const gchar *label_text,
|
|
|
|
gfloat xalign,
|
|
|
|
gfloat yalign,
|
|
|
|
GtkWidget *widget,
|
|
|
|
gint columns)
|
|
|
|
{
|
|
|
|
GtkWidget *label = NULL;
|
|
|
|
|
|
|
|
if (label_text)
|
|
|
|
{
|
|
|
|
GtkWidget *mnemonic_widget;
|
|
|
|
|
|
|
|
label = gtk_label_new_with_mnemonic (label_text);
|
|
|
|
gtk_label_set_xalign (GTK_LABEL (label), xalign);
|
|
|
|
gtk_label_set_yalign (GTK_LABEL (label), yalign);
|
|
|
|
gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
|
|
|
|
gtk_grid_attach (grid, label, left, top, 1, 1);
|
|
|
|
gtk_widget_show (label);
|
|
|
|
|
|
|
|
mnemonic_widget = find_mnemonic_widget (widget, 0);
|
|
|
|
|
|
|
|
if (mnemonic_widget)
|
|
|
|
gtk_label_set_mnemonic_widget (GTK_LABEL (label), mnemonic_widget);
|
|
|
|
}
|
|
|
|
|
2018-05-03 04:00:16 +02:00
|
|
|
gtk_widget_set_hexpand (widget, TRUE);
|
2018-04-29 02:15:48 +02:00
|
|
|
gtk_grid_attach (grid, widget, left + 1, top, columns, 1);
|
|
|
|
gtk_widget_show (widget);
|
|
|
|
|
|
|
|
return label;
|
|
|
|
}
|
|
|
|
|
2015-05-06 22:15:30 +02:00
|
|
|
/**
|
2020-05-21 13:43:47 +02:00
|
|
|
* gimp_label_set_attributes: (skip)
|
2015-05-06 22:15:30 +02:00
|
|
|
* @label: a #GtkLabel
|
|
|
|
* @...: a list of PangoAttrType and value pairs terminated by -1.
|
|
|
|
*
|
|
|
|
* Sets Pango attributes on a #GtkLabel in a more convenient way than
|
|
|
|
* gtk_label_set_attributes().
|
|
|
|
*
|
|
|
|
* This function is useful if you want to change the font attributes
|
|
|
|
* of a #GtkLabel. This is an alternative to using PangoMarkup which
|
2016-06-25 22:54:10 +02:00
|
|
|
* is slow to parse and awkward to handle in an i18n-friendly way.
|
2015-05-06 22:15:30 +02:00
|
|
|
*
|
|
|
|
* The attributes are set on the complete label, from start to end. If
|
|
|
|
* you need to set attributes on part of the label, you will have to
|
|
|
|
* use the PangoAttributes API directly.
|
|
|
|
*
|
2015-05-31 21:18:09 +02:00
|
|
|
* Since: 2.2
|
2015-05-06 22:15:30 +02:00
|
|
|
**/
|
|
|
|
void
|
|
|
|
gimp_label_set_attributes (GtkLabel *label,
|
|
|
|
...)
|
|
|
|
{
|
|
|
|
PangoAttribute *attr = NULL;
|
|
|
|
PangoAttrList *attrs;
|
|
|
|
va_list args;
|
|
|
|
|
|
|
|
g_return_if_fail (GTK_IS_LABEL (label));
|
|
|
|
|
|
|
|
attrs = pango_attr_list_new ();
|
|
|
|
|
|
|
|
va_start (args, label);
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
PangoAttrType attr_type = va_arg (args, PangoAttrType);
|
|
|
|
|
|
|
|
if (attr_type == -1)
|
|
|
|
attr_type = PANGO_ATTR_INVALID;
|
|
|
|
|
|
|
|
switch (attr_type)
|
|
|
|
{
|
|
|
|
case PANGO_ATTR_LANGUAGE:
|
|
|
|
attr = pango_attr_language_new (va_arg (args, PangoLanguage *));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PANGO_ATTR_FAMILY:
|
|
|
|
attr = pango_attr_family_new (va_arg (args, const gchar *));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PANGO_ATTR_STYLE:
|
|
|
|
attr = pango_attr_style_new (va_arg (args, PangoStyle));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PANGO_ATTR_WEIGHT:
|
|
|
|
attr = pango_attr_weight_new (va_arg (args, PangoWeight));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PANGO_ATTR_VARIANT:
|
|
|
|
attr = pango_attr_variant_new (va_arg (args, PangoVariant));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PANGO_ATTR_STRETCH:
|
|
|
|
attr = pango_attr_stretch_new (va_arg (args, PangoStretch));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PANGO_ATTR_SIZE:
|
|
|
|
attr = pango_attr_size_new (va_arg (args, gint));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PANGO_ATTR_FONT_DESC:
|
|
|
|
attr = pango_attr_font_desc_new (va_arg (args,
|
|
|
|
const PangoFontDescription *));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PANGO_ATTR_FOREGROUND:
|
|
|
|
{
|
|
|
|
const PangoColor *color = va_arg (args, const PangoColor *);
|
|
|
|
|
|
|
|
attr = pango_attr_foreground_new (color->red,
|
|
|
|
color->green,
|
|
|
|
color->blue);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PANGO_ATTR_BACKGROUND:
|
|
|
|
{
|
|
|
|
const PangoColor *color = va_arg (args, const PangoColor *);
|
|
|
|
|
|
|
|
attr = pango_attr_background_new (color->red,
|
|
|
|
color->green,
|
|
|
|
color->blue);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PANGO_ATTR_UNDERLINE:
|
|
|
|
attr = pango_attr_underline_new (va_arg (args, PangoUnderline));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PANGO_ATTR_STRIKETHROUGH:
|
|
|
|
attr = pango_attr_strikethrough_new (va_arg (args, gboolean));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PANGO_ATTR_RISE:
|
|
|
|
attr = pango_attr_rise_new (va_arg (args, gint));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PANGO_ATTR_SCALE:
|
|
|
|
attr = pango_attr_scale_new (va_arg (args, gdouble));
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
g_warning ("%s: invalid PangoAttribute type %d",
|
|
|
|
G_STRFUNC, attr_type);
|
|
|
|
case PANGO_ATTR_INVALID:
|
|
|
|
attr = NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (attr)
|
|
|
|
{
|
|
|
|
attr->start_index = 0;
|
|
|
|
attr->end_index = -1;
|
|
|
|
pango_attr_list_insert (attrs, attr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
while (attr);
|
|
|
|
|
|
|
|
va_end (args);
|
|
|
|
|
|
|
|
gtk_label_set_attributes (label, attrs);
|
|
|
|
pango_attr_list_unref (attrs);
|
|
|
|
}
|
|
|
|
|
2019-08-05 17:13:59 +02:00
|
|
|
/**
|
|
|
|
* gimp_widget_get_monitor:
|
|
|
|
* @widget: a #GtkWidget.
|
|
|
|
*
|
|
|
|
* Returns: (transfer none): the #GdkMonitor where @widget is current displayed on.
|
|
|
|
*/
|
2018-04-29 17:27:47 +02:00
|
|
|
GdkMonitor *
|
2015-05-06 22:15:30 +02:00
|
|
|
gimp_widget_get_monitor (GtkWidget *widget)
|
|
|
|
{
|
|
|
|
GdkWindow *window;
|
|
|
|
GtkAllocation allocation;
|
|
|
|
gint x, y;
|
|
|
|
|
|
|
|
g_return_val_if_fail (GTK_IS_WIDGET (widget), 0);
|
|
|
|
|
|
|
|
window = gtk_widget_get_window (widget);
|
|
|
|
|
|
|
|
if (! window)
|
2018-04-29 17:27:47 +02:00
|
|
|
return gimp_get_monitor_at_pointer ();
|
2015-05-06 22:15:30 +02:00
|
|
|
|
|
|
|
gdk_window_get_origin (window, &x, &y);
|
|
|
|
gtk_widget_get_allocation (widget, &allocation);
|
|
|
|
|
|
|
|
if (! gtk_widget_get_has_window (widget))
|
|
|
|
{
|
|
|
|
x += allocation.x;
|
|
|
|
y += allocation.y;
|
|
|
|
}
|
|
|
|
|
|
|
|
x += allocation.width / 2;
|
|
|
|
y += allocation.height / 2;
|
|
|
|
|
2018-04-29 17:27:47 +02:00
|
|
|
return gdk_display_get_monitor_at_point (gdk_display_get_default (), x, y);
|
2015-05-06 22:15:30 +02:00
|
|
|
}
|
|
|
|
|
2019-08-05 17:13:59 +02:00
|
|
|
/**
|
|
|
|
* gimp_get_monitor_at_pointer:
|
|
|
|
*
|
|
|
|
* Returns: (transfer none): the #GdkMonitor where the pointer is.
|
|
|
|
*/
|
2018-04-29 17:27:47 +02:00
|
|
|
GdkMonitor *
|
|
|
|
gimp_get_monitor_at_pointer (void)
|
2015-05-06 22:15:30 +02:00
|
|
|
{
|
2018-04-29 17:27:47 +02:00
|
|
|
GdkDisplay *display;
|
|
|
|
GdkSeat *seat;
|
|
|
|
gint x, y;
|
2015-05-06 22:15:30 +02:00
|
|
|
|
2018-04-29 17:27:47 +02:00
|
|
|
display = gdk_display_get_default ();
|
|
|
|
seat = gdk_display_get_default_seat (display);
|
2011-03-05 21:29:21 +01:00
|
|
|
|
2018-04-29 17:27:47 +02:00
|
|
|
gdk_device_get_position (gdk_seat_get_pointer (seat),
|
|
|
|
NULL, &x, &y);
|
2015-05-06 22:15:30 +02:00
|
|
|
|
2018-04-29 17:27:47 +02:00
|
|
|
return gdk_display_get_monitor_at_point (display, x, y);
|
2015-05-06 22:15:30 +02:00
|
|
|
}
|
|
|
|
|
2016-06-01 20:39:03 +02:00
|
|
|
typedef void (* MonitorChangedCallback) (GtkWidget *, gpointer);
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
2018-04-29 17:27:47 +02:00
|
|
|
GtkWidget *widget;
|
|
|
|
GdkMonitor *monitor;
|
2016-06-01 20:39:03 +02:00
|
|
|
|
|
|
|
MonitorChangedCallback callback;
|
|
|
|
gpointer user_data;
|
|
|
|
} TrackMonitorData;
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
track_monitor_configure_event (GtkWidget *toplevel,
|
|
|
|
GdkEvent *event,
|
|
|
|
TrackMonitorData *track_data)
|
|
|
|
{
|
2018-04-29 17:27:47 +02:00
|
|
|
GdkMonitor *monitor = gimp_widget_get_monitor (toplevel);
|
2016-06-01 20:39:03 +02:00
|
|
|
|
|
|
|
if (monitor != track_data->monitor)
|
|
|
|
{
|
|
|
|
track_data->monitor = monitor;
|
|
|
|
|
|
|
|
track_data->callback (track_data->widget, track_data->user_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
track_monitor_hierarchy_changed (GtkWidget *widget,
|
|
|
|
GtkWidget *previous_toplevel,
|
|
|
|
TrackMonitorData *track_data)
|
|
|
|
{
|
|
|
|
GtkWidget *toplevel;
|
|
|
|
|
|
|
|
if (previous_toplevel)
|
|
|
|
{
|
|
|
|
g_signal_handlers_disconnect_by_func (previous_toplevel,
|
|
|
|
track_monitor_configure_event,
|
|
|
|
track_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
toplevel = gtk_widget_get_toplevel (widget);
|
|
|
|
|
|
|
|
if (GTK_IS_WINDOW (toplevel))
|
|
|
|
{
|
2018-04-29 17:27:47 +02:00
|
|
|
GClosure *closure;
|
|
|
|
GdkMonitor *monitor;
|
2016-06-01 20:39:03 +02:00
|
|
|
|
|
|
|
closure = g_cclosure_new (G_CALLBACK (track_monitor_configure_event),
|
|
|
|
track_data, NULL);
|
|
|
|
g_object_watch_closure (G_OBJECT (widget), closure);
|
|
|
|
g_signal_connect_closure (toplevel, "configure-event", closure, FALSE);
|
|
|
|
|
|
|
|
monitor = gimp_widget_get_monitor (toplevel);
|
|
|
|
|
|
|
|
if (monitor != track_data->monitor)
|
|
|
|
{
|
|
|
|
track_data->monitor = monitor;
|
|
|
|
|
|
|
|
track_data->callback (track_data->widget, track_data->user_data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* gimp_widget_track_monitor:
|
|
|
|
* @widget: a #GtkWidget
|
|
|
|
* @monitor_changed_callback: the callback when @widget's monitor changes
|
|
|
|
* @user_data: data passed to @monitor_changed_callback
|
2019-08-18 14:07:17 +02:00
|
|
|
* @user_data_destroy: destroy function for @user_data.
|
2016-06-01 20:39:03 +02:00
|
|
|
*
|
|
|
|
* This function behaves as if #GtkWidget had a signal
|
|
|
|
*
|
|
|
|
* GtkWidget::monitor_changed(GtkWidget *widget, gpointer user_data)
|
|
|
|
*
|
|
|
|
* That is emitted whenever @widget's toplevel window is moved from
|
|
|
|
* one monitor to another. This function automatically connects to
|
|
|
|
* the right toplevel #GtkWindow, even across moving @widget between
|
|
|
|
* toplevel windows.
|
|
|
|
*
|
|
|
|
* Note that this function tracks the toplevel, not @widget itself, so
|
|
|
|
* all a window's widgets are always considered to be on the same
|
|
|
|
* monitor. This is because this function is mainly used for fetching
|
|
|
|
* the new monitor's color profile, and it makes little sense to use
|
|
|
|
* different profiles for the widgets of one window.
|
|
|
|
*
|
|
|
|
* Since: 2.10
|
|
|
|
**/
|
|
|
|
void
|
2019-08-07 23:44:18 +02:00
|
|
|
gimp_widget_track_monitor (GtkWidget *widget,
|
|
|
|
GCallback monitor_changed_callback,
|
|
|
|
gpointer user_data,
|
|
|
|
GDestroyNotify user_data_destroy)
|
2016-06-01 20:39:03 +02:00
|
|
|
{
|
|
|
|
TrackMonitorData *track_data;
|
|
|
|
GtkWidget *toplevel;
|
|
|
|
|
|
|
|
g_return_if_fail (GTK_IS_WIDGET (widget));
|
|
|
|
g_return_if_fail (monitor_changed_callback != NULL);
|
|
|
|
|
|
|
|
track_data = g_new0 (TrackMonitorData, 1);
|
|
|
|
|
|
|
|
track_data->widget = widget;
|
|
|
|
track_data->callback = (MonitorChangedCallback) monitor_changed_callback;
|
|
|
|
track_data->user_data = user_data;
|
|
|
|
|
2019-08-07 23:44:18 +02:00
|
|
|
g_object_weak_ref (G_OBJECT (widget), (GWeakNotify) g_free,
|
|
|
|
track_data);
|
|
|
|
|
|
|
|
if (user_data_destroy)
|
|
|
|
g_object_weak_ref (G_OBJECT (widget), (GWeakNotify) user_data_destroy,
|
|
|
|
user_data);
|
2016-06-01 20:39:03 +02:00
|
|
|
|
|
|
|
g_signal_connect (widget, "hierarchy-changed",
|
|
|
|
G_CALLBACK (track_monitor_hierarchy_changed),
|
|
|
|
track_data);
|
|
|
|
|
|
|
|
toplevel = gtk_widget_get_toplevel (widget);
|
|
|
|
|
|
|
|
if (GTK_IS_WINDOW (toplevel))
|
|
|
|
track_monitor_hierarchy_changed (widget, NULL, track_data);
|
|
|
|
}
|
|
|
|
|
2018-04-29 17:27:47 +02:00
|
|
|
static gint
|
|
|
|
monitor_number (GdkMonitor *monitor)
|
|
|
|
{
|
|
|
|
GdkDisplay *display = gdk_monitor_get_display (monitor);
|
|
|
|
gint n_monitors = gdk_display_get_n_monitors (display);
|
|
|
|
gint i;
|
|
|
|
|
|
|
|
for (i = 0; i < n_monitors; i++)
|
|
|
|
if (gdk_display_get_monitor (display, i) == monitor)
|
|
|
|
return i;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-01-31 18:58:27 +01:00
|
|
|
/**
|
2018-04-29 17:27:47 +02:00
|
|
|
* gimp_monitor_get_color_profile:
|
|
|
|
* @monitor: a #GdkMonitor
|
2017-01-31 18:58:27 +01:00
|
|
|
*
|
2018-04-29 17:27:47 +02:00
|
|
|
* This function returns the #GimpColorProfile of @monitor
|
|
|
|
* or %NULL if there is no profile configured.
|
2017-01-31 18:58:27 +01:00
|
|
|
*
|
2018-04-29 17:27:47 +02:00
|
|
|
* Since: 3.0
|
2017-01-31 18:58:27 +01:00
|
|
|
*
|
2019-08-03 00:36:59 +02:00
|
|
|
* Returns: (nullable) (transfer full): the monitor's #GimpColorProfile,
|
|
|
|
* or %NULL.
|
2017-01-31 18:58:27 +01:00
|
|
|
**/
|
2015-07-10 22:43:53 +02:00
|
|
|
GimpColorProfile *
|
2018-04-29 17:27:47 +02:00
|
|
|
gimp_monitor_get_color_profile (GdkMonitor *monitor)
|
2015-05-06 22:15:30 +02:00
|
|
|
{
|
2015-07-10 22:43:53 +02:00
|
|
|
GimpColorProfile *profile = NULL;
|
2015-05-06 22:15:30 +02:00
|
|
|
|
2018-04-29 17:27:47 +02:00
|
|
|
g_return_val_if_fail (GDK_IS_MONITOR (monitor), NULL);
|
|
|
|
|
2015-05-06 22:15:30 +02:00
|
|
|
#if defined GDK_WINDOWING_X11
|
|
|
|
{
|
2021-10-12 13:20:53 +02:00
|
|
|
GdkDisplay *display;
|
|
|
|
GdkScreen *screen;
|
|
|
|
GdkAtom type = GDK_NONE;
|
|
|
|
gint format = 0;
|
|
|
|
gint nitems = 0;
|
|
|
|
gint number;
|
|
|
|
gchar *atom_name;
|
|
|
|
guchar *data = NULL;
|
|
|
|
|
|
|
|
display = gdk_monitor_get_display (monitor);
|
|
|
|
number = monitor_number (monitor);
|
2018-07-27 20:56:55 +02:00
|
|
|
|
|
|
|
if (number > 0)
|
|
|
|
atom_name = g_strdup_printf ("_ICC_PROFILE_%d", number);
|
2015-05-06 22:15:30 +02:00
|
|
|
else
|
|
|
|
atom_name = g_strdup ("_ICC_PROFILE");
|
|
|
|
|
2018-04-29 17:27:47 +02:00
|
|
|
screen = gdk_display_get_default_screen (display);
|
|
|
|
|
2015-05-06 22:15:30 +02:00
|
|
|
if (gdk_property_get (gdk_screen_get_root_window (screen),
|
|
|
|
gdk_atom_intern (atom_name, FALSE),
|
|
|
|
GDK_NONE,
|
|
|
|
0, 64 * 1024 * 1024, FALSE,
|
|
|
|
&type, &format, &nitems, &data) && nitems > 0)
|
|
|
|
{
|
2015-07-10 22:43:53 +02:00
|
|
|
profile = gimp_color_profile_new_from_icc_profile (data, nitems,
|
|
|
|
NULL);
|
2015-05-06 22:15:30 +02:00
|
|
|
g_free (data);
|
|
|
|
}
|
|
|
|
|
|
|
|
g_free (atom_name);
|
|
|
|
}
|
|
|
|
#elif defined GDK_WINDOWING_QUARTZ
|
|
|
|
{
|
2016-05-06 20:45:59 +02:00
|
|
|
CGColorSpaceRef space = NULL;
|
2015-05-06 22:15:30 +02:00
|
|
|
|
2018-07-27 20:56:55 +02:00
|
|
|
space = CGDisplayCopyColorSpace (monitor_number (monitor));
|
2015-05-06 22:15:30 +02:00
|
|
|
|
2016-05-06 20:45:59 +02:00
|
|
|
if (space)
|
2015-05-06 22:15:30 +02:00
|
|
|
{
|
|
|
|
CFDataRef data;
|
|
|
|
|
2018-06-26 14:49:11 +00:00
|
|
|
data = CGColorSpaceCopyICCData (space);
|
2015-05-06 22:15:30 +02:00
|
|
|
|
|
|
|
if (data)
|
|
|
|
{
|
|
|
|
UInt8 *buffer = g_malloc (CFDataGetLength (data));
|
|
|
|
|
|
|
|
/* We cannot use CFDataGetBytesPtr(), because that returns
|
|
|
|
* a const pointer where cmsOpenProfileFromMem wants a
|
|
|
|
* non-const pointer.
|
|
|
|
*/
|
|
|
|
CFDataGetBytes (data, CFRangeMake (0, CFDataGetLength (data)),
|
|
|
|
buffer);
|
|
|
|
|
2016-05-06 20:50:07 +02:00
|
|
|
profile = gimp_color_profile_new_from_icc_profile (buffer,
|
2015-07-10 22:43:53 +02:00
|
|
|
CFDataGetLength (data),
|
|
|
|
NULL);
|
2015-05-06 22:15:30 +02:00
|
|
|
|
|
|
|
g_free (buffer);
|
|
|
|
CFRelease (data);
|
|
|
|
}
|
2016-05-06 20:45:59 +02:00
|
|
|
|
|
|
|
CFRelease (space);
|
2015-05-06 22:15:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#elif defined G_OS_WIN32
|
|
|
|
{
|
2021-12-17 21:58:03 +00:00
|
|
|
GdkRectangle monitor_geometry;
|
|
|
|
gint scale_factor;
|
|
|
|
POINT point;
|
|
|
|
gint offsetx = GetSystemMetrics (SM_XVIRTUALSCREEN);
|
|
|
|
gint offsety = GetSystemMetrics (SM_YVIRTUALSCREEN);
|
|
|
|
HMONITOR monitor_handle;
|
|
|
|
MONITORINFOEX info;
|
|
|
|
DISPLAY_DEVICE display_device;
|
|
|
|
|
|
|
|
info.cbSize = sizeof (MONITORINFOEX);
|
|
|
|
display_device.cb = sizeof (DISPLAY_DEVICE);
|
|
|
|
|
|
|
|
/* If the first monitor is not set as the main monitor,
|
|
|
|
* monitor_number(monitor) may not match the index used in
|
|
|
|
* EnumDisplayDevices(devicename, index, displaydevice, flags).
|
|
|
|
*/
|
|
|
|
gdk_monitor_get_geometry (monitor, &monitor_geometry);
|
|
|
|
scale_factor = gdk_monitor_get_scale_factor (monitor);
|
|
|
|
point.x = monitor_geometry.x * scale_factor + offsetx;
|
|
|
|
point.y = monitor_geometry.y * scale_factor + offsety;
|
|
|
|
monitor_handle = MonitorFromPoint (point, MONITOR_DEFAULTTONEAREST);
|
|
|
|
|
|
|
|
if (GetMonitorInfo (monitor_handle, (LPMONITORINFO)&info))
|
2015-05-06 22:15:30 +02:00
|
|
|
{
|
2021-12-17 21:58:03 +00:00
|
|
|
if (EnumDisplayDevices (info.szDevice, 0, &display_device, 0))
|
2015-05-06 22:15:30 +02:00
|
|
|
{
|
2021-12-17 21:58:03 +00:00
|
|
|
gchar *device_key = g_convert (display_device.DeviceKey, -1, "UTF-16LE", "WINDOWS-1252", NULL, NULL, NULL);
|
|
|
|
gchar *filename = NULL;
|
|
|
|
gchar *dir = NULL;
|
|
|
|
gchar *fullpath = NULL;
|
|
|
|
GFile *file;
|
|
|
|
DWORD len = 0;
|
|
|
|
gboolean per_user;
|
|
|
|
WCS_PROFILE_MANAGEMENT_SCOPE scope;
|
|
|
|
|
|
|
|
WcsGetUsePerUserProfiles ((LPWSTR)device_key, CLASS_MONITOR, &per_user);
|
|
|
|
scope = per_user ? WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER : WCS_PROFILE_MANAGEMENT_SCOPE_SYSTEM_WIDE;
|
|
|
|
|
|
|
|
if (WcsGetDefaultColorProfileSize (scope,
|
|
|
|
(LPWSTR)device_key,
|
|
|
|
CPT_ICC,
|
|
|
|
CPST_NONE,
|
|
|
|
0,
|
|
|
|
&len))
|
|
|
|
{
|
|
|
|
gchar *filename_utf16 = g_new (gchar, len);
|
|
|
|
|
|
|
|
WcsGetDefaultColorProfile (scope,
|
|
|
|
(LPWSTR)device_key,
|
|
|
|
CPT_ICC,
|
|
|
|
CPST_NONE,
|
|
|
|
0,
|
|
|
|
len,
|
|
|
|
(LPWSTR)filename_utf16);
|
|
|
|
|
|
|
|
/* filename_utf16 must be native endian */
|
|
|
|
filename = g_utf16_to_utf8 ((gunichar2 *)filename_utf16, -1, NULL, NULL, NULL);
|
|
|
|
g_free (filename_utf16);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Due to a bug in Windows, the meanings of LCS_sRGB and
|
|
|
|
* LCS_WINDOWS_COLOR_SPACE are swapped.
|
|
|
|
*/
|
|
|
|
GetStandardColorSpaceProfile (NULL, LCS_sRGB, NULL, &len);
|
|
|
|
filename = g_new (gchar, len);
|
|
|
|
GetStandardColorSpaceProfile (NULL, LCS_sRGB, filename, &len);
|
|
|
|
}
|
|
|
|
|
|
|
|
GetColorDirectory (NULL, NULL, &len);
|
|
|
|
dir = g_new (gchar, len);
|
|
|
|
GetColorDirectory (NULL, dir, &len);
|
|
|
|
|
|
|
|
fullpath = g_build_filename (dir, filename, NULL);
|
|
|
|
file = g_file_new_for_path (fullpath);
|
2015-05-06 22:15:30 +02:00
|
|
|
|
2015-07-10 22:43:53 +02:00
|
|
|
profile = gimp_color_profile_new_from_file (file, NULL);
|
2015-05-06 22:15:30 +02:00
|
|
|
g_object_unref (file);
|
|
|
|
|
2021-12-17 21:58:03 +00:00
|
|
|
g_free (fullpath);
|
|
|
|
g_free (dir);
|
|
|
|
g_free (filename);
|
|
|
|
g_free (device_key);
|
|
|
|
}
|
2015-05-06 22:15:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return profile;
|
|
|
|
}
|
2015-05-09 01:20:50 +02:00
|
|
|
|
2019-08-02 11:45:24 +02:00
|
|
|
/**
|
|
|
|
* gimp_widget_get_color_profile:
|
2019-08-02 23:12:50 +02:00
|
|
|
* @widget: a #GtkWidget
|
2019-08-02 11:45:24 +02:00
|
|
|
*
|
|
|
|
* This function returns the #GimpColorProfile of the monitor @widget is
|
|
|
|
* currently displayed on, or %NULL if there is no profile configured.
|
|
|
|
*
|
|
|
|
* Since: 3.0
|
|
|
|
*
|
2019-08-03 00:36:59 +02:00
|
|
|
* Returns: (nullable) (transfer full): @widget's monitor's #GimpColorProfile,
|
|
|
|
* or %NULL.
|
2019-08-02 11:45:24 +02:00
|
|
|
**/
|
2017-01-31 18:58:27 +01:00
|
|
|
GimpColorProfile *
|
|
|
|
gimp_widget_get_color_profile (GtkWidget *widget)
|
|
|
|
{
|
2018-04-29 17:27:47 +02:00
|
|
|
GdkMonitor *monitor;
|
2017-01-31 18:58:27 +01:00
|
|
|
|
|
|
|
g_return_val_if_fail (widget == NULL || GTK_IS_WIDGET (widget), NULL);
|
|
|
|
|
|
|
|
if (widget)
|
|
|
|
{
|
|
|
|
monitor = gimp_widget_get_monitor (widget);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-04-29 17:27:47 +02:00
|
|
|
monitor = gdk_display_get_monitor (gdk_display_get_default (), 0);
|
2017-01-31 18:58:27 +01:00
|
|
|
}
|
|
|
|
|
2018-04-29 17:27:47 +02:00
|
|
|
return gimp_monitor_get_color_profile (monitor);
|
2017-01-31 18:58:27 +01:00
|
|
|
}
|
|
|
|
|
2015-07-10 22:43:53 +02:00
|
|
|
static GimpColorProfile *
|
2015-05-09 01:20:50 +02:00
|
|
|
get_display_profile (GtkWidget *widget,
|
|
|
|
GimpColorConfig *config)
|
|
|
|
{
|
2015-07-10 22:43:53 +02:00
|
|
|
GimpColorProfile *profile = NULL;
|
2015-05-09 01:20:50 +02:00
|
|
|
|
2016-06-05 23:58:02 +02:00
|
|
|
if (gimp_color_config_get_display_profile_from_gdk (config))
|
2016-05-31 21:57:31 +02:00
|
|
|
/* get the toplevel's profile so all a window's colors look the same */
|
|
|
|
profile = gimp_widget_get_color_profile (gtk_widget_get_toplevel (widget));
|
2015-05-09 01:20:50 +02:00
|
|
|
|
|
|
|
if (! profile)
|
2015-06-03 09:57:34 +02:00
|
|
|
profile = gimp_color_config_get_display_color_profile (config, NULL);
|
2015-05-09 01:20:50 +02:00
|
|
|
|
2015-05-30 01:07:34 +02:00
|
|
|
if (! profile)
|
2015-12-15 20:42:21 +01:00
|
|
|
profile = gimp_color_profile_new_rgb_srgb ();
|
2015-05-30 01:07:34 +02:00
|
|
|
|
2015-05-09 01:20:50 +02:00
|
|
|
return profile;
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:55:06 +02:00
|
|
|
typedef struct _TransformCache TransformCache;
|
|
|
|
|
|
|
|
struct _TransformCache
|
|
|
|
{
|
|
|
|
GimpColorTransform *transform;
|
|
|
|
|
|
|
|
GimpColorConfig *config;
|
|
|
|
GimpColorProfile *src_profile;
|
|
|
|
const Babl *src_format;
|
|
|
|
GimpColorProfile *dest_profile;
|
|
|
|
const Babl *dest_format;
|
|
|
|
GimpColorProfile *proof_profile;
|
|
|
|
|
|
|
|
gulong notify_id;
|
|
|
|
};
|
|
|
|
|
|
|
|
static GList *transform_caches = NULL;
|
|
|
|
static gboolean debug_cache = FALSE;
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
profiles_equal (GimpColorProfile *profile1,
|
|
|
|
GimpColorProfile *profile2)
|
|
|
|
{
|
|
|
|
return ((profile1 == NULL && profile2 == NULL) ||
|
|
|
|
(profile1 != NULL && profile2 != NULL &&
|
|
|
|
gimp_color_profile_is_equal (profile1, profile2)));
|
|
|
|
}
|
|
|
|
|
|
|
|
static TransformCache *
|
|
|
|
transform_cache_get (GimpColorConfig *config,
|
|
|
|
GimpColorProfile *src_profile,
|
|
|
|
const Babl *src_format,
|
|
|
|
GimpColorProfile *dest_profile,
|
|
|
|
const Babl *dest_format,
|
|
|
|
GimpColorProfile *proof_profile)
|
|
|
|
{
|
|
|
|
GList *list;
|
|
|
|
|
|
|
|
for (list = transform_caches; list; list = g_list_next (list))
|
|
|
|
{
|
|
|
|
TransformCache *cache = list->data;
|
|
|
|
|
|
|
|
if (config == cache->config &&
|
|
|
|
src_format == cache->src_format &&
|
|
|
|
dest_format == cache->dest_format &&
|
|
|
|
profiles_equal (src_profile, cache->src_profile) &&
|
|
|
|
profiles_equal (dest_profile, cache->dest_profile) &&
|
|
|
|
profiles_equal (proof_profile, cache->proof_profile))
|
|
|
|
{
|
|
|
|
if (debug_cache)
|
|
|
|
g_printerr ("found cache %p\n", cache);
|
|
|
|
|
|
|
|
return cache;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
transform_cache_config_notify (GObject *config,
|
|
|
|
const GParamSpec *pspec,
|
|
|
|
TransformCache *cache)
|
|
|
|
{
|
|
|
|
transform_caches = g_list_remove (transform_caches, cache);
|
|
|
|
|
|
|
|
g_signal_handler_disconnect (config, cache->notify_id);
|
|
|
|
|
2016-05-30 12:48:10 +02:00
|
|
|
if (cache->transform)
|
|
|
|
g_object_unref (cache->transform);
|
|
|
|
|
2016-05-27 21:55:06 +02:00
|
|
|
g_object_unref (cache->src_profile);
|
|
|
|
g_object_unref (cache->dest_profile);
|
|
|
|
|
|
|
|
if (cache->proof_profile)
|
|
|
|
g_object_unref (cache->proof_profile);
|
|
|
|
|
|
|
|
if (debug_cache)
|
|
|
|
g_printerr ("deleted cache %p\n", cache);
|
2021-10-01 12:25:34 +02:00
|
|
|
|
|
|
|
g_free (cache);
|
2016-05-27 21:55:06 +02:00
|
|
|
}
|
|
|
|
|
2019-08-09 12:32:09 +02:00
|
|
|
/**
|
|
|
|
* gimp_widget_get_color_transform:
|
|
|
|
* @widget: a #GtkWidget
|
|
|
|
* @config: a #GimpColorConfig
|
|
|
|
* @src_profile: a #GimpColorProfile
|
|
|
|
* @src_format: Babl format for the transform's source pixels
|
|
|
|
* @dest_format: Babl format for the transforms's destination pixels
|
|
|
|
*
|
|
|
|
* This function returns the #GimpColorTransform that transforms pixels
|
|
|
|
* from @src_profile to the profile of the #GdkMonitor the @widget is
|
|
|
|
* displayed on.
|
|
|
|
*
|
|
|
|
* Returns: (nullable) (transfer full): the #GimpColorTransform.
|
|
|
|
*
|
|
|
|
* Since: 2.10
|
|
|
|
**/
|
2016-05-25 21:35:54 +02:00
|
|
|
GimpColorTransform *
|
|
|
|
gimp_widget_get_color_transform (GtkWidget *widget,
|
|
|
|
GimpColorConfig *config,
|
|
|
|
GimpColorProfile *src_profile,
|
|
|
|
const Babl *src_format,
|
2022-05-31 20:59:31 +00:00
|
|
|
const Babl *dest_format,
|
|
|
|
GimpColorProfile *softproof_profile)
|
2015-05-09 01:20:50 +02:00
|
|
|
{
|
2016-05-27 21:55:06 +02:00
|
|
|
static gboolean initialized = FALSE;
|
2015-07-10 22:43:53 +02:00
|
|
|
GimpColorProfile *proof_profile = NULL;
|
2022-05-31 20:59:31 +00:00
|
|
|
GimpColorProfile *dest_profile = NULL;
|
2016-05-27 21:55:06 +02:00
|
|
|
TransformCache *cache;
|
2015-05-09 01:20:50 +02:00
|
|
|
|
|
|
|
g_return_val_if_fail (widget == NULL || GTK_IS_WIDGET (widget), NULL);
|
|
|
|
g_return_val_if_fail (GIMP_IS_COLOR_CONFIG (config), NULL);
|
2016-05-27 21:55:06 +02:00
|
|
|
g_return_val_if_fail (GIMP_IS_COLOR_PROFILE (src_profile), NULL);
|
2015-05-09 01:20:50 +02:00
|
|
|
g_return_val_if_fail (src_format != NULL, NULL);
|
|
|
|
g_return_val_if_fail (dest_format != NULL, NULL);
|
|
|
|
|
2016-05-27 21:55:06 +02:00
|
|
|
if (G_UNLIKELY (! initialized))
|
|
|
|
{
|
|
|
|
initialized = TRUE;
|
|
|
|
|
|
|
|
debug_cache = g_getenv ("GIMP_DEBUG_TRANSFORM_CACHE") != NULL;
|
|
|
|
}
|
|
|
|
|
2016-06-05 23:58:02 +02:00
|
|
|
switch (gimp_color_config_get_mode (config))
|
2015-05-09 01:20:50 +02:00
|
|
|
{
|
|
|
|
case GIMP_COLOR_MANAGEMENT_OFF:
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
case GIMP_COLOR_MANAGEMENT_SOFTPROOF:
|
2022-05-31 20:59:31 +00:00
|
|
|
if (! softproof_profile)
|
|
|
|
proof_profile = gimp_color_config_get_simulation_color_profile (config,
|
|
|
|
NULL);
|
|
|
|
else
|
|
|
|
proof_profile = softproof_profile;
|
2015-05-09 01:20:50 +02:00
|
|
|
/* fallthru */
|
|
|
|
|
|
|
|
case GIMP_COLOR_MANAGEMENT_DISPLAY:
|
|
|
|
dest_profile = get_display_profile (widget, config);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:55:06 +02:00
|
|
|
cache = transform_cache_get (config,
|
|
|
|
src_profile,
|
|
|
|
src_format,
|
|
|
|
dest_profile,
|
|
|
|
dest_format,
|
|
|
|
proof_profile);
|
|
|
|
|
|
|
|
if (cache)
|
|
|
|
{
|
|
|
|
g_object_unref (dest_profile);
|
|
|
|
|
|
|
|
if (proof_profile)
|
|
|
|
g_object_unref (proof_profile);
|
|
|
|
|
|
|
|
if (cache->transform)
|
|
|
|
return g_object_ref (cache->transform);
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (! proof_profile &&
|
|
|
|
gimp_color_profile_is_equal (src_profile, dest_profile))
|
|
|
|
{
|
|
|
|
g_object_unref (dest_profile);
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
cache = g_new0 (TransformCache, 1);
|
|
|
|
|
|
|
|
if (debug_cache)
|
|
|
|
g_printerr ("creating cache %p\n", cache);
|
|
|
|
|
|
|
|
cache->config = g_object_ref (config);
|
|
|
|
cache->src_profile = g_object_ref (src_profile);
|
|
|
|
cache->src_format = src_format;
|
|
|
|
cache->dest_profile = dest_profile;
|
|
|
|
cache->dest_format = dest_format;
|
2022-05-31 20:59:31 +00:00
|
|
|
cache->proof_profile = g_object_ref (proof_profile);
|
2016-05-27 21:55:06 +02:00
|
|
|
|
|
|
|
cache->notify_id =
|
|
|
|
g_signal_connect (cache->config, "notify",
|
|
|
|
G_CALLBACK (transform_cache_config_notify),
|
|
|
|
cache);
|
|
|
|
|
|
|
|
transform_caches = g_list_prepend (transform_caches, cache);
|
|
|
|
|
|
|
|
if (cache->proof_profile)
|
2015-05-09 01:20:50 +02:00
|
|
|
{
|
2016-05-26 22:18:50 +02:00
|
|
|
GimpColorTransformFlags flags = 0;
|
2015-05-09 01:20:50 +02:00
|
|
|
|
2016-06-05 23:58:02 +02:00
|
|
|
if (gimp_color_config_get_simulation_bpc (config))
|
2016-06-05 16:50:02 +02:00
|
|
|
flags |= GIMP_COLOR_TRANSFORM_FLAGS_BLACK_POINT_COMPENSATION;
|
|
|
|
|
2016-06-06 01:15:26 +02:00
|
|
|
if (! gimp_color_config_get_simulation_optimize (config))
|
2016-06-05 16:50:02 +02:00
|
|
|
flags |= GIMP_COLOR_TRANSFORM_FLAGS_NOOPTIMIZE;
|
2015-05-09 01:20:50 +02:00
|
|
|
|
2016-06-05 23:58:02 +02:00
|
|
|
if (gimp_color_config_get_simulation_gamut_check (config))
|
2015-05-09 01:20:50 +02:00
|
|
|
{
|
2018-05-05 20:07:27 +02:00
|
|
|
GimpRGB color;
|
2016-05-27 21:55:06 +02:00
|
|
|
cmsUInt16Number alarmCodes[cmsMAXCHANNELS] = { 0, };
|
|
|
|
guchar r, g, b;
|
2015-05-09 01:20:50 +02:00
|
|
|
|
2016-05-26 22:18:50 +02:00
|
|
|
flags |= GIMP_COLOR_TRANSFORM_FLAGS_GAMUT_CHECK;
|
2015-05-09 01:20:50 +02:00
|
|
|
|
2018-05-05 20:07:27 +02:00
|
|
|
gimp_color_config_get_out_of_gamut_color (config, &color);
|
|
|
|
gimp_rgb_get_uchar (&color, &r, &g, &b);
|
2015-05-09 01:20:50 +02:00
|
|
|
|
|
|
|
alarmCodes[0] = (cmsUInt16Number) r * 256;
|
|
|
|
alarmCodes[1] = (cmsUInt16Number) g * 256;
|
|
|
|
alarmCodes[2] = (cmsUInt16Number) b * 256;
|
|
|
|
|
|
|
|
cmsSetAlarmCodes (alarmCodes);
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:55:06 +02:00
|
|
|
cache->transform =
|
|
|
|
gimp_color_transform_new_proofing (cache->src_profile,
|
|
|
|
cache->src_format,
|
|
|
|
cache->dest_profile,
|
|
|
|
cache->dest_format,
|
|
|
|
cache->proof_profile,
|
2016-06-05 23:58:02 +02:00
|
|
|
gimp_color_config_get_simulation_intent (config),
|
|
|
|
gimp_color_config_get_display_intent (config),
|
2016-05-27 21:55:06 +02:00
|
|
|
flags);
|
2015-05-09 01:20:50 +02:00
|
|
|
}
|
2016-05-27 21:55:06 +02:00
|
|
|
else
|
2015-05-09 01:20:50 +02:00
|
|
|
{
|
2016-05-26 22:18:50 +02:00
|
|
|
GimpColorTransformFlags flags = 0;
|
2015-05-09 01:20:50 +02:00
|
|
|
|
2016-06-05 23:58:02 +02:00
|
|
|
if (gimp_color_config_get_display_bpc (config))
|
2016-06-05 16:50:02 +02:00
|
|
|
flags |= GIMP_COLOR_TRANSFORM_FLAGS_BLACK_POINT_COMPENSATION;
|
|
|
|
|
2016-06-06 01:15:26 +02:00
|
|
|
if (! gimp_color_config_get_display_optimize (config))
|
2016-06-05 16:50:02 +02:00
|
|
|
flags |= GIMP_COLOR_TRANSFORM_FLAGS_NOOPTIMIZE;
|
2015-05-09 01:20:50 +02:00
|
|
|
|
2016-05-27 21:55:06 +02:00
|
|
|
cache->transform =
|
|
|
|
gimp_color_transform_new (cache->src_profile,
|
|
|
|
cache->src_format,
|
|
|
|
cache->dest_profile,
|
|
|
|
cache->dest_format,
|
2016-06-05 23:58:02 +02:00
|
|
|
gimp_color_config_get_display_intent (config),
|
2016-05-27 21:55:06 +02:00
|
|
|
flags);
|
2015-05-09 01:20:50 +02:00
|
|
|
}
|
|
|
|
|
2016-05-27 21:55:06 +02:00
|
|
|
if (cache->transform)
|
|
|
|
return g_object_ref (cache->transform);
|
2015-05-09 01:20:50 +02:00
|
|
|
|
2016-05-27 21:55:06 +02:00
|
|
|
return NULL;
|
2015-05-09 01:20:50 +02:00
|
|
|
}
|