gimp/app/widgets/gimpnavigationview.c
Ell a33f549896 app: show full image contents in GimpNavigationEditor
In GimpNavigation{Editor,View}, show the full image contents when
the corresponding display is in "show all" mode.  Additionally,
when the display's "show canvas boundary" is active, show the
canvas boundary in the navigation view as well.
2019-09-04 20:51:43 +03:00

677 lines
20 KiB
C

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* GimpNavigationView Widget
* Copyright (C) 2001-2002 Michael Natterer <mitch@gimp.org>
*
* partly based on app/nav_window
* Copyright (C) 1999 Andy Thomas <alt@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 <math.h>
#include <gegl.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include "libgimpmath/gimpmath.h"
#include "widgets-types.h"
#include "core/gimpimage.h"
#include "core/gimpmarshal.h"
#include "display/gimpcanvas-style.h"
#include "gimpnavigationview.h"
#include "gimpviewrenderer.h"
#include "gimpwidgets-utils.h"
#define BORDER_WIDTH 2
enum
{
MARKER_CHANGED,
ZOOM,
SCROLL,
LAST_SIGNAL
};
struct _GimpNavigationView
{
GimpView parent_instance;
/* values in image coordinates */
gdouble center_x;
gdouble center_y;
gdouble width;
gdouble height;
gboolean flip_horizontally;
gboolean flip_vertically;
gdouble rotate_angle;
gboolean canvas_visible;
gdouble canvas_x;
gdouble canvas_y;
gdouble canvas_width;
gdouble canvas_height;
/* values in view coordinates */
gint p_center_x;
gint p_center_y;
gint p_width;
gint p_height;
gint p_canvas_x;
gint p_canvas_y;
gint p_canvas_width;
gint p_canvas_height;
gint motion_offset_x;
gint motion_offset_y;
gboolean has_grab;
};
static void gimp_navigation_view_size_allocate (GtkWidget *widget,
GtkAllocation *allocation);
static gboolean gimp_navigation_view_draw (GtkWidget *widget,
cairo_t *cr);
static gboolean gimp_navigation_view_button_press (GtkWidget *widget,
GdkEventButton *bevent);
static gboolean gimp_navigation_view_button_release (GtkWidget *widget,
GdkEventButton *bevent);
static gboolean gimp_navigation_view_scroll (GtkWidget *widget,
GdkEventScroll *sevent);
static gboolean gimp_navigation_view_motion_notify (GtkWidget *widget,
GdkEventMotion *mevent);
static gboolean gimp_navigation_view_key_press (GtkWidget *widget,
GdkEventKey *kevent);
static void gimp_navigation_view_transform (GimpNavigationView *nav_view);
static void gimp_navigation_view_draw_marker (GimpNavigationView *nav_view,
cairo_t *cr);
static void gimp_navigation_view_move_to (GimpNavigationView *nav_view,
gint tx,
gint ty);
static void gimp_navigation_view_get_ratio (GimpNavigationView *nav_view,
gdouble *ratiox,
gdouble *ratioy);
static gboolean gimp_navigation_view_point_in_marker (GimpNavigationView *nav_view,
gint x,
gint y);
G_DEFINE_TYPE (GimpNavigationView, gimp_navigation_view, GIMP_TYPE_VIEW)
#define parent_class gimp_navigation_view_parent_class
static guint view_signals[LAST_SIGNAL] = { 0 };
static void
gimp_navigation_view_class_init (GimpNavigationViewClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
view_signals[MARKER_CHANGED] =
g_signal_new ("marker-changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GimpNavigationViewClass, marker_changed),
NULL, NULL,
gimp_marshal_VOID__DOUBLE_DOUBLE_DOUBLE_DOUBLE,
G_TYPE_NONE, 4,
G_TYPE_DOUBLE,
G_TYPE_DOUBLE,
G_TYPE_DOUBLE,
G_TYPE_DOUBLE);
view_signals[ZOOM] =
g_signal_new ("zoom",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GimpNavigationViewClass, zoom),
NULL, NULL,
gimp_marshal_VOID__ENUM_DOUBLE,
G_TYPE_NONE, 2,
GIMP_TYPE_ZOOM_TYPE,
G_TYPE_DOUBLE);
view_signals[SCROLL] =
g_signal_new ("scroll",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GimpNavigationViewClass, scroll),
NULL, NULL,
gimp_marshal_VOID__BOXED,
G_TYPE_NONE, 1,
GDK_TYPE_EVENT);
widget_class->size_allocate = gimp_navigation_view_size_allocate;
widget_class->draw = gimp_navigation_view_draw;
widget_class->button_press_event = gimp_navigation_view_button_press;
widget_class->button_release_event = gimp_navigation_view_button_release;
widget_class->scroll_event = gimp_navigation_view_scroll;
widget_class->motion_notify_event = gimp_navigation_view_motion_notify;
widget_class->key_press_event = gimp_navigation_view_key_press;
}
static void
gimp_navigation_view_init (GimpNavigationView *view)
{
gtk_widget_set_can_focus (GTK_WIDGET (view), TRUE);
gtk_widget_add_events (GTK_WIDGET (view),
GDK_SCROLL_MASK |
GDK_SMOOTH_SCROLL_MASK |
GDK_POINTER_MOTION_MASK |
GDK_KEY_PRESS_MASK);
view->center_x = 0.0;
view->center_y = 0.0;
view->width = 0.0;
view->height = 0.0;
view->flip_horizontally = FALSE;
view->flip_vertically = FALSE;
view->rotate_angle = 0.0;
view->canvas_visible = FALSE;
view->canvas_x = 0.0;
view->canvas_y = 0.0;
view->canvas_width = 0.0;
view->canvas_height = 0.0;
view->p_center_x = 0;
view->p_center_y = 0;
view->p_width = 0;
view->p_height = 0;
view->p_canvas_x = 0;
view->p_canvas_y = 0;
view->p_canvas_width = 0;
view->p_canvas_height = 0;
view->motion_offset_x = 0;
view->motion_offset_y = 0;
view->has_grab = FALSE;
}
static void
gimp_navigation_view_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
if (GIMP_VIEW (widget)->renderer->viewable)
gimp_navigation_view_transform (GIMP_NAVIGATION_VIEW (widget));
}
static gboolean
gimp_navigation_view_draw (GtkWidget *widget,
cairo_t *cr)
{
GTK_WIDGET_CLASS (parent_class)->draw (widget, cr);
gimp_navigation_view_draw_marker (GIMP_NAVIGATION_VIEW (widget), cr);
return TRUE;
}
void
gimp_navigation_view_grab_pointer (GimpNavigationView *nav_view,
GdkEvent *event)
{
GtkWidget *widget = GTK_WIDGET (nav_view);
GdkDisplay *display;
GdkCursor *cursor;
GdkWindow *window;
nav_view->has_grab = TRUE;
gtk_grab_add (widget);
display = gtk_widget_get_display (widget);
cursor = gdk_cursor_new_for_display (display, GDK_FLEUR);
window = GIMP_VIEW (nav_view)->event_window;
gdk_seat_grab (gdk_event_get_seat (event),
window,
GDK_SEAT_CAPABILITY_ALL,
FALSE, cursor, event, NULL, NULL);
g_object_unref (cursor);
}
static gboolean
gimp_navigation_view_button_press (GtkWidget *widget,
GdkEventButton *bevent)
{
GimpNavigationView *nav_view = GIMP_NAVIGATION_VIEW (widget);
gint tx, ty;
GdkDisplay *display;
tx = bevent->x;
ty = bevent->y;
if (bevent->type == GDK_BUTTON_PRESS && bevent->button == 1)
{
if (! gimp_navigation_view_point_in_marker (nav_view, tx, ty))
{
GdkCursor *cursor;
nav_view->motion_offset_x = 0;
nav_view->motion_offset_y = 0;
gimp_navigation_view_move_to (nav_view, tx, ty);
display = gtk_widget_get_display (widget);
cursor = gdk_cursor_new_for_display (display, GDK_FLEUR);
gdk_window_set_cursor (GIMP_VIEW (widget)->event_window, cursor);
g_object_unref (cursor);
}
else
{
nav_view->motion_offset_x = tx - nav_view->p_center_x;
nav_view->motion_offset_y = ty - nav_view->p_center_y;
}
gimp_navigation_view_grab_pointer (nav_view, (GdkEvent *) bevent);
}
return TRUE;
}
static gboolean
gimp_navigation_view_button_release (GtkWidget *widget,
GdkEventButton *bevent)
{
GimpNavigationView *nav_view = GIMP_NAVIGATION_VIEW (widget);
if (bevent->button == 1 && nav_view->has_grab)
{
nav_view->has_grab = FALSE;
gtk_grab_remove (widget);
gdk_seat_ungrab (gdk_event_get_seat ((GdkEvent *) bevent));
}
return TRUE;
}
static gboolean
gimp_navigation_view_scroll (GtkWidget *widget,
GdkEventScroll *sevent)
{
if (sevent->state & gimp_get_toggle_behavior_mask ())
{
gdouble delta;
switch (sevent->direction)
{
case GDK_SCROLL_UP:
g_signal_emit (widget, view_signals[ZOOM], 0, GIMP_ZOOM_IN, 0.0);
break;
case GDK_SCROLL_DOWN:
g_signal_emit (widget, view_signals[ZOOM], 0, GIMP_ZOOM_OUT, 0.0);
break;
case GDK_SCROLL_SMOOTH:
gdk_event_get_scroll_deltas ((GdkEvent *) sevent, NULL, &delta);
g_signal_emit (widget, view_signals[ZOOM], 0, GIMP_ZOOM_SMOOTH, delta);
break;
default:
break;
}
}
else
{
g_signal_emit (widget, view_signals[SCROLL], 0, sevent);
}
return TRUE;
}
static gboolean
gimp_navigation_view_motion_notify (GtkWidget *widget,
GdkEventMotion *mevent)
{
GimpNavigationView *nav_view = GIMP_NAVIGATION_VIEW (widget);
GimpView *view = GIMP_VIEW (widget);
if (! nav_view->has_grab)
{
GdkDisplay *display = gtk_widget_get_display (widget);
GdkCursor *cursor;
if (nav_view->p_center_x == view->renderer->width / 2 &&
nav_view->p_center_y == view->renderer->height / 2 &&
nav_view->p_width == view->renderer->width &&
nav_view->p_height == view->renderer->height)
{
gdk_window_set_cursor (view->event_window, NULL);
return FALSE;
}
else if (gimp_navigation_view_point_in_marker (nav_view,
mevent->x, mevent->y))
{
cursor = gdk_cursor_new_for_display (display, GDK_FLEUR);
}
else
{
cursor = gdk_cursor_new_for_display (display, GDK_HAND2);
}
gdk_window_set_cursor (view->event_window, cursor);
g_object_unref (cursor);
return FALSE;
}
gimp_navigation_view_move_to (nav_view,
mevent->x - nav_view->motion_offset_x,
mevent->y - nav_view->motion_offset_y);
gdk_event_request_motions (mevent);
return TRUE;
}
static gboolean
gimp_navigation_view_key_press (GtkWidget *widget,
GdkEventKey *kevent)
{
GimpNavigationView *nav_view = GIMP_NAVIGATION_VIEW (widget);
gint scroll_x = 0;
gint scroll_y = 0;
switch (kevent->keyval)
{
case GDK_KEY_Up:
scroll_y = -1;
break;
case GDK_KEY_Left:
scroll_x = -1;
break;
case GDK_KEY_Right:
scroll_x = 1;
break;
case GDK_KEY_Down:
scroll_y = 1;
break;
default:
break;
}
if (scroll_x || scroll_y)
{
gimp_navigation_view_move_to (nav_view,
nav_view->p_center_x + scroll_x,
nav_view->p_center_y + scroll_y);
return TRUE;
}
return FALSE;
}
/* public functions */
void
gimp_navigation_view_set_marker (GimpNavigationView *nav_view,
gdouble center_x,
gdouble center_y,
gdouble width,
gdouble height,
gboolean flip_horizontally,
gboolean flip_vertically,
gdouble rotate_angle)
{
GimpView *view;
g_return_if_fail (GIMP_IS_NAVIGATION_VIEW (nav_view));
view = GIMP_VIEW (nav_view);
g_return_if_fail (view->renderer->viewable);
nav_view->center_x = center_x;
nav_view->center_y = center_y;
nav_view->width = MAX (1.0, width);
nav_view->height = MAX (1.0, height);
nav_view->flip_horizontally = flip_horizontally ? TRUE : FALSE;
nav_view->flip_vertically = flip_vertically ? TRUE : FALSE;
nav_view->rotate_angle = rotate_angle;
gimp_navigation_view_transform (nav_view);
/* Marker changed, redraw */
gtk_widget_queue_draw (GTK_WIDGET (view));
}
void
gimp_navigation_view_set_canvas (GimpNavigationView *nav_view,
gboolean visible,
gdouble x,
gdouble y,
gdouble width,
gdouble height)
{
GimpView *view;
g_return_if_fail (GIMP_IS_NAVIGATION_VIEW (nav_view));
view = GIMP_VIEW (nav_view);
g_return_if_fail (view->renderer->viewable);
nav_view->canvas_visible = visible;
nav_view->canvas_x = x;
nav_view->canvas_y = y;
nav_view->canvas_width = MAX (1.0, width);
nav_view->canvas_height = MAX (1.0, height);
gimp_navigation_view_transform (nav_view);
/* Marker changed, redraw */
gtk_widget_queue_draw (GTK_WIDGET (view));
}
void
gimp_navigation_view_set_motion_offset (GimpNavigationView *view,
gint motion_offset_x,
gint motion_offset_y)
{
g_return_if_fail (GIMP_IS_NAVIGATION_VIEW (view));
view->motion_offset_x = motion_offset_x;
view->motion_offset_y = motion_offset_y;
}
void
gimp_navigation_view_get_local_marker (GimpNavigationView *view,
gint *center_x,
gint *center_y,
gint *width,
gint *height)
{
g_return_if_fail (GIMP_IS_NAVIGATION_VIEW (view));
if (center_x) *center_x = view->p_center_x;
if (center_y) *center_y = view->p_center_y;
if (width) *width = view->p_width;
if (height) *height = view->p_height;
}
/* private functions */
static void
gimp_navigation_view_transform (GimpNavigationView *nav_view)
{
gdouble ratiox, ratioy;
gimp_navigation_view_get_ratio (nav_view, &ratiox, &ratioy);
nav_view->p_center_x = RINT (nav_view->center_x * ratiox);
nav_view->p_center_y = RINT (nav_view->center_y * ratioy);
nav_view->p_width = ceil (nav_view->width * ratiox);
nav_view->p_height = ceil (nav_view->height * ratioy);
nav_view->p_canvas_x = RINT (nav_view->canvas_x * ratiox);
nav_view->p_canvas_y = RINT (nav_view->canvas_y * ratioy);
nav_view->p_canvas_width = ceil (nav_view->canvas_width * ratiox);
nav_view->p_canvas_height = ceil (nav_view->canvas_height * ratioy);
}
static void
gimp_navigation_view_draw_marker (GimpNavigationView *nav_view,
cairo_t *cr)
{
GimpView *view = GIMP_VIEW (nav_view);
if (view->renderer->viewable && nav_view->width && nav_view->height)
{
GtkWidget *widget = GTK_WIDGET (view);
GtkAllocation allocation;
cairo_matrix_t matrix;
gint p_width_2;
gint p_height_2;
gdouble angle;
p_width_2 = nav_view->p_width / 2;
p_height_2 = nav_view->p_height / 2;
angle = G_PI * nav_view->rotate_angle / 180.0;
if (nav_view->flip_horizontally != nav_view->flip_vertically)
angle = -angle;
gtk_widget_get_allocation (widget, &allocation);
cairo_get_matrix (cr, &matrix);
cairo_rectangle (cr,
0, 0,
allocation.width, allocation.height);
cairo_translate (cr, nav_view->p_center_x, nav_view->p_center_y);
cairo_rotate (cr, -angle);
cairo_rectangle (cr,
-p_width_2, -p_height_2,
nav_view->p_width, nav_view->p_height);
cairo_set_source_rgba (cr, 0, 0, 0, 0.5);
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
cairo_fill (cr);
if (nav_view->canvas_visible &&
nav_view->canvas_width && nav_view->canvas_height)
{
cairo_save (cr);
cairo_set_matrix (cr, &matrix);
cairo_rectangle (cr,
nav_view->p_canvas_x + 0.5,
nav_view->p_canvas_y + 0.5,
nav_view->p_canvas_width - 1.0,
nav_view->p_canvas_height - 1.0);
gimp_canvas_set_canvas_style (GTK_WIDGET (nav_view), cr, 0, 0);
cairo_stroke (cr);
cairo_restore (cr);
}
cairo_rectangle (cr,
-p_width_2, -p_height_2,
nav_view->p_width, nav_view->p_height);
cairo_set_source_rgb (cr, 1, 1, 1);
cairo_set_line_width (cr, BORDER_WIDTH);
cairo_stroke (cr);
}
}
static void
gimp_navigation_view_move_to (GimpNavigationView *nav_view,
gint tx,
gint ty)
{
GimpView *view = GIMP_VIEW (nav_view);
gdouble ratiox, ratioy;
gdouble x, y;
if (! view->renderer->viewable)
return;
gimp_navigation_view_get_ratio (nav_view, &ratiox, &ratioy);
x = tx / ratiox;
y = ty / ratioy;
g_signal_emit (view, view_signals[MARKER_CHANGED], 0,
x, y, nav_view->width, nav_view->height);
}
static void
gimp_navigation_view_get_ratio (GimpNavigationView *nav_view,
gdouble *ratiox,
gdouble *ratioy)
{
GimpView *view = GIMP_VIEW (nav_view);
gint width;
gint height;
gimp_viewable_get_size (view->renderer->viewable, &width, &height);
*ratiox = (gdouble) view->renderer->width / (gdouble) width;
*ratioy = (gdouble) view->renderer->height / (gdouble) height;
}
static gboolean
gimp_navigation_view_point_in_marker (GimpNavigationView *nav_view,
gint x,
gint y)
{
gint p_width_2, p_height_2;
gdouble angle;
gdouble tx, ty;
p_width_2 = nav_view->p_width / 2;
p_height_2 = nav_view->p_height / 2;
angle = G_PI * nav_view->rotate_angle / 180.0;
if (nav_view->flip_horizontally != nav_view->flip_vertically)
angle = -angle;
x -= nav_view->p_center_x;
y -= nav_view->p_center_y;
tx = cos (angle) * x - sin (angle) * y;
ty = sin (angle) * x + cos (angle) * y;
return tx >= -p_width_2 && tx < p_width_2 &&
ty >= -p_height_2 && ty < p_height_2;
}