1503 lines
50 KiB
C
1503 lines
50 KiB
C
/* PIKA - Photo and Image Kooker Application
|
|
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
|
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
|
*
|
|
* Original copyright, applying to most contents (license remains unchanged):
|
|
* 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 <math.h>
|
|
|
|
#include <gegl.h>
|
|
#include <gtk/gtk.h>
|
|
|
|
#include "libpikabase/pikabase.h"
|
|
#include "libpikamath/pikamath.h"
|
|
#include "libpikawidgets/pikawidgets.h"
|
|
|
|
#include "display-types.h"
|
|
|
|
#include "config/pikaguiconfig.h"
|
|
|
|
#include "core/pika.h"
|
|
#include "core/pikaimage.h"
|
|
|
|
#include "pikadisplay.h"
|
|
#include "pikadisplayshell.h"
|
|
#include "pikadisplayshell-expose.h"
|
|
#include "pikadisplayshell-render.h"
|
|
#include "pikadisplayshell-rotate.h"
|
|
#include "pikadisplayshell-scale.h"
|
|
#include "pikadisplayshell-scroll.h"
|
|
#include "pikadisplayshell-transform.h"
|
|
#include "pikaimagewindow.h"
|
|
|
|
|
|
#define SCALE_TIMEOUT 2
|
|
#define SCALE_EPSILON 0.0001
|
|
#define ALMOST_CENTERED_THRESHOLD 2
|
|
|
|
#define SCALE_EQUALS(a,b) (fabs ((a) - (b)) < SCALE_EPSILON)
|
|
|
|
|
|
/* local function prototypes */
|
|
|
|
static void pika_display_shell_scale_get_screen_resolution
|
|
(PikaDisplayShell *shell,
|
|
gdouble *xres,
|
|
gdouble *yres);
|
|
static void pika_display_shell_scale_get_image_size_for_scale
|
|
(PikaDisplayShell *shell,
|
|
gdouble scale,
|
|
gint *w,
|
|
gint *h);
|
|
static void pika_display_shell_calculate_scale_x_and_y
|
|
(PikaDisplayShell *shell,
|
|
gdouble scale,
|
|
gdouble *scale_x,
|
|
gdouble *scale_y);
|
|
|
|
static void pika_display_shell_scale_to (PikaDisplayShell *shell,
|
|
gdouble scale,
|
|
gdouble viewport_x,
|
|
gdouble viewport_y);
|
|
static void pika_display_shell_scale_fit_or_fill (PikaDisplayShell *shell,
|
|
gboolean fill);
|
|
|
|
static gboolean pika_display_shell_scale_image_starts_to_fit
|
|
(PikaDisplayShell *shell,
|
|
gdouble new_scale,
|
|
gdouble current_scale,
|
|
gboolean *horizontally,
|
|
gboolean *vertically);
|
|
static gboolean pika_display_shell_scale_image_stops_to_fit
|
|
(PikaDisplayShell *shell,
|
|
gdouble new_scale,
|
|
gdouble current_scale,
|
|
gboolean *horizontally,
|
|
gboolean *vertically);
|
|
|
|
static gboolean pika_display_shell_scale_viewport_coord_almost_centered
|
|
(PikaDisplayShell *shell,
|
|
gint x,
|
|
gint y,
|
|
gboolean *horizontally,
|
|
gboolean *vertically);
|
|
|
|
static void pika_display_shell_scale_get_image_center_viewport
|
|
(PikaDisplayShell *shell,
|
|
gint *image_center_x,
|
|
gint *image_center_y);
|
|
|
|
|
|
static void pika_display_shell_scale_get_zoom_focus (PikaDisplayShell *shell,
|
|
gdouble new_scale,
|
|
gdouble current_scale,
|
|
gdouble *x,
|
|
gdouble *y,
|
|
PikaZoomFocus zoom_focus);
|
|
|
|
|
|
/* public functions */
|
|
|
|
/**
|
|
* pika_display_shell_scale_revert:
|
|
* @shell: the #PikaDisplayShell
|
|
*
|
|
* Reverts the display to the previously used scale. If no previous
|
|
* scale exist, then the call does nothing.
|
|
*
|
|
* Returns: %TRUE if the scale was reverted, otherwise %FALSE.
|
|
**/
|
|
gboolean
|
|
pika_display_shell_scale_revert (PikaDisplayShell *shell)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_DISPLAY_SHELL (shell), FALSE);
|
|
|
|
/* don't bother if no scale has been set */
|
|
if (shell->last_scale < SCALE_EPSILON)
|
|
return FALSE;
|
|
|
|
shell->last_scale_time = 0;
|
|
|
|
pika_display_shell_scale_by_values (shell,
|
|
shell->last_scale,
|
|
shell->last_offset_x,
|
|
shell->last_offset_y,
|
|
FALSE); /* don't resize the window */
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_scale_can_revert:
|
|
* @shell: the #PikaDisplayShell
|
|
*
|
|
* Returns: %TRUE if a previous display scale exists, otherwise %FALSE.
|
|
**/
|
|
gboolean
|
|
pika_display_shell_scale_can_revert (PikaDisplayShell *shell)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_DISPLAY_SHELL (shell), FALSE);
|
|
|
|
return (shell->last_scale > SCALE_EPSILON);
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_scale_save_revert_values:
|
|
* @shell:
|
|
*
|
|
* Handle the updating of the Revert Zoom variables.
|
|
**/
|
|
void
|
|
pika_display_shell_scale_save_revert_values (PikaDisplayShell *shell)
|
|
{
|
|
guint now;
|
|
|
|
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
|
|
|
|
now = time (NULL);
|
|
|
|
if (now - shell->last_scale_time >= SCALE_TIMEOUT)
|
|
{
|
|
shell->last_scale = pika_zoom_model_get_factor (shell->zoom);
|
|
shell->last_offset_x = shell->offset_x;
|
|
shell->last_offset_y = shell->offset_y;
|
|
}
|
|
|
|
shell->last_scale_time = now;
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_scale_set_dot_for_dot:
|
|
* @shell: the #PikaDisplayShell
|
|
* @dot_for_dot: whether "Dot for Dot" should be enabled
|
|
*
|
|
* If @dot_for_dot is set to %TRUE then the "Dot for Dot" mode (where image and
|
|
* screen pixels are of the same size) is activated. Dually, the mode is
|
|
* disabled if @dot_for_dot is %FALSE.
|
|
**/
|
|
void
|
|
pika_display_shell_scale_set_dot_for_dot (PikaDisplayShell *shell,
|
|
gboolean dot_for_dot)
|
|
{
|
|
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
|
|
|
|
if (dot_for_dot != shell->dot_for_dot)
|
|
{
|
|
PikaDisplayConfig *config = shell->display->config;
|
|
gboolean resize_window;
|
|
|
|
/* Resize windows only in multi-window mode */
|
|
resize_window = (config->resize_windows_on_zoom &&
|
|
! PIKA_GUI_CONFIG (config)->single_window_mode);
|
|
|
|
/* freeze the active tool */
|
|
pika_display_shell_pause (shell);
|
|
|
|
shell->dot_for_dot = dot_for_dot;
|
|
|
|
pika_display_shell_scale_update (shell);
|
|
|
|
pika_display_shell_scale_resize (shell, resize_window, FALSE);
|
|
|
|
/* re-enable the active tool */
|
|
pika_display_shell_resume (shell);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_scale_get_image_size:
|
|
* @shell:
|
|
* @w:
|
|
* @h:
|
|
*
|
|
* Gets the size of the rendered image after it has been scaled.
|
|
*
|
|
**/
|
|
void
|
|
pika_display_shell_scale_get_image_size (PikaDisplayShell *shell,
|
|
gint *w,
|
|
gint *h)
|
|
{
|
|
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
|
|
|
|
pika_display_shell_scale_get_image_size_for_scale (shell,
|
|
pika_zoom_model_get_factor (shell->zoom),
|
|
w, h);
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_scale_get_image_bounds:
|
|
* @shell:
|
|
* @x:
|
|
* @y:
|
|
* @w:
|
|
* @h:
|
|
*
|
|
* Gets the screen-space boudning box of the image, after it has
|
|
* been transformed (i.e., scaled, rotated, and scrolled).
|
|
**/
|
|
void
|
|
pika_display_shell_scale_get_image_bounds (PikaDisplayShell *shell,
|
|
gint *x,
|
|
gint *y,
|
|
gint *w,
|
|
gint *h)
|
|
{
|
|
PikaImage *image;
|
|
gdouble x1, y1;
|
|
gdouble x2, y2;
|
|
|
|
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
|
|
|
|
image = pika_display_get_image (shell->display);
|
|
|
|
pika_display_shell_transform_bounds (shell,
|
|
0, 0,
|
|
pika_image_get_width (image),
|
|
pika_image_get_height (image),
|
|
&x1, &y1,
|
|
&x2, &y2);
|
|
|
|
x1 = ceil (x1);
|
|
y1 = ceil (y1);
|
|
x2 = floor (x2);
|
|
y2 = floor (y2);
|
|
|
|
if (x) *x = x1 + shell->offset_x;
|
|
if (y) *y = y1 + shell->offset_y;
|
|
if (w) *w = x2 - x1;
|
|
if (h) *h = y2 - y1;
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_scale_get_image_unrotated_bounds:
|
|
* @shell:
|
|
* @x:
|
|
* @y:
|
|
* @w:
|
|
* @h:
|
|
*
|
|
* Gets the screen-space boudning box of the image, after it has
|
|
* been scaled and scrolled, but before it has been rotated.
|
|
**/
|
|
void
|
|
pika_display_shell_scale_get_image_unrotated_bounds (PikaDisplayShell *shell,
|
|
gint *x,
|
|
gint *y,
|
|
gint *w,
|
|
gint *h)
|
|
{
|
|
PikaImage *image;
|
|
|
|
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
|
|
|
|
image = pika_display_get_image (shell->display);
|
|
|
|
if (x) *x = -shell->offset_x;
|
|
if (y) *y = -shell->offset_y;
|
|
if (w) *w = floor (pika_image_get_width (image) * shell->scale_x);
|
|
if (h) *h = floor (pika_image_get_height (image) * shell->scale_y);
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_scale_get_image_bounding_box:
|
|
* @shell:
|
|
* @x:
|
|
* @y:
|
|
* @w:
|
|
* @h:
|
|
*
|
|
* Gets the screen-space boudning box of the image content, after it has
|
|
* been transformed (i.e., scaled, rotated, and scrolled).
|
|
**/
|
|
void
|
|
pika_display_shell_scale_get_image_bounding_box (PikaDisplayShell *shell,
|
|
gint *x,
|
|
gint *y,
|
|
gint *w,
|
|
gint *h)
|
|
{
|
|
GeglRectangle bounding_box;
|
|
gdouble x1, y1;
|
|
gdouble x2, y2;
|
|
|
|
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
|
|
|
|
bounding_box = pika_display_shell_get_bounding_box (shell);
|
|
|
|
pika_display_shell_transform_bounds (shell,
|
|
bounding_box.x,
|
|
bounding_box.y,
|
|
bounding_box.x + bounding_box.width,
|
|
bounding_box.y + bounding_box.height,
|
|
&x1, &y1,
|
|
&x2, &y2);
|
|
|
|
if (! shell->show_all)
|
|
{
|
|
x1 = ceil (x1);
|
|
y1 = ceil (y1);
|
|
x2 = floor (x2);
|
|
y2 = floor (y2);
|
|
}
|
|
else
|
|
{
|
|
x1 = floor (x1);
|
|
y1 = floor (y1);
|
|
x2 = ceil (x2);
|
|
y2 = ceil (y2);
|
|
}
|
|
|
|
if (x) *x = x1 + shell->offset_x;
|
|
if (y) *y = y1 + shell->offset_y;
|
|
if (w) *w = x2 - x1;
|
|
if (h) *h = y2 - y1;
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_scale_get_image_unrotated_bounding_box:
|
|
* @shell:
|
|
* @x:
|
|
* @y:
|
|
* @w:
|
|
* @h:
|
|
*
|
|
* Gets the screen-space boudning box of the image content, after it has
|
|
* been scaled and scrolled, but before it has been rotated.
|
|
**/
|
|
void
|
|
pika_display_shell_scale_get_image_unrotated_bounding_box (PikaDisplayShell *shell,
|
|
gint *x,
|
|
gint *y,
|
|
gint *w,
|
|
gint *h)
|
|
{
|
|
GeglRectangle bounding_box;
|
|
gdouble x1, y1;
|
|
gdouble x2, y2;
|
|
|
|
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
|
|
|
|
bounding_box = pika_display_shell_get_bounding_box (shell);
|
|
|
|
x1 = bounding_box.x * shell->scale_x -
|
|
shell->offset_x;
|
|
y1 = bounding_box.y * shell->scale_y -
|
|
shell->offset_y;
|
|
|
|
x2 = (bounding_box.x + bounding_box.width) * shell->scale_x -
|
|
shell->offset_x;
|
|
y2 = (bounding_box.y + bounding_box.height) * shell->scale_y -
|
|
shell->offset_y;
|
|
|
|
if (! shell->show_all)
|
|
{
|
|
x1 = ceil (x1);
|
|
y1 = ceil (y1);
|
|
x2 = floor (x2);
|
|
y2 = floor (y2);
|
|
}
|
|
else
|
|
{
|
|
x1 = floor (x1);
|
|
y1 = floor (y1);
|
|
x2 = ceil (x2);
|
|
y2 = ceil (y2);
|
|
}
|
|
|
|
if (x) *x = x1;
|
|
if (y) *y = y1;
|
|
if (w) *w = x2 - x1;
|
|
if (h) *h = y2 - y1;
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_scale_image_is_within_viewport:
|
|
* @shell:
|
|
*
|
|
* Returns: %TRUE if the (scaled) image is smaller than and within the
|
|
* viewport.
|
|
**/
|
|
gboolean
|
|
pika_display_shell_scale_image_is_within_viewport (PikaDisplayShell *shell,
|
|
gboolean *horizontally,
|
|
gboolean *vertically)
|
|
{
|
|
gboolean horizontally_dummy, vertically_dummy;
|
|
|
|
g_return_val_if_fail (PIKA_IS_DISPLAY_SHELL (shell), FALSE);
|
|
|
|
if (! horizontally) horizontally = &horizontally_dummy;
|
|
if (! vertically) vertically = &vertically_dummy;
|
|
|
|
if (! pika_display_shell_get_infinite_canvas (shell))
|
|
{
|
|
gint sx, sy;
|
|
gint sw, sh;
|
|
|
|
pika_display_shell_scale_get_image_bounding_box (shell,
|
|
&sx, &sy, &sw, &sh);
|
|
|
|
sx -= shell->offset_x;
|
|
sy -= shell->offset_y;
|
|
|
|
*horizontally = sx >= 0 && sx + sw <= shell->disp_width;
|
|
*vertically = sy >= 0 && sy + sh <= shell->disp_height;
|
|
}
|
|
else
|
|
{
|
|
*horizontally = FALSE;
|
|
*vertically = FALSE;
|
|
}
|
|
|
|
return *vertically && *horizontally;
|
|
}
|
|
|
|
/* We used to calculate the scale factor in the SCALEFACTOR_X() and
|
|
* SCALEFACTOR_Y() macros. But since these are rather frequently
|
|
* called and the values rarely change, we now store them in the
|
|
* shell and call this function whenever they need to be recalculated.
|
|
*/
|
|
void
|
|
pika_display_shell_scale_update (PikaDisplayShell *shell)
|
|
{
|
|
PikaImage *image;
|
|
|
|
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
|
|
|
|
image = pika_display_get_image (shell->display);
|
|
|
|
if (image)
|
|
{
|
|
pika_display_shell_calculate_scale_x_and_y (shell,
|
|
pika_zoom_model_get_factor (shell->zoom),
|
|
&shell->scale_x,
|
|
&shell->scale_y);
|
|
}
|
|
else
|
|
{
|
|
shell->scale_x = 1.0;
|
|
shell->scale_y = 1.0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_scale:
|
|
* @shell: the #PikaDisplayShell
|
|
* @zoom_type: whether to zoom in, out or to a specific scale
|
|
* @scale: ignored unless @zoom_type == %PIKA_ZOOM_TO
|
|
*
|
|
* This function figures out the context of the zoom and behaves
|
|
* appropriately thereafter.
|
|
*
|
|
**/
|
|
void
|
|
pika_display_shell_scale (PikaDisplayShell *shell,
|
|
PikaZoomType zoom_type,
|
|
gdouble new_scale,
|
|
PikaZoomFocus zoom_focus)
|
|
{
|
|
PikaDisplayConfig *config;
|
|
gdouble current_scale;
|
|
gdouble delta = 0.0;
|
|
gboolean resize_window;
|
|
|
|
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
|
|
g_return_if_fail (shell->canvas != NULL);
|
|
|
|
current_scale = pika_zoom_model_get_factor (shell->zoom);
|
|
|
|
if (zoom_type == PIKA_ZOOM_SMOOTH)
|
|
delta = -new_scale;
|
|
|
|
if (zoom_type == PIKA_ZOOM_PINCH)
|
|
delta = new_scale;
|
|
|
|
if (zoom_type != PIKA_ZOOM_TO)
|
|
new_scale = pika_zoom_model_zoom_step (zoom_type, current_scale, delta);
|
|
|
|
if (SCALE_EQUALS (new_scale, current_scale))
|
|
return;
|
|
|
|
config = shell->display->config;
|
|
|
|
/* Resize windows only in multi-window mode */
|
|
resize_window = (config->resize_windows_on_zoom &&
|
|
! PIKA_GUI_CONFIG (config)->single_window_mode);
|
|
|
|
if (resize_window)
|
|
{
|
|
/* If the window is resized on zoom, simply do the zoom and get
|
|
* things rolling
|
|
*/
|
|
pika_zoom_model_zoom (shell->zoom, PIKA_ZOOM_TO, new_scale);
|
|
|
|
pika_display_shell_scale_resize (shell, TRUE, FALSE);
|
|
|
|
/* XXX The @zoom_focus policy is clearly not working in this code
|
|
* path. This should be fixed.
|
|
*/
|
|
}
|
|
else
|
|
{
|
|
gdouble x, y;
|
|
gint image_center_x;
|
|
gint image_center_y;
|
|
|
|
pika_display_shell_scale_get_zoom_focus (shell,
|
|
new_scale,
|
|
current_scale,
|
|
&x,
|
|
&y,
|
|
zoom_focus);
|
|
pika_display_shell_scale_get_image_center_viewport (shell,
|
|
&image_center_x,
|
|
&image_center_y);
|
|
|
|
pika_display_shell_scale_to (shell, new_scale, x, y);
|
|
|
|
/* skip centering magic if pointer focus was requested */
|
|
if (zoom_focus != PIKA_ZOOM_FOCUS_POINTER)
|
|
{
|
|
gboolean starts_fitting_horiz;
|
|
gboolean starts_fitting_vert;
|
|
gboolean zoom_focus_almost_centered_horiz;
|
|
gboolean zoom_focus_almost_centered_vert;
|
|
gboolean image_center_almost_centered_horiz;
|
|
gboolean image_center_almost_centered_vert;
|
|
|
|
/* If an image axis started to fit due to zooming out or if
|
|
* the focus point is as good as in the center, center on
|
|
* that axis
|
|
*/
|
|
pika_display_shell_scale_image_starts_to_fit (shell,
|
|
new_scale,
|
|
current_scale,
|
|
&starts_fitting_horiz,
|
|
&starts_fitting_vert);
|
|
|
|
pika_display_shell_scale_viewport_coord_almost_centered (shell,
|
|
x,
|
|
y,
|
|
&zoom_focus_almost_centered_horiz,
|
|
&zoom_focus_almost_centered_vert);
|
|
pika_display_shell_scale_viewport_coord_almost_centered (shell,
|
|
image_center_x,
|
|
image_center_y,
|
|
&image_center_almost_centered_horiz,
|
|
&image_center_almost_centered_vert);
|
|
|
|
pika_display_shell_scroll_center_image (shell,
|
|
starts_fitting_horiz ||
|
|
(zoom_focus_almost_centered_horiz &&
|
|
image_center_almost_centered_horiz),
|
|
starts_fitting_vert ||
|
|
(zoom_focus_almost_centered_vert &&
|
|
image_center_almost_centered_vert));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_scale_to_rectangle:
|
|
* @shell: the #PikaDisplayShell
|
|
* @zoom_type: whether to zoom in or out
|
|
* @x: retangle's x in image coordinates
|
|
* @y: retangle's y in image coordinates
|
|
* @width: retangle's width in image coordinates
|
|
* @height: retangle's height in image coordinates
|
|
* @resize_window: whether the display window should be resized
|
|
*
|
|
* Scales and scrolls to a specific image rectangle
|
|
**/
|
|
void
|
|
pika_display_shell_scale_to_rectangle (PikaDisplayShell *shell,
|
|
PikaZoomType zoom_type,
|
|
gdouble x,
|
|
gdouble y,
|
|
gdouble width,
|
|
gdouble height,
|
|
gboolean resize_window)
|
|
{
|
|
gdouble current_scale;
|
|
gdouble new_scale;
|
|
gdouble factor = 1.0;
|
|
gint offset_x = 0;
|
|
gint offset_y = 0;
|
|
|
|
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
|
|
|
|
pika_display_shell_transform_bounds (shell,
|
|
x, y,
|
|
x + width, y + height,
|
|
&x, &y,
|
|
&width, &height);
|
|
|
|
/* Convert scrolled (x1, y1, x2, y2) to unscrolled (x, y, width, height). */
|
|
width -= x;
|
|
height -= y;
|
|
x += shell->offset_x;
|
|
y += shell->offset_y;
|
|
|
|
width = MAX (1.0, width);
|
|
height = MAX (1.0, height);
|
|
|
|
current_scale = pika_zoom_model_get_factor (shell->zoom);
|
|
|
|
switch (zoom_type)
|
|
{
|
|
case PIKA_ZOOM_IN:
|
|
factor = MIN ((shell->disp_width / width),
|
|
(shell->disp_height / height));
|
|
break;
|
|
|
|
case PIKA_ZOOM_OUT:
|
|
factor = MAX ((width / shell->disp_width),
|
|
(height / shell->disp_height));
|
|
break;
|
|
|
|
default:
|
|
g_return_if_reached ();
|
|
break;
|
|
}
|
|
|
|
new_scale = current_scale * factor;
|
|
|
|
switch (zoom_type)
|
|
{
|
|
case PIKA_ZOOM_IN:
|
|
/* move the center of the rectangle to the center of the
|
|
* viewport:
|
|
*
|
|
* new_offset = center of rectangle in new scale screen coords
|
|
* including offset
|
|
* -
|
|
* center of viewport in screen coords without
|
|
* offset
|
|
*/
|
|
offset_x = RINT (factor * (x + width / 2.0) - (shell->disp_width / 2));
|
|
offset_y = RINT (factor * (y + height / 2.0) - (shell->disp_height / 2));
|
|
break;
|
|
|
|
case PIKA_ZOOM_OUT:
|
|
/* move the center of the viewport to the center of the
|
|
* rectangle:
|
|
*
|
|
* new_offset = center of viewport in new scale screen coords
|
|
* including offset
|
|
* -
|
|
* center of rectangle in screen coords without
|
|
* offset
|
|
*/
|
|
offset_x = RINT (factor * (shell->offset_x + shell->disp_width / 2) -
|
|
((x + width / 2.0) - shell->offset_x));
|
|
|
|
offset_y = RINT (factor * (shell->offset_y + shell->disp_height / 2) -
|
|
((y + height / 2.0) - shell->offset_y));
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (new_scale != current_scale ||
|
|
offset_x != shell->offset_x ||
|
|
offset_y != shell->offset_y)
|
|
{
|
|
pika_display_shell_scale_by_values (shell,
|
|
new_scale,
|
|
offset_x, offset_y,
|
|
resize_window);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_scale_fit_in:
|
|
* @shell: the #PikaDisplayShell
|
|
*
|
|
* Sets the scale such that the entire image precisely fits in the
|
|
* display area.
|
|
**/
|
|
void
|
|
pika_display_shell_scale_fit_in (PikaDisplayShell *shell)
|
|
{
|
|
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
|
|
|
|
pika_display_shell_scale_fit_or_fill (shell,
|
|
/* fill = */ FALSE);
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_scale_fill:
|
|
* @shell: the #PikaDisplayShell
|
|
*
|
|
* Sets the scale such that the entire display area is precisely
|
|
* filled by the image.
|
|
**/
|
|
void
|
|
pika_display_shell_scale_fill (PikaDisplayShell *shell)
|
|
{
|
|
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
|
|
|
|
pika_display_shell_scale_fit_or_fill (shell,
|
|
/* fill = */ TRUE);
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_scale_by_values:
|
|
* @shell: the #PikaDisplayShell
|
|
* @scale: the new scale
|
|
* @offset_x: the new X offset
|
|
* @offset_y: the new Y offset
|
|
* @resize_window: whether the display window should be resized
|
|
*
|
|
* Directly sets the image scale and image offsets used by the display. If
|
|
* @resize_window is %TRUE then the display window is resized to better
|
|
* accommodate the image, see pika_display_shell_shrink_wrap().
|
|
**/
|
|
void
|
|
pika_display_shell_scale_by_values (PikaDisplayShell *shell,
|
|
gdouble scale,
|
|
gint offset_x,
|
|
gint offset_y,
|
|
gboolean resize_window)
|
|
{
|
|
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
|
|
|
|
/* Abort early if the values are all setup already. We don't
|
|
* want to inadvertently resize the window (bug #164281).
|
|
*/
|
|
if (SCALE_EQUALS (pika_zoom_model_get_factor (shell->zoom), scale) &&
|
|
shell->offset_x == offset_x &&
|
|
shell->offset_y == offset_y)
|
|
return;
|
|
|
|
pika_display_shell_scale_save_revert_values (shell);
|
|
|
|
/* freeze the active tool */
|
|
pika_display_shell_pause (shell);
|
|
|
|
pika_zoom_model_zoom (shell->zoom, PIKA_ZOOM_TO, scale);
|
|
|
|
shell->offset_x = offset_x;
|
|
shell->offset_y = offset_y;
|
|
|
|
pika_display_shell_rotate_update_transform (shell);
|
|
|
|
pika_display_shell_scale_resize (shell, resize_window, FALSE);
|
|
|
|
/* re-enable the active tool */
|
|
pika_display_shell_resume (shell);
|
|
}
|
|
|
|
void
|
|
pika_display_shell_scale_drag (PikaDisplayShell *shell,
|
|
gdouble start_x,
|
|
gdouble start_y,
|
|
gdouble delta_x,
|
|
gdouble delta_y)
|
|
{
|
|
gdouble scale;
|
|
|
|
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
|
|
|
|
scale = pika_zoom_model_get_factor (shell->zoom);
|
|
|
|
if (delta_y != 0.0)
|
|
{
|
|
PikaDisplayConfig *config = shell->display->config;
|
|
gdouble speed = config->drag_zoom_speed * 0.01;
|
|
|
|
pika_display_shell_push_zoom_focus_pointer_pos (shell, start_x, start_y);
|
|
|
|
if (config->drag_zoom_mode == PROP_DRAG_ZOOM_MODE_DISTANCE)
|
|
{
|
|
pika_display_shell_scale (shell,
|
|
PIKA_ZOOM_TO,
|
|
scale * exp (0.005 * speed * delta_y),
|
|
PIKA_ZOOM_FOCUS_POINTER);
|
|
}
|
|
else if (delta_y > 0.0) /* drag_zoom_mode == PROP_DRAG_ZOOM_MODE_DURATION */
|
|
{
|
|
pika_display_shell_scale (shell,
|
|
PIKA_ZOOM_TO,
|
|
scale * (1 + 0.1 * speed),
|
|
PIKA_ZOOM_FOCUS_POINTER);
|
|
}
|
|
else /* delta_y < 0.0 */
|
|
{
|
|
pika_display_shell_scale (shell,
|
|
PIKA_ZOOM_TO,
|
|
scale * (1 - 0.1 * speed),
|
|
PIKA_ZOOM_FOCUS_POINTER);
|
|
}
|
|
|
|
|
|
if (shell->zoom_focus_point)
|
|
{
|
|
/* In case we hit one of the cases when the focus pointer
|
|
* position was unused.
|
|
*/
|
|
g_slice_free (GdkPoint, shell->zoom_focus_point);
|
|
shell->zoom_focus_point = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_scale_shrink_wrap:
|
|
* @shell: the #PikaDisplayShell
|
|
*
|
|
* Convenience function with the same functionality as
|
|
* pika_display_shell_scale_resize(@shell, TRUE, grow_only).
|
|
**/
|
|
void
|
|
pika_display_shell_scale_shrink_wrap (PikaDisplayShell *shell,
|
|
gboolean grow_only)
|
|
{
|
|
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
|
|
|
|
pika_display_shell_scale_resize (shell, TRUE, grow_only);
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_scale_resize:
|
|
* @shell: the #PikaDisplayShell
|
|
* @resize_window: whether the display window should be resized
|
|
* @grow_only: whether shrinking of the window is allowed or not
|
|
*
|
|
* Function commonly called after a change in display scale to make the changes
|
|
* visible to the user. If @resize_window is %TRUE then the display window is
|
|
* resized to accommodate the display image as per
|
|
* pika_display_shell_shrink_wrap().
|
|
**/
|
|
void
|
|
pika_display_shell_scale_resize (PikaDisplayShell *shell,
|
|
gboolean resize_window,
|
|
gboolean grow_only)
|
|
{
|
|
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
|
|
|
|
/* freeze the active tool */
|
|
pika_display_shell_pause (shell);
|
|
|
|
if (resize_window)
|
|
{
|
|
PikaImageWindow *window = pika_display_shell_get_window (shell);
|
|
|
|
if (window && pika_image_window_get_active_shell (window) == shell)
|
|
{
|
|
pika_image_window_shrink_wrap (window, grow_only);
|
|
}
|
|
}
|
|
|
|
pika_display_shell_scroll_clamp_and_update (shell);
|
|
pika_display_shell_scaled (shell);
|
|
|
|
pika_display_shell_expose_full (shell);
|
|
pika_display_shell_render_invalidate_full (shell);
|
|
|
|
/* re-enable the active tool */
|
|
pika_display_shell_resume (shell);
|
|
}
|
|
|
|
void
|
|
pika_display_shell_set_initial_scale (PikaDisplayShell *shell,
|
|
gdouble scale,
|
|
gint *display_width,
|
|
gint *display_height)
|
|
{
|
|
PikaImage *image;
|
|
GdkRectangle workarea;
|
|
gint image_width;
|
|
gint image_height;
|
|
gint monitor_width;
|
|
gint monitor_height;
|
|
gint shell_width;
|
|
gint shell_height;
|
|
|
|
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
|
|
|
|
image = pika_display_get_image (shell->display);
|
|
|
|
gdk_monitor_get_workarea (shell->initial_monitor, &workarea);
|
|
|
|
image_width = pika_image_get_width (image);
|
|
image_height = pika_image_get_height (image);
|
|
|
|
monitor_width = workarea.width * 0.75;
|
|
monitor_height = workarea.height * 0.75;
|
|
|
|
/* We need to zoom before we use SCALE[XY] */
|
|
pika_zoom_model_zoom (shell->zoom, PIKA_ZOOM_TO, scale);
|
|
|
|
shell_width = SCALEX (shell, image_width);
|
|
shell_height = SCALEY (shell, image_height);
|
|
|
|
if (shell->display->config->initial_zoom_to_fit)
|
|
{
|
|
/* Limit to the size of the monitor... */
|
|
if (shell_width > monitor_width || shell_height > monitor_height)
|
|
{
|
|
gdouble new_scale;
|
|
gdouble current = pika_zoom_model_get_factor (shell->zoom);
|
|
|
|
new_scale = current * MIN (((gdouble) monitor_height) / shell_height,
|
|
((gdouble) monitor_width) / shell_width);
|
|
|
|
new_scale = pika_zoom_model_zoom_step (PIKA_ZOOM_OUT, new_scale, 0.0);
|
|
|
|
/* Since zooming out might skip a zoom step we zoom in
|
|
* again and test if we are small enough.
|
|
*/
|
|
pika_zoom_model_zoom (shell->zoom, PIKA_ZOOM_TO,
|
|
pika_zoom_model_zoom_step (PIKA_ZOOM_IN,
|
|
new_scale, 0.0));
|
|
|
|
if (SCALEX (shell, image_width) > monitor_width ||
|
|
SCALEY (shell, image_height) > monitor_height)
|
|
pika_zoom_model_zoom (shell->zoom, PIKA_ZOOM_TO, new_scale);
|
|
|
|
shell_width = SCALEX (shell, image_width);
|
|
shell_height = SCALEY (shell, image_height);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Set up size like above, but do not zoom to fit. Useful when
|
|
* working on large images.
|
|
*/
|
|
shell_width = MIN (shell_width, monitor_width);
|
|
shell_height = MIN (shell_height, monitor_height);
|
|
}
|
|
|
|
if (display_width) *display_width = shell_width;
|
|
if (display_height) *display_height = shell_height;
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_get_rotated_scale:
|
|
* @shell: the #PikaDisplayShell
|
|
* @scale_x: horizontal scale output
|
|
* @scale_y: vertical scale output
|
|
*
|
|
* Returns the screen space horizontal and vertical scaling
|
|
* factors, taking rotation into account.
|
|
**/
|
|
void
|
|
pika_display_shell_get_rotated_scale (PikaDisplayShell *shell,
|
|
gdouble *scale_x,
|
|
gdouble *scale_y)
|
|
{
|
|
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
|
|
|
|
if (shell->rotate_angle == 0.0 || shell->scale_x == shell->scale_y)
|
|
{
|
|
if (scale_x) *scale_x = shell->scale_x;
|
|
if (scale_y) *scale_y = shell->scale_y;
|
|
}
|
|
else
|
|
{
|
|
gdouble a = G_PI * shell->rotate_angle / 180.0;
|
|
gdouble cos_a = cos (a);
|
|
gdouble sin_a = sin (a);
|
|
|
|
if (scale_x) *scale_x = 1.0 / sqrt (SQR (cos_a / shell->scale_x) +
|
|
SQR (sin_a / shell->scale_y));
|
|
|
|
if (scale_y) *scale_y = 1.0 / sqrt (SQR (cos_a / shell->scale_y) +
|
|
SQR (sin_a / shell->scale_x));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_push_zoom_focus_pointer_pos:
|
|
* @shell:
|
|
* @x:
|
|
* @y:
|
|
*
|
|
* When the zoom focus mechanism asks for the pointer the next time,
|
|
* use @x and @y.
|
|
*
|
|
* It was primarily created for unit testing (see commit 7e3898da093).
|
|
* Therefore it should not be used by our code. When it is, we should
|
|
* make sure that @shell->zoom_focus_point has been properly used and
|
|
* freed, if we don't want it to leak.
|
|
* Just calling pika_display_shell_scale() is not enough as there are
|
|
* currently some code paths where the values is not used.
|
|
**/
|
|
void
|
|
pika_display_shell_push_zoom_focus_pointer_pos (PikaDisplayShell *shell,
|
|
gint x,
|
|
gint y)
|
|
{
|
|
GdkPoint *point = g_slice_new (GdkPoint);
|
|
point->x = x;
|
|
point->y = y;
|
|
|
|
g_slice_free (GdkPoint, shell->zoom_focus_point);
|
|
shell->zoom_focus_point = point;
|
|
}
|
|
|
|
|
|
/* private functions */
|
|
|
|
static void
|
|
pika_display_shell_scale_get_screen_resolution (PikaDisplayShell *shell,
|
|
gdouble *xres,
|
|
gdouble *yres)
|
|
{
|
|
gdouble x, y;
|
|
|
|
if (shell->dot_for_dot)
|
|
{
|
|
pika_image_get_resolution (pika_display_get_image (shell->display),
|
|
&x, &y);
|
|
}
|
|
else
|
|
{
|
|
x = shell->monitor_xres;
|
|
y = shell->monitor_yres;
|
|
}
|
|
|
|
if (xres) *xres = x;
|
|
if (yres) *yres = y;
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_scale_get_image_size_for_scale:
|
|
* @shell:
|
|
* @scale:
|
|
* @w:
|
|
* @h:
|
|
*
|
|
**/
|
|
static void
|
|
pika_display_shell_scale_get_image_size_for_scale (PikaDisplayShell *shell,
|
|
gdouble scale,
|
|
gint *w,
|
|
gint *h)
|
|
{
|
|
PikaImage *image = pika_display_get_image (shell->display);
|
|
gdouble scale_x;
|
|
gdouble scale_y;
|
|
|
|
pika_display_shell_calculate_scale_x_and_y (shell, scale, &scale_x, &scale_y);
|
|
|
|
if (w) *w = scale_x * pika_image_get_width (image);
|
|
if (h) *h = scale_y * pika_image_get_height (image);
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_calculate_scale_x_and_y:
|
|
* @shell:
|
|
* @scale:
|
|
* @scale_x:
|
|
* @scale_y:
|
|
*
|
|
**/
|
|
static void
|
|
pika_display_shell_calculate_scale_x_and_y (PikaDisplayShell *shell,
|
|
gdouble scale,
|
|
gdouble *scale_x,
|
|
gdouble *scale_y)
|
|
{
|
|
PikaImage *image = pika_display_get_image (shell->display);
|
|
gdouble xres;
|
|
gdouble yres;
|
|
gdouble screen_xres;
|
|
gdouble screen_yres;
|
|
|
|
pika_image_get_resolution (image, &xres, &yres);
|
|
pika_display_shell_scale_get_screen_resolution (shell,
|
|
&screen_xres, &screen_yres);
|
|
|
|
if (scale_x) *scale_x = scale * screen_xres / xres;
|
|
if (scale_y) *scale_y = scale * screen_yres / yres;
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_scale_to:
|
|
* @shell:
|
|
* @scale:
|
|
* @viewport_x:
|
|
* @viewport_y:
|
|
*
|
|
* Zooms. The display offsets are adjusted so that the point specified
|
|
* by @x and @y doesn't change it's position on screen.
|
|
**/
|
|
static void
|
|
pika_display_shell_scale_to (PikaDisplayShell *shell,
|
|
gdouble scale,
|
|
gdouble viewport_x,
|
|
gdouble viewport_y)
|
|
{
|
|
gdouble image_x, image_y;
|
|
gdouble new_viewport_x, new_viewport_y;
|
|
|
|
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
|
|
|
|
if (! shell->display)
|
|
return;
|
|
|
|
/* freeze the active tool */
|
|
pika_display_shell_pause (shell);
|
|
|
|
pika_display_shell_untransform_xy_f (shell,
|
|
viewport_x,
|
|
viewport_y,
|
|
&image_x,
|
|
&image_y);
|
|
|
|
/* Note that we never come here if we need to resize_windows_on_zoom
|
|
*/
|
|
pika_display_shell_scale_by_values (shell,
|
|
scale,
|
|
shell->offset_x,
|
|
shell->offset_y,
|
|
FALSE);
|
|
|
|
pika_display_shell_transform_xy_f (shell,
|
|
image_x,
|
|
image_y,
|
|
&new_viewport_x,
|
|
&new_viewport_y);
|
|
|
|
pika_display_shell_scroll (shell,
|
|
new_viewport_x - viewport_x,
|
|
new_viewport_y - viewport_y);
|
|
|
|
/* re-enable the active tool */
|
|
pika_display_shell_resume (shell);
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_scale_fit_or_fill:
|
|
* @shell: the #PikaDisplayShell
|
|
* @fill: whether to scale the image to fill the viewport,
|
|
* or fit inside the viewport
|
|
*
|
|
* A common implementation for pika_display_shell_scale_{fit_in,fill}().
|
|
**/
|
|
static void
|
|
pika_display_shell_scale_fit_or_fill (PikaDisplayShell *shell,
|
|
gboolean fill)
|
|
{
|
|
GeglRectangle bounding_box;
|
|
gdouble image_x;
|
|
gdouble image_y;
|
|
gdouble image_width;
|
|
gdouble image_height;
|
|
gdouble current_scale;
|
|
gdouble zoom_factor;
|
|
|
|
if (! pika_display_shell_get_infinite_canvas (shell))
|
|
{
|
|
PikaImage *image = pika_display_get_image (shell->display);
|
|
|
|
bounding_box.x = 0;
|
|
bounding_box.y = 0;
|
|
bounding_box.width = pika_image_get_width (image);
|
|
bounding_box.height = pika_image_get_height (image);
|
|
}
|
|
else
|
|
{
|
|
bounding_box = pika_display_shell_get_bounding_box (shell);
|
|
}
|
|
|
|
pika_display_shell_transform_bounds (shell,
|
|
bounding_box.x,
|
|
bounding_box.y,
|
|
bounding_box.x + bounding_box.width,
|
|
bounding_box.y + bounding_box.height,
|
|
&image_x,
|
|
&image_y,
|
|
&image_width,
|
|
&image_height);
|
|
|
|
image_width -= image_x;
|
|
image_height -= image_y;
|
|
|
|
current_scale = pika_zoom_model_get_factor (shell->zoom);
|
|
|
|
if (fill)
|
|
{
|
|
zoom_factor = MAX (shell->disp_width / image_width,
|
|
shell->disp_height / image_height);
|
|
}
|
|
else
|
|
{
|
|
zoom_factor = MIN (shell->disp_width / image_width,
|
|
shell->disp_height / image_height);
|
|
}
|
|
|
|
pika_display_shell_scale (shell,
|
|
PIKA_ZOOM_TO,
|
|
zoom_factor * current_scale,
|
|
PIKA_ZOOM_FOCUS_BEST_GUESS);
|
|
|
|
pika_display_shell_scroll_center_content (shell, TRUE, TRUE);
|
|
}
|
|
|
|
static gboolean
|
|
pika_display_shell_scale_image_starts_to_fit (PikaDisplayShell *shell,
|
|
gdouble new_scale,
|
|
gdouble current_scale,
|
|
gboolean *horizontally,
|
|
gboolean *vertically)
|
|
{
|
|
gboolean vertically_dummy;
|
|
gboolean horizontally_dummy;
|
|
|
|
if (! vertically) vertically = &vertically_dummy;
|
|
if (! horizontally) horizontally = &horizontally_dummy;
|
|
|
|
/* The image can only start to fit if we zoom out */
|
|
if (new_scale > current_scale ||
|
|
pika_display_shell_get_infinite_canvas (shell))
|
|
{
|
|
*vertically = FALSE;
|
|
*horizontally = FALSE;
|
|
}
|
|
else
|
|
{
|
|
gint current_scale_width;
|
|
gint current_scale_height;
|
|
gint new_scale_width;
|
|
gint new_scale_height;
|
|
|
|
pika_display_shell_scale_get_image_size_for_scale (shell,
|
|
current_scale,
|
|
¤t_scale_width,
|
|
¤t_scale_height);
|
|
|
|
pika_display_shell_scale_get_image_size_for_scale (shell,
|
|
new_scale,
|
|
&new_scale_width,
|
|
&new_scale_height);
|
|
|
|
*vertically = (current_scale_width > shell->disp_width &&
|
|
new_scale_width <= shell->disp_width);
|
|
*horizontally = (current_scale_height > shell->disp_height &&
|
|
new_scale_height <= shell->disp_height);
|
|
}
|
|
|
|
return *vertically && *horizontally;
|
|
}
|
|
|
|
static gboolean
|
|
pika_display_shell_scale_image_stops_to_fit (PikaDisplayShell *shell,
|
|
gdouble new_scale,
|
|
gdouble current_scale,
|
|
gboolean *horizontally,
|
|
gboolean *vertically)
|
|
{
|
|
return pika_display_shell_scale_image_starts_to_fit (shell,
|
|
current_scale,
|
|
new_scale,
|
|
horizontally,
|
|
vertically);
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_scale_viewport_coord_almost_centered:
|
|
* @shell:
|
|
* @x:
|
|
* @y:
|
|
* @horizontally:
|
|
* @vertically:
|
|
*
|
|
**/
|
|
static gboolean
|
|
pika_display_shell_scale_viewport_coord_almost_centered (PikaDisplayShell *shell,
|
|
gint x,
|
|
gint y,
|
|
gboolean *horizontally,
|
|
gboolean *vertically)
|
|
{
|
|
gboolean local_horizontally = FALSE;
|
|
gboolean local_vertically = FALSE;
|
|
gint center_x = shell->disp_width / 2;
|
|
gint center_y = shell->disp_height / 2;
|
|
|
|
if (! pika_display_shell_get_infinite_canvas (shell))
|
|
{
|
|
local_horizontally = (x > center_x - ALMOST_CENTERED_THRESHOLD &&
|
|
x < center_x + ALMOST_CENTERED_THRESHOLD);
|
|
|
|
local_vertically = (y > center_y - ALMOST_CENTERED_THRESHOLD &&
|
|
y < center_y + ALMOST_CENTERED_THRESHOLD);
|
|
}
|
|
|
|
if (horizontally) *horizontally = local_horizontally;
|
|
if (vertically) *vertically = local_vertically;
|
|
|
|
return local_horizontally && local_vertically;
|
|
}
|
|
|
|
static void
|
|
pika_display_shell_scale_get_image_center_viewport (PikaDisplayShell *shell,
|
|
gint *image_center_x,
|
|
gint *image_center_y)
|
|
{
|
|
gint sw, sh;
|
|
|
|
pika_display_shell_scale_get_image_size (shell, &sw, &sh);
|
|
|
|
if (image_center_x) *image_center_x = -shell->offset_x + sw / 2;
|
|
if (image_center_y) *image_center_y = -shell->offset_y + sh / 2;
|
|
}
|
|
|
|
/**
|
|
* pika_display_shell_scale_get_zoom_focus:
|
|
* @shell:
|
|
* @new_scale:
|
|
* @x:
|
|
* @y:
|
|
*
|
|
* Calculates the viewport coordinate to focus on when zooming
|
|
* independently for each axis.
|
|
**/
|
|
static void
|
|
pika_display_shell_scale_get_zoom_focus (PikaDisplayShell *shell,
|
|
gdouble new_scale,
|
|
gdouble current_scale,
|
|
gdouble *x,
|
|
gdouble *y,
|
|
PikaZoomFocus zoom_focus)
|
|
{
|
|
GtkWidget *window = GTK_WIDGET (pika_display_shell_get_window (shell));
|
|
GdkEvent *event;
|
|
gint image_center_x;
|
|
gint image_center_y;
|
|
gint other_x;
|
|
gint other_y;
|
|
|
|
/* Calculate stops-to-fit focus point */
|
|
pika_display_shell_scale_get_image_center_viewport (shell,
|
|
&image_center_x,
|
|
&image_center_y);
|
|
|
|
/* Calculate other focus point, default is the canvas center */
|
|
other_x = shell->disp_width / 2;
|
|
other_y = shell->disp_height / 2;
|
|
|
|
/* Center on the mouse position instead of the display center if
|
|
* one of the following conditions are fulfilled and pointer is
|
|
* within the canvas:
|
|
*
|
|
* (1) there's no current event (the action was triggered by an
|
|
* input controller)
|
|
* (2) the event originates from the canvas (a scroll event)
|
|
* (3) the event originates from the window (a key press event)
|
|
*
|
|
* Basically the only situation where we don't want to center on
|
|
* mouse position is if the action is being called from a menu.
|
|
*/
|
|
event = gtk_get_current_event ();
|
|
|
|
if (! event ||
|
|
gtk_get_event_widget (event) == shell->canvas ||
|
|
gtk_get_event_widget (event) == window)
|
|
{
|
|
gint canvas_pointer_x;
|
|
gint canvas_pointer_y;
|
|
|
|
if (shell->zoom_focus_point)
|
|
{
|
|
canvas_pointer_x = shell->zoom_focus_point->x;
|
|
canvas_pointer_y = shell->zoom_focus_point->y;
|
|
|
|
g_slice_free (GdkPoint, shell->zoom_focus_point);
|
|
shell->zoom_focus_point = NULL;
|
|
}
|
|
else
|
|
{
|
|
GdkDisplay *display = gtk_widget_get_display (shell->canvas);
|
|
GdkSeat *seat = gdk_display_get_default_seat (display);
|
|
|
|
gdk_window_get_device_position (gtk_widget_get_window (shell->canvas),
|
|
gdk_seat_get_pointer (seat),
|
|
&canvas_pointer_x,
|
|
&canvas_pointer_y,
|
|
NULL);
|
|
}
|
|
|
|
if (canvas_pointer_x >= 0 &&
|
|
canvas_pointer_y >= 0 &&
|
|
canvas_pointer_x < shell->disp_width &&
|
|
canvas_pointer_y < shell->disp_height)
|
|
{
|
|
other_x = canvas_pointer_x;
|
|
other_y = canvas_pointer_y;
|
|
}
|
|
}
|
|
|
|
if (zoom_focus == PIKA_ZOOM_FOCUS_RETAIN_CENTERING_ELSE_BEST_GUESS)
|
|
{
|
|
if (pika_display_shell_scale_viewport_coord_almost_centered (shell,
|
|
image_center_x,
|
|
image_center_y,
|
|
NULL,
|
|
NULL))
|
|
{
|
|
zoom_focus = PIKA_ZOOM_FOCUS_IMAGE_CENTER;
|
|
}
|
|
else
|
|
{
|
|
zoom_focus = PIKA_ZOOM_FOCUS_BEST_GUESS;
|
|
}
|
|
}
|
|
|
|
switch (zoom_focus)
|
|
{
|
|
case PIKA_ZOOM_FOCUS_POINTER:
|
|
*x = other_x;
|
|
*y = other_y;
|
|
break;
|
|
|
|
case PIKA_ZOOM_FOCUS_IMAGE_CENTER:
|
|
*x = image_center_x;
|
|
*y = image_center_y;
|
|
break;
|
|
|
|
case PIKA_ZOOM_FOCUS_BEST_GUESS:
|
|
default:
|
|
{
|
|
gboolean within_horizontally, within_vertically;
|
|
gboolean stops_horizontally, stops_vertically;
|
|
|
|
pika_display_shell_scale_image_is_within_viewport (shell,
|
|
&within_horizontally,
|
|
&within_vertically);
|
|
|
|
pika_display_shell_scale_image_stops_to_fit (shell,
|
|
new_scale,
|
|
current_scale,
|
|
&stops_horizontally,
|
|
&stops_vertically);
|
|
|
|
*x = within_horizontally && ! stops_horizontally ? image_center_x : other_x;
|
|
*y = within_vertically && ! stops_vertically ? image_center_y : other_y;
|
|
}
|
|
break;
|
|
}
|
|
}
|