Gimp/app/widgets/gimpview.c
Jehan 7262faed16 app, pdb: allow rendering font previews with theme colors.
This also depends on the same settings to render brushes with theme
colors, because this is data, not user interface. Therefore following
theme colors is not necessarily what creators would expect.
2025-06-20 00:11:08 +02:00

854 lines
26 KiB
C

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* gimpview.c
* Copyright (C) 2001-2006 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 <string.h>
#include <gegl.h>
#include <gtk/gtk.h>
#include "libgimpcolor/gimpcolor.h"
#include "libgimpmath/gimpmath.h"
#include "libgimpbase/gimpbase.h"
#include "libgimpwidgets/gimpwidgets.h"
#include "widgets-types.h"
#include "core/gimpcontext.h"
#include "core/gimpmarshal.h"
#include "core/gimpviewable.h"
#include "gimpdnd.h"
#include "gimpview.h"
#include "gimpview-popup.h"
#include "gimpviewrenderer.h"
#include "gimpviewrenderer-utils.h"
enum
{
SET_VIEWABLE,
CLICKED,
DOUBLE_CLICKED,
CONTEXT,
LAST_SIGNAL
};
static void gimp_view_dispose (GObject *object);
static void gimp_view_realize (GtkWidget *widget);
static void gimp_view_unrealize (GtkWidget *widget);
static void gimp_view_map (GtkWidget *widget);
static void gimp_view_unmap (GtkWidget *widget);
static void gimp_view_get_preferred_width (GtkWidget *widget,
gint *minimum_width,
gint *natural_width);
static void gimp_view_get_preferred_height (GtkWidget *widget,
gint *minimum_height,
gint *natural_height);
static void gimp_view_size_allocate (GtkWidget *widget,
GtkAllocation *allocation);
static void gimp_view_style_updated (GtkWidget *widget);
static gboolean gimp_view_draw (GtkWidget *widget,
cairo_t *cr);
static gboolean gimp_view_button_press_event (GtkWidget *widget,
GdkEventButton *bevent);
static gboolean gimp_view_button_release_event (GtkWidget *widget,
GdkEventButton *bevent);
static gboolean gimp_view_enter_notify_event (GtkWidget *widget,
GdkEventCrossing *event);
static gboolean gimp_view_leave_notify_event (GtkWidget *widget,
GdkEventCrossing *event);
static void gimp_view_real_set_viewable (GimpView *view,
GimpViewable *old,
GimpViewable *viewable);
static void gimp_view_update_callback (GimpViewRenderer *renderer,
GimpView *view);
static void gimp_view_monitor_changed (GimpView *view);
static GimpViewable * gimp_view_drag_viewable (GtkWidget *widget,
GimpContext **context,
gpointer data);
static GdkPixbuf * gimp_view_drag_pixbuf (GtkWidget *widget,
gpointer data);
G_DEFINE_TYPE (GimpView, gimp_view, GTK_TYPE_WIDGET)
#define parent_class gimp_view_parent_class
static guint view_signals[LAST_SIGNAL] = { 0 };
static void
gimp_view_class_init (GimpViewClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
view_signals[SET_VIEWABLE] =
g_signal_new ("set-viewable",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GimpViewClass, set_viewable),
NULL, NULL,
gimp_marshal_VOID__OBJECT_OBJECT,
G_TYPE_NONE, 2,
GIMP_TYPE_VIEWABLE, GIMP_TYPE_VIEWABLE);
view_signals[CLICKED] =
g_signal_new ("clicked",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GimpViewClass, clicked),
NULL, NULL, NULL,
G_TYPE_NONE, 1,
GDK_TYPE_MODIFIER_TYPE);
view_signals[DOUBLE_CLICKED] =
g_signal_new ("double-clicked",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GimpViewClass, double_clicked),
NULL, NULL, NULL,
G_TYPE_NONE, 0);
view_signals[CONTEXT] =
g_signal_new ("context",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GimpViewClass, context),
NULL, NULL, NULL,
G_TYPE_NONE, 0);
object_class->dispose = gimp_view_dispose;
widget_class->activate_signal = view_signals[CLICKED];
widget_class->realize = gimp_view_realize;
widget_class->unrealize = gimp_view_unrealize;
widget_class->map = gimp_view_map;
widget_class->unmap = gimp_view_unmap;
widget_class->get_preferred_width = gimp_view_get_preferred_width;
widget_class->get_preferred_height = gimp_view_get_preferred_height;
widget_class->size_allocate = gimp_view_size_allocate;
widget_class->style_updated = gimp_view_style_updated;
widget_class->draw = gimp_view_draw;
widget_class->button_press_event = gimp_view_button_press_event;
widget_class->button_release_event = gimp_view_button_release_event;
widget_class->enter_notify_event = gimp_view_enter_notify_event;
widget_class->leave_notify_event = gimp_view_leave_notify_event;
klass->set_viewable = gimp_view_real_set_viewable;
klass->clicked = NULL;
klass->double_clicked = NULL;
klass->context = NULL;
gtk_widget_class_set_css_name (widget_class, "GimpView");
}
static void
gimp_view_init (GimpView *view)
{
gtk_widget_set_has_window (GTK_WIDGET (view), FALSE);
gtk_widget_add_events (GTK_WIDGET (view),
GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK |
GDK_ENTER_NOTIFY_MASK |
GDK_LEAVE_NOTIFY_MASK);
view->clickable = FALSE;
view->eat_button_events = TRUE;
view->show_popup = FALSE;
view->expand = FALSE;
view->in_button = FALSE;
view->has_grab = FALSE;
view->press_state = 0;
gimp_widget_track_monitor (GTK_WIDGET (view),
G_CALLBACK (gimp_view_monitor_changed),
NULL, NULL);
}
static void
gimp_view_dispose (GObject *object)
{
GimpView *view = GIMP_VIEW (object);
if (view->viewable)
gimp_view_set_viewable (view, NULL);
g_clear_object (&view->renderer);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
gimp_view_realize (GtkWidget *widget)
{
GimpView *view = GIMP_VIEW (widget);
GtkAllocation allocation;
GdkWindowAttr attributes;
gint attributes_mask;
GTK_WIDGET_CLASS (parent_class)->realize (widget);
gtk_widget_get_allocation (widget, &allocation);
attributes.window_type = GDK_WINDOW_CHILD;
attributes.x = allocation.x;
attributes.y = allocation.y;
attributes.width = allocation.width;
attributes.height = allocation.height;
attributes.wclass = GDK_INPUT_ONLY;
attributes.event_mask = gtk_widget_get_events (widget);
attributes_mask = GDK_WA_X | GDK_WA_Y;
view->event_window = gdk_window_new (gtk_widget_get_window (widget),
&attributes, attributes_mask);
gdk_window_set_user_data (view->event_window, view);
}
static void
gimp_view_unrealize (GtkWidget *widget)
{
GimpView *view = GIMP_VIEW (widget);
if (view->event_window)
{
gdk_window_set_user_data (view->event_window, NULL);
gdk_window_destroy (view->event_window);
view->event_window = NULL;
}
GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
}
static void
gimp_view_map (GtkWidget *widget)
{
GimpView *view = GIMP_VIEW (widget);
GTK_WIDGET_CLASS (parent_class)->map (widget);
if (view->event_window)
gdk_window_show (view->event_window);
}
static void
gimp_view_unmap (GtkWidget *widget)
{
GimpView *view = GIMP_VIEW (widget);
if (view->has_grab)
{
gtk_grab_remove (widget);
view->has_grab = FALSE;
}
if (view->event_window)
gdk_window_hide (view->event_window);
GTK_WIDGET_CLASS (parent_class)->unmap (widget);
}
static void
gimp_view_get_preferred_width (GtkWidget *widget,
gint *minimum_width,
gint *natural_width)
{
GimpView *view = GIMP_VIEW (widget);
if (view->expand)
{
*minimum_width = *natural_width = 2 * view->renderer->border_width + 1;
}
else
{
*minimum_width = *natural_width = (view->renderer->width +
2 * view->renderer->border_width);
}
}
static void
gimp_view_get_preferred_height (GtkWidget *widget,
gint *minimum_height,
gint *natural_height)
{
GimpView *view = GIMP_VIEW (widget);
if (view->expand)
{
*minimum_height = *natural_height = 2 * view->renderer->border_width + 1;
}
else
{
*minimum_height = *natural_height = (view->renderer->height +
2 * view->renderer->border_width);
}
}
static void
gimp_view_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
GimpView *view = GIMP_VIEW (widget);
gint width;
gint height;
if (view->expand)
{
width = MIN (GIMP_VIEWABLE_MAX_PREVIEW_SIZE,
allocation->width - 2 * view->renderer->border_width);
height = MIN (GIMP_VIEWABLE_MAX_PREVIEW_SIZE,
allocation->height - 2 * view->renderer->border_width);
if (view->renderer->width != width ||
view->renderer->height != height)
{
gint border_width = view->renderer->border_width;
if (view->renderer->size != -1 && view->renderer->viewable)
{
gint view_width;
gint view_height;
gint scaled_width;
gint scaled_height;
gimp_viewable_get_preview_size (view->renderer->viewable,
GIMP_VIEWABLE_MAX_PREVIEW_SIZE,
view->renderer->is_popup,
view->renderer->dot_for_dot,
&view_width,
&view_height);
gimp_viewable_calc_preview_size (view_width, view_height,
width, height,
TRUE, 1.0, 1.0,
&scaled_width, &scaled_height,
NULL);
if (scaled_width > width)
{
scaled_height = scaled_height * width / scaled_width;
scaled_width = scaled_width * width / scaled_width;
}
else if (scaled_height > height)
{
scaled_width = scaled_width * height / scaled_height;
scaled_height = scaled_height * height / scaled_height;
}
gimp_view_renderer_set_size (view->renderer,
MAX (scaled_width, scaled_height),
border_width);
}
else
{
gimp_view_renderer_set_size_full (view->renderer,
width, height,
border_width);
}
gimp_view_renderer_remove_idle (view->renderer);
}
}
width = (view->renderer->width +
2 * view->renderer->border_width);
height = (view->renderer->height +
2 * view->renderer->border_width);
if (allocation->width > width)
allocation->x += (allocation->width - width) / 2;
if (allocation->height > height)
allocation->y += (allocation->height - height) / 2;
allocation->width = width;
allocation->height = height;
GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
if (gtk_widget_get_realized (widget))
gdk_window_move_resize (view->event_window,
allocation->x,
allocation->y,
allocation->width,
allocation->height);
}
static void
gimp_view_style_updated (GtkWidget *widget)
{
GimpView *view = GIMP_VIEW (widget);
GTK_WIDGET_CLASS (parent_class)->style_updated (widget);
gimp_view_renderer_invalidate (view->renderer);
}
static gboolean
gimp_view_draw (GtkWidget *widget,
cairo_t *cr)
{
GtkAllocation allocation;
gtk_widget_get_allocation (widget, &allocation);
gimp_view_renderer_draw (GIMP_VIEW (widget)->renderer,
widget, cr,
allocation.width,
allocation.height);
return FALSE;
}
#define DEBUG_MEMSIZE 1
#ifdef DEBUG_MEMSIZE
extern gboolean gimp_debug_memsize;
#endif
static gboolean
gimp_view_button_press_event (GtkWidget *widget,
GdkEventButton *bevent)
{
GimpView *view = GIMP_VIEW (widget);
#ifdef DEBUG_MEMSIZE
if (bevent->type == GDK_BUTTON_PRESS && bevent->button == 2)
{
gimp_debug_memsize = TRUE;
gimp_object_get_memsize (GIMP_OBJECT (view->viewable), NULL);
gimp_debug_memsize = FALSE;
}
#endif /* DEBUG_MEMSIZE */
if (! view->clickable &&
! view->show_popup)
return FALSE;
if (! gtk_widget_get_realized (widget))
return FALSE;
if (bevent->type == GDK_BUTTON_PRESS)
{
if (gdk_event_triggers_context_menu ((GdkEvent *) bevent))
{
view->press_state = 0;
g_signal_emit (widget, view_signals[CONTEXT], 0);
}
else if (bevent->button == 1)
{
gtk_grab_add (widget);
view->has_grab = TRUE;
view->press_state = bevent->state;
if (view->show_popup && view->viewable)
{
gimp_view_popup_show (widget, bevent,
view->renderer->context,
view->viewable,
view->renderer->width,
view->renderer->height,
view->renderer->dot_for_dot);
}
}
else
{
view->press_state = 0;
if (bevent->button == 2)
gimp_view_popup_show (widget, bevent,
view->renderer->context,
view->viewable,
view->renderer->width,
view->renderer->height,
view->renderer->dot_for_dot);
return FALSE;
}
}
else if (bevent->type == GDK_2BUTTON_PRESS)
{
if (bevent->button == 1)
g_signal_emit (widget, view_signals[DOUBLE_CLICKED], 0);
}
return view->eat_button_events ? TRUE : FALSE;
}
static gboolean
gimp_view_button_release_event (GtkWidget *widget,
GdkEventButton *bevent)
{
GimpView *view = GIMP_VIEW (widget);
if (! view->clickable &&
! view->show_popup)
return FALSE;
if (bevent->button == 1)
{
gtk_grab_remove (widget);
view->has_grab = FALSE;
if (view->clickable && view->in_button)
{
g_signal_emit (widget, view_signals[CLICKED], 0, view->press_state);
}
}
else
{
return FALSE;
}
return view->eat_button_events ? TRUE : FALSE;
}
static gboolean
gimp_view_enter_notify_event (GtkWidget *widget,
GdkEventCrossing *event)
{
GimpView *view = GIMP_VIEW (widget);
GtkWidget *event_widget = gtk_get_event_widget ((GdkEvent *) event);
if ((event_widget == widget) &&
(event->detail != GDK_NOTIFY_INFERIOR))
{
view->in_button = TRUE;
}
return FALSE;
}
static gboolean
gimp_view_leave_notify_event (GtkWidget *widget,
GdkEventCrossing *event)
{
GimpView *view = GIMP_VIEW (widget);
GtkWidget *event_widget = gtk_get_event_widget ((GdkEvent *) event);
if ((event_widget == widget) &&
(event->detail != GDK_NOTIFY_INFERIOR))
{
view->in_button = FALSE;
}
return FALSE;
}
static void
gimp_view_real_set_viewable (GimpView *view,
GimpViewable *old,
GimpViewable *viewable)
{
GType viewable_type = G_TYPE_NONE;
if (viewable == view->viewable)
return;
if (viewable)
{
viewable_type = G_TYPE_FROM_INSTANCE (viewable);
g_return_if_fail (g_type_is_a (viewable_type,
view->renderer->viewable_type));
}
if (view->viewable)
{
if (! viewable && ! view->renderer->is_popup)
{
if (gimp_dnd_viewable_source_remove (GTK_WIDGET (view),
G_TYPE_FROM_INSTANCE (view->viewable)))
{
if (gimp_viewable_get_size (view->viewable, NULL, NULL))
gimp_dnd_pixbuf_source_remove (GTK_WIDGET (view));
gtk_drag_source_unset (GTK_WIDGET (view));
}
}
}
else if (viewable && ! view->renderer->is_popup)
{
if (gimp_dnd_drag_source_set_by_type (GTK_WIDGET (view),
GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
viewable_type,
GDK_ACTION_COPY))
{
gimp_dnd_viewable_source_add (GTK_WIDGET (view),
viewable_type,
gimp_view_drag_viewable,
NULL);
if (gimp_viewable_get_size (viewable, NULL, NULL))
gimp_dnd_pixbuf_source_add (GTK_WIDGET (view),
gimp_view_drag_pixbuf,
NULL);
}
}
gimp_view_renderer_set_viewable (view->renderer, viewable);
g_set_weak_pointer (&view->viewable, viewable);
}
/* public functions */
GtkWidget *
gimp_view_new (GimpContext *context,
GimpViewable *viewable,
gint size,
gint border_width,
gboolean is_popup)
{
GtkWidget *view;
g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL);
g_return_val_if_fail (GIMP_IS_VIEWABLE (viewable), NULL);
view = gimp_view_new_by_types (context,
GIMP_TYPE_VIEW,
G_TYPE_FROM_INSTANCE (viewable),
size, border_width, is_popup);
if (view)
gimp_view_set_viewable (GIMP_VIEW (view), viewable);
gimp_view_renderer_remove_idle (GIMP_VIEW (view)->renderer);
return view;
}
GtkWidget *
gimp_view_new_full (GimpContext *context,
GimpViewable *viewable,
gint width,
gint height,
gint border_width,
gboolean is_popup,
gboolean clickable,
gboolean show_popup)
{
GtkWidget *view;
g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL);
g_return_val_if_fail (GIMP_IS_VIEWABLE (viewable), NULL);
view = gimp_view_new_full_by_types (context,
GIMP_TYPE_VIEW,
G_TYPE_FROM_INSTANCE (viewable),
width, height, border_width,
is_popup, clickable, show_popup);
if (view)
gimp_view_set_viewable (GIMP_VIEW (view), viewable);
gimp_view_renderer_remove_idle (GIMP_VIEW (view)->renderer);
return view;
}
GtkWidget *
gimp_view_new_by_types (GimpContext *context,
GType view_type,
GType viewable_type,
gint size,
gint border_width,
gboolean is_popup)
{
GimpViewRenderer *renderer;
GimpView *view;
g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL);
g_return_val_if_fail (g_type_is_a (view_type, GIMP_TYPE_VIEW), NULL);
g_return_val_if_fail (g_type_is_a (viewable_type, GIMP_TYPE_VIEWABLE), NULL);
g_return_val_if_fail (size > 0 &&
size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL);
g_return_val_if_fail (border_width >= 0 &&
border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, NULL);
renderer = gimp_view_renderer_new (context, viewable_type, size,
border_width, is_popup);
g_return_val_if_fail (renderer != NULL, NULL);
view = g_object_new (view_type, NULL);
g_signal_connect (renderer, "update",
G_CALLBACK (gimp_view_update_callback),
view);
view->renderer = renderer;
return GTK_WIDGET (view);
}
GtkWidget *
gimp_view_new_full_by_types (GimpContext *context,
GType view_type,
GType viewable_type,
gint width,
gint height,
gint border_width,
gboolean is_popup,
gboolean clickable,
gboolean show_popup)
{
GimpViewRenderer *renderer;
GimpView *view;
g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL);
g_return_val_if_fail (g_type_is_a (view_type, GIMP_TYPE_VIEW), NULL);
g_return_val_if_fail (g_type_is_a (viewable_type, GIMP_TYPE_VIEWABLE), NULL);
g_return_val_if_fail (width > 0 &&
width <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL);
g_return_val_if_fail (height > 0 &&
height <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL);
g_return_val_if_fail (border_width >= 0 &&
border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, NULL);
renderer = gimp_view_renderer_new_full (context, viewable_type,
width, height, border_width,
is_popup);
g_return_val_if_fail (renderer != NULL, NULL);
view = g_object_new (view_type, NULL);
g_signal_connect (renderer, "update",
G_CALLBACK (gimp_view_update_callback),
view);
view->renderer = renderer;
view->clickable = clickable ? TRUE : FALSE;
view->show_popup = show_popup ? TRUE : FALSE;
return GTK_WIDGET (view);
}
GimpViewable *
gimp_view_get_viewable (GimpView *view)
{
g_return_val_if_fail (GIMP_IS_VIEW (view), NULL);
return view->viewable;
}
void
gimp_view_set_viewable (GimpView *view,
GimpViewable *viewable)
{
g_return_if_fail (GIMP_IS_VIEW (view));
g_return_if_fail (viewable == NULL || GIMP_IS_VIEWABLE (viewable));
if (viewable == view->viewable)
return;
g_signal_emit (view, view_signals[SET_VIEWABLE], 0, view->viewable, viewable);
}
void
gimp_view_set_expand (GimpView *view,
gboolean expand)
{
g_return_if_fail (GIMP_IS_VIEW (view));
if (view->expand != expand)
{
view->expand = expand ? TRUE : FALSE;
gtk_widget_queue_resize (GTK_WIDGET (view));
}
}
/* private functions */
static void
gimp_view_update_callback (GimpViewRenderer *renderer,
GimpView *view)
{
GtkWidget *widget = GTK_WIDGET (view);
GtkRequisition requisition;
gint width;
gint height;
gtk_widget_get_preferred_size (widget, &requisition, NULL);
width = renderer->width + 2 * renderer->border_width;
height = renderer->height + 2 * renderer->border_width;
if (width != requisition.width ||
height != requisition.height)
{
gtk_widget_queue_resize (widget);
}
else
{
gtk_widget_queue_draw (widget);
}
}
static void
gimp_view_monitor_changed (GimpView *view)
{
if (view->renderer)
gimp_view_renderer_free_color_transform (view->renderer);
}
static GimpViewable *
gimp_view_drag_viewable (GtkWidget *widget,
GimpContext **context,
gpointer data)
{
if (context)
*context = GIMP_VIEW (widget)->renderer->context;
return GIMP_VIEW (widget)->viewable;
}
static GdkPixbuf *
gimp_view_drag_pixbuf (GtkWidget *widget,
gpointer data)
{
GimpView *view = GIMP_VIEW (widget);
GimpViewable *viewable = view->viewable;
gint width;
gint height;
if (viewable && gimp_viewable_get_size (viewable, &width, &height))
return gimp_viewable_get_new_pixbuf (viewable, view->renderer->context,
width, height, NULL, NULL);
return NULL;
}