1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
16  */
17 
18 #include "config.h"
19 
20 #include <math.h>
21 
22 #include <gegl.h>
23 #include <gtk/gtk.h>
24 
25 #include "libgimpbase/gimpbase.h"
26 #include "libgimpmath/gimpmath.h"
27 #include "libgimpwidgets/gimpwidgets.h"
28 
29 #include "display-types.h"
30 
31 #include "config/gimpguiconfig.h"
32 
33 #include "core/gimp.h"
34 #include "core/gimpimage.h"
35 
36 #include "gimpdisplay.h"
37 #include "gimpdisplayshell.h"
38 #include "gimpdisplayshell-expose.h"
39 #include "gimpdisplayshell-rotate.h"
40 #include "gimpdisplayshell-scale.h"
41 #include "gimpdisplayshell-scroll.h"
42 #include "gimpdisplayshell-transform.h"
43 #include "gimpimagewindow.h"
44 
45 
46 #define SCALE_TIMEOUT             2
47 #define SCALE_EPSILON             0.0001
48 #define ALMOST_CENTERED_THRESHOLD 2
49 
50 #define SCALE_EQUALS(a,b) (fabs ((a) - (b)) < SCALE_EPSILON)
51 
52 
53 /*  local function prototypes  */
54 
55 static void      gimp_display_shell_scale_get_screen_resolution
56                                                          (GimpDisplayShell *shell,
57                                                           gdouble          *xres,
58                                                           gdouble          *yres);
59 static void      gimp_display_shell_scale_get_image_size_for_scale
60                                                          (GimpDisplayShell *shell,
61                                                           gdouble           scale,
62                                                           gint             *w,
63                                                           gint             *h);
64 static void      gimp_display_shell_calculate_scale_x_and_y
65                                                          (GimpDisplayShell *shell,
66                                                           gdouble           scale,
67                                                           gdouble          *scale_x,
68                                                           gdouble          *scale_y);
69 
70 static void      gimp_display_shell_scale_to             (GimpDisplayShell *shell,
71                                                           gdouble           scale,
72                                                           gdouble           viewport_x,
73                                                           gdouble           viewport_y);
74 static void      gimp_display_shell_scale_fit_or_fill    (GimpDisplayShell *shell,
75                                                           gboolean          fill);
76 
77 static gboolean  gimp_display_shell_scale_image_starts_to_fit
78                                                          (GimpDisplayShell *shell,
79                                                           gdouble           new_scale,
80                                                           gdouble           current_scale,
81                                                           gboolean         *vertically,
82                                                           gboolean         *horizontally);
83 static gboolean  gimp_display_shell_scale_viewport_coord_almost_centered
84                                                          (GimpDisplayShell *shell,
85                                                           gint              x,
86                                                           gint              y,
87                                                           gboolean         *horizontally,
88                                                           gboolean         *vertically);
89 
90 static void      gimp_display_shell_scale_get_image_center_viewport
91                                                          (GimpDisplayShell *shell,
92                                                           gint             *image_center_x,
93                                                           gint             *image_center_y);
94 
95 
96 static void      gimp_display_shell_scale_get_zoom_focus (GimpDisplayShell *shell,
97                                                           gdouble           new_scale,
98                                                           gdouble           current_scale,
99                                                           gdouble          *x,
100                                                           gdouble          *y,
101                                                           GimpZoomFocus     zoom_focus);
102 
103 
104 /*  public functions  */
105 
106 /**
107  * gimp_display_shell_scale_revert:
108  * @shell:     the #GimpDisplayShell
109  *
110  * Reverts the display to the previously used scale. If no previous
111  * scale exist, then the call does nothing.
112  *
113  * Return value: %TRUE if the scale was reverted, otherwise %FALSE.
114  **/
115 gboolean
gimp_display_shell_scale_revert(GimpDisplayShell * shell)116 gimp_display_shell_scale_revert (GimpDisplayShell *shell)
117 {
118   g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
119 
120   /* don't bother if no scale has been set */
121   if (shell->last_scale < SCALE_EPSILON)
122     return FALSE;
123 
124   shell->last_scale_time = 0;
125 
126   gimp_display_shell_scale_by_values (shell,
127                                       shell->last_scale,
128                                       shell->last_offset_x,
129                                       shell->last_offset_y,
130                                       FALSE);   /* don't resize the window */
131 
132   return TRUE;
133 }
134 
135 /**
136  * gimp_display_shell_scale_can_revert:
137  * @shell: the #GimpDisplayShell
138  *
139  * Return value: %TRUE if a previous display scale exists, otherwise %FALSE.
140  **/
141 gboolean
gimp_display_shell_scale_can_revert(GimpDisplayShell * shell)142 gimp_display_shell_scale_can_revert (GimpDisplayShell *shell)
143 {
144   g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
145 
146   return (shell->last_scale > SCALE_EPSILON);
147 }
148 
149 /**
150  * gimp_display_shell_scale_save_revert_values:
151  * @shell:
152  *
153  * Handle the updating of the Revert Zoom variables.
154  **/
155 void
gimp_display_shell_scale_save_revert_values(GimpDisplayShell * shell)156 gimp_display_shell_scale_save_revert_values (GimpDisplayShell *shell)
157 {
158   guint now;
159 
160   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
161 
162   now = time (NULL);
163 
164   if (now - shell->last_scale_time >= SCALE_TIMEOUT)
165     {
166       shell->last_scale    = gimp_zoom_model_get_factor (shell->zoom);
167       shell->last_offset_x = shell->offset_x;
168       shell->last_offset_y = shell->offset_y;
169     }
170 
171   shell->last_scale_time = now;
172 }
173 
174 /**
175  * gimp_display_shell_scale_set_dot_for_dot:
176  * @shell:        the #GimpDisplayShell
177  * @dot_for_dot:  whether "Dot for Dot" should be enabled
178  *
179  * If @dot_for_dot is set to %TRUE then the "Dot for Dot" mode (where image and
180  * screen pixels are of the same size) is activated. Dually, the mode is
181  * disabled if @dot_for_dot is %FALSE.
182  **/
183 void
gimp_display_shell_scale_set_dot_for_dot(GimpDisplayShell * shell,gboolean dot_for_dot)184 gimp_display_shell_scale_set_dot_for_dot (GimpDisplayShell *shell,
185                                           gboolean          dot_for_dot)
186 {
187   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
188 
189   if (dot_for_dot != shell->dot_for_dot)
190     {
191       GimpDisplayConfig *config = shell->display->config;
192       gboolean           resize_window;
193 
194       /* Resize windows only in multi-window mode */
195       resize_window = (config->resize_windows_on_zoom &&
196                        ! GIMP_GUI_CONFIG (config)->single_window_mode);
197 
198       /* freeze the active tool */
199       gimp_display_shell_pause (shell);
200 
201       shell->dot_for_dot = dot_for_dot;
202 
203       gimp_display_shell_scale_update (shell);
204 
205       gimp_display_shell_scale_resize (shell, resize_window, FALSE);
206 
207       /* re-enable the active tool */
208       gimp_display_shell_resume (shell);
209     }
210 }
211 
212 /**
213  * gimp_display_shell_scale_get_image_size:
214  * @shell:
215  * @w:
216  * @h:
217  *
218  * Gets the size of the rendered image after it has been scaled.
219  *
220  **/
221 void
gimp_display_shell_scale_get_image_size(GimpDisplayShell * shell,gint * w,gint * h)222 gimp_display_shell_scale_get_image_size (GimpDisplayShell *shell,
223                                          gint             *w,
224                                          gint             *h)
225 {
226   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
227 
228   gimp_display_shell_scale_get_image_size_for_scale (shell,
229                                                      gimp_zoom_model_get_factor (shell->zoom),
230                                                      w, h);
231 }
232 
233 /**
234  * gimp_display_shell_scale_get_image_bounds:
235  * @shell:
236  * @x:
237  * @y:
238  * @w:
239  * @h:
240  *
241  * Gets the screen-space boudning box of the image, after it has
242  * been transformed (i.e., scaled, rotated, and scrolled).
243  **/
244 void
gimp_display_shell_scale_get_image_bounds(GimpDisplayShell * shell,gint * x,gint * y,gint * w,gint * h)245 gimp_display_shell_scale_get_image_bounds (GimpDisplayShell *shell,
246                                            gint             *x,
247                                            gint             *y,
248                                            gint             *w,
249                                            gint             *h)
250 {
251   GimpImage *image;
252   gdouble    x1, y1;
253   gdouble    x2, y2;
254 
255   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
256 
257   image = gimp_display_get_image (shell->display);
258 
259   gimp_display_shell_transform_bounds (shell,
260                                        0, 0,
261                                        gimp_image_get_width (image),
262                                        gimp_image_get_height (image),
263                                        &x1, &y1,
264                                        &x2, &y2);
265 
266   x1 = ceil (x1);
267   y1 = ceil (y1);
268   x2 = floor (x2);
269   y2 = floor (y2);
270 
271   if (x) *x = x1 + shell->offset_x;
272   if (y) *y = y1 + shell->offset_y;
273   if (w) *w = x2 - x1;
274   if (h) *h = y2 - y1;
275 }
276 
277 /**
278  * gimp_display_shell_scale_get_image_unrotated_bounds:
279  * @shell:
280  * @x:
281  * @y:
282  * @w:
283  * @h:
284  *
285  * Gets the screen-space boudning box of the image, after it has
286  * been scaled and scrolled, but before it has been rotated.
287  **/
288 void
gimp_display_shell_scale_get_image_unrotated_bounds(GimpDisplayShell * shell,gint * x,gint * y,gint * w,gint * h)289 gimp_display_shell_scale_get_image_unrotated_bounds (GimpDisplayShell *shell,
290                                                      gint             *x,
291                                                      gint             *y,
292                                                      gint             *w,
293                                                      gint             *h)
294 {
295   GimpImage *image;
296 
297   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
298 
299   image = gimp_display_get_image (shell->display);
300 
301   if (x) *x = -shell->offset_x;
302   if (y) *y = -shell->offset_y;
303   if (w) *w = floor (gimp_image_get_width  (image) * shell->scale_x);
304   if (h) *h = floor (gimp_image_get_height (image) * shell->scale_y);
305 }
306 
307 /**
308  * gimp_display_shell_scale_get_image_bounding_box:
309  * @shell:
310  * @x:
311  * @y:
312  * @w:
313  * @h:
314  *
315  * Gets the screen-space boudning box of the image content, after it has
316  * been transformed (i.e., scaled, rotated, and scrolled).
317  **/
318 void
gimp_display_shell_scale_get_image_bounding_box(GimpDisplayShell * shell,gint * x,gint * y,gint * w,gint * h)319 gimp_display_shell_scale_get_image_bounding_box (GimpDisplayShell *shell,
320                                                  gint             *x,
321                                                  gint             *y,
322                                                  gint             *w,
323                                                  gint             *h)
324 {
325   GeglRectangle bounding_box;
326   gdouble       x1, y1;
327   gdouble       x2, y2;
328 
329   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
330 
331   bounding_box = gimp_display_shell_get_bounding_box (shell);
332 
333   gimp_display_shell_transform_bounds (shell,
334                                        bounding_box.x,
335                                        bounding_box.y,
336                                        bounding_box.x + bounding_box.width,
337                                        bounding_box.y + bounding_box.height,
338                                        &x1, &y1,
339                                        &x2, &y2);
340 
341   if (! shell->show_all)
342     {
343       x1 = ceil  (x1);
344       y1 = ceil  (y1);
345       x2 = floor (x2);
346       y2 = floor (y2);
347     }
348   else
349     {
350       x1 = floor (x1);
351       y1 = floor (y1);
352       x2 = ceil  (x2);
353       y2 = ceil  (y2);
354     }
355 
356   if (x) *x = x1 + shell->offset_x;
357   if (y) *y = y1 + shell->offset_y;
358   if (w) *w = x2 - x1;
359   if (h) *h = y2 - y1;
360 }
361 
362 /**
363  * gimp_display_shell_scale_get_image_unrotated_bounding_box:
364  * @shell:
365  * @x:
366  * @y:
367  * @w:
368  * @h:
369  *
370  * Gets the screen-space boudning box of the image content, after it has
371  * been scaled and scrolled, but before it has been rotated.
372  **/
373 void
gimp_display_shell_scale_get_image_unrotated_bounding_box(GimpDisplayShell * shell,gint * x,gint * y,gint * w,gint * h)374 gimp_display_shell_scale_get_image_unrotated_bounding_box (GimpDisplayShell *shell,
375                                                            gint             *x,
376                                                            gint             *y,
377                                                            gint             *w,
378                                                            gint             *h)
379 {
380   GeglRectangle bounding_box;
381   gdouble       x1, y1;
382   gdouble       x2, y2;
383 
384   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
385 
386   bounding_box = gimp_display_shell_get_bounding_box (shell);
387 
388   x1 = bounding_box.x * shell->scale_x -
389        shell->offset_x;
390   y1 = bounding_box.y * shell->scale_y -
391        shell->offset_y;
392 
393   x2 = (bounding_box.x + bounding_box.width)  * shell->scale_x -
394        shell->offset_x;
395   y2 = (bounding_box.y + bounding_box.height) * shell->scale_y -
396        shell->offset_y;
397 
398   if (! shell->show_all)
399     {
400       x1 = ceil  (x1);
401       y1 = ceil  (y1);
402       x2 = floor (x2);
403       y2 = floor (y2);
404     }
405   else
406     {
407       x1 = floor (x1);
408       y1 = floor (y1);
409       x2 = ceil  (x2);
410       y2 = ceil  (y2);
411     }
412 
413   if (x) *x = x1;
414   if (y) *y = y1;
415   if (w) *w = x2 - x1;
416   if (h) *h = y2 - y1;
417 }
418 
419 /**
420  * gimp_display_shell_scale_image_is_within_viewport:
421  * @shell:
422  *
423  * Returns: %TRUE if the (scaled) image is smaller than and within the
424  *          viewport.
425  **/
426 gboolean
gimp_display_shell_scale_image_is_within_viewport(GimpDisplayShell * shell,gboolean * horizontally,gboolean * vertically)427 gimp_display_shell_scale_image_is_within_viewport (GimpDisplayShell *shell,
428                                                    gboolean         *horizontally,
429                                                    gboolean         *vertically)
430 {
431   gboolean horizontally_dummy, vertically_dummy;
432 
433   g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
434 
435   if (! horizontally) horizontally = &horizontally_dummy;
436   if (! vertically)   vertically   = &vertically_dummy;
437 
438   if (! gimp_display_shell_get_infinite_canvas (shell))
439     {
440       gint sx, sy;
441       gint sw, sh;
442 
443       gimp_display_shell_scale_get_image_bounding_box (shell,
444                                                        &sx, &sy, &sw, &sh);
445 
446       sx -= shell->offset_x;
447       sy -= shell->offset_y;
448 
449       *horizontally = sx >= 0 && sx + sw <= shell->disp_width;
450       *vertically   = sy >= 0 && sy + sh <= shell->disp_height;
451     }
452   else
453     {
454       *horizontally = FALSE;
455       *vertically   = FALSE;
456     }
457 
458   return *vertically && *horizontally;
459 }
460 
461 /* We used to calculate the scale factor in the SCALEFACTOR_X() and
462  * SCALEFACTOR_Y() macros. But since these are rather frequently
463  * called and the values rarely change, we now store them in the
464  * shell and call this function whenever they need to be recalculated.
465  */
466 void
gimp_display_shell_scale_update(GimpDisplayShell * shell)467 gimp_display_shell_scale_update (GimpDisplayShell *shell)
468 {
469   GimpImage *image;
470 
471   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
472 
473   image = gimp_display_get_image (shell->display);
474 
475   if (image)
476     {
477       gimp_display_shell_calculate_scale_x_and_y (shell,
478                                                   gimp_zoom_model_get_factor (shell->zoom),
479                                                   &shell->scale_x,
480                                                   &shell->scale_y);
481     }
482   else
483     {
484       shell->scale_x = 1.0;
485       shell->scale_y = 1.0;
486     }
487 }
488 
489 /**
490  * gimp_display_shell_scale:
491  * @shell:     the #GimpDisplayShell
492  * @zoom_type: whether to zoom in, out or to a specific scale
493  * @scale:     ignored unless @zoom_type == %GIMP_ZOOM_TO
494  *
495  * This function figures out the context of the zoom and behaves
496  * appropriately thereafter.
497  *
498  **/
499 void
gimp_display_shell_scale(GimpDisplayShell * shell,GimpZoomType zoom_type,gdouble new_scale,GimpZoomFocus zoom_focus)500 gimp_display_shell_scale (GimpDisplayShell *shell,
501                           GimpZoomType      zoom_type,
502                           gdouble           new_scale,
503                           GimpZoomFocus     zoom_focus)
504 {
505   GimpDisplayConfig *config;
506   gdouble            current_scale;
507   gboolean           resize_window;
508 
509   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
510   g_return_if_fail (shell->canvas != NULL);
511 
512   current_scale = gimp_zoom_model_get_factor (shell->zoom);
513 
514   if (zoom_type != GIMP_ZOOM_TO)
515     new_scale = gimp_zoom_model_zoom_step (zoom_type, current_scale);
516 
517   if (SCALE_EQUALS (new_scale, current_scale))
518     return;
519 
520   config = shell->display->config;
521 
522   /* Resize windows only in multi-window mode */
523   resize_window = (config->resize_windows_on_zoom &&
524                    ! GIMP_GUI_CONFIG (config)->single_window_mode);
525 
526   if (resize_window)
527     {
528       /* If the window is resized on zoom, simply do the zoom and get
529        * things rolling
530        */
531       gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO, new_scale);
532 
533       gimp_display_shell_scale_resize (shell, TRUE, FALSE);
534     }
535   else
536     {
537       gdouble  x, y;
538       gint     image_center_x;
539       gint     image_center_y;
540 
541       gimp_display_shell_scale_get_zoom_focus (shell,
542                                                new_scale,
543                                                current_scale,
544                                                &x,
545                                                &y,
546                                                zoom_focus);
547       gimp_display_shell_scale_get_image_center_viewport (shell,
548                                                           &image_center_x,
549                                                           &image_center_y);
550 
551       gimp_display_shell_scale_to (shell, new_scale, x, y);
552 
553       /* skip centering magic if pointer focus was requested */
554       if (zoom_focus != GIMP_ZOOM_FOCUS_POINTER)
555         {
556           gboolean starts_fitting_horiz;
557           gboolean starts_fitting_vert;
558           gboolean zoom_focus_almost_centered_horiz;
559           gboolean zoom_focus_almost_centered_vert;
560           gboolean image_center_almost_centered_horiz;
561           gboolean image_center_almost_centered_vert;
562 
563           /* If an image axis started to fit due to zooming out or if
564            * the focus point is as good as in the center, center on
565            * that axis
566            */
567           gimp_display_shell_scale_image_starts_to_fit (shell,
568                                                         new_scale,
569                                                         current_scale,
570                                                         &starts_fitting_horiz,
571                                                         &starts_fitting_vert);
572 
573           gimp_display_shell_scale_viewport_coord_almost_centered (shell,
574                                                                    x,
575                                                                    y,
576                                                                    &zoom_focus_almost_centered_horiz,
577                                                                    &zoom_focus_almost_centered_vert);
578           gimp_display_shell_scale_viewport_coord_almost_centered (shell,
579                                                                    image_center_x,
580                                                                    image_center_y,
581                                                                    &image_center_almost_centered_horiz,
582                                                                    &image_center_almost_centered_vert);
583 
584           gimp_display_shell_scroll_center_image (shell,
585                                                   starts_fitting_horiz ||
586                                                   (zoom_focus_almost_centered_horiz &&
587                                                    image_center_almost_centered_horiz),
588                                                   starts_fitting_vert ||
589                                                   (zoom_focus_almost_centered_vert &&
590                                                    image_center_almost_centered_vert));
591         }
592     }
593 }
594 
595 /**
596  * gimp_display_shell_scale_to_rectangle:
597  * @shell:         the #GimpDisplayShell
598  * @zoom_type:     whether to zoom in or out
599  * @x:             retangle's x in image coordinates
600  * @y:             retangle's y in image coordinates
601  * @width:         retangle's width in image coordinates
602  * @height:        retangle's height in image coordinates
603  * @resize_window: whether the display window should be resized
604  *
605  * Scales and scrolls to a specific image rectangle
606  **/
607 void
gimp_display_shell_scale_to_rectangle(GimpDisplayShell * shell,GimpZoomType zoom_type,gdouble x,gdouble y,gdouble width,gdouble height,gboolean resize_window)608 gimp_display_shell_scale_to_rectangle (GimpDisplayShell *shell,
609                                        GimpZoomType      zoom_type,
610                                        gdouble           x,
611                                        gdouble           y,
612                                        gdouble           width,
613                                        gdouble           height,
614                                        gboolean          resize_window)
615 {
616   gdouble current_scale;
617   gdouble new_scale;
618   gdouble factor   = 1.0;
619   gint    offset_x = 0;
620   gint    offset_y = 0;
621 
622   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
623 
624   gimp_display_shell_transform_bounds (shell,
625                                        x, y,
626                                        x + width, y + height,
627                                        &x, &y,
628                                        &width, &height);
629 
630   /* Convert scrolled (x1, y1, x2, y2) to unscrolled (x, y, width, height). */
631   width  -= x;
632   height -= y;
633   x      += shell->offset_x;
634   y      += shell->offset_y;
635 
636   width  = MAX (1.0, width);
637   height = MAX (1.0, height);
638 
639   current_scale = gimp_zoom_model_get_factor (shell->zoom);
640 
641   switch (zoom_type)
642     {
643     case GIMP_ZOOM_IN:
644       factor = MIN ((shell->disp_width  / width),
645                     (shell->disp_height / height));
646       break;
647 
648     case GIMP_ZOOM_OUT:
649       factor = MAX ((width  / shell->disp_width),
650                     (height / shell->disp_height));
651       break;
652 
653     default:
654       g_return_if_reached ();
655       break;
656     }
657 
658   new_scale = current_scale * factor;
659 
660   switch (zoom_type)
661     {
662     case GIMP_ZOOM_IN:
663       /*  move the center of the rectangle to the center of the
664        *  viewport:
665        *
666        *  new_offset = center of rectangle in new scale screen coords
667        *               including offset
668        *               -
669        *               center of viewport in screen coords without
670        *               offset
671        */
672       offset_x = RINT (factor * (x + width  / 2.0) - (shell->disp_width  / 2));
673       offset_y = RINT (factor * (y + height / 2.0) - (shell->disp_height / 2));
674       break;
675 
676     case GIMP_ZOOM_OUT:
677       /*  move the center of the viewport to the center of the
678        *  rectangle:
679        *
680        *  new_offset = center of viewport in new scale screen coords
681        *               including offset
682        *               -
683        *               center of rectangle in screen coords without
684        *               offset
685        */
686       offset_x = RINT (factor * (shell->offset_x + shell->disp_width  / 2) -
687                        ((x + width  / 2.0) - shell->offset_x));
688 
689       offset_y = RINT (factor * (shell->offset_y + shell->disp_height / 2) -
690                        ((y + height / 2.0) - shell->offset_y));
691       break;
692 
693     default:
694       break;
695     }
696 
697   if (new_scale != current_scale   ||
698       offset_x  != shell->offset_x ||
699       offset_y  != shell->offset_y)
700     {
701       gimp_display_shell_scale_by_values (shell,
702                                           new_scale,
703                                           offset_x, offset_y,
704                                           resize_window);
705     }
706 }
707 
708 /**
709  * gimp_display_shell_scale_fit_in:
710  * @shell: the #GimpDisplayShell
711  *
712  * Sets the scale such that the entire image precisely fits in the
713  * display area.
714  **/
715 void
gimp_display_shell_scale_fit_in(GimpDisplayShell * shell)716 gimp_display_shell_scale_fit_in (GimpDisplayShell *shell)
717 {
718   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
719 
720   gimp_display_shell_scale_fit_or_fill (shell,
721                                         /* fill = */ FALSE);
722  }
723 
724 /**
725  * gimp_display_shell_scale_fill:
726  * @shell: the #GimpDisplayShell
727  *
728  * Sets the scale such that the entire display area is precisely
729  * filled by the image.
730  **/
731 void
gimp_display_shell_scale_fill(GimpDisplayShell * shell)732 gimp_display_shell_scale_fill (GimpDisplayShell *shell)
733 {
734   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
735 
736   gimp_display_shell_scale_fit_or_fill (shell,
737                                         /* fill = */ TRUE);
738 }
739 
740 /**
741  * gimp_display_shell_scale_by_values:
742  * @shell:         the #GimpDisplayShell
743  * @scale:         the new scale
744  * @offset_x:      the new X offset
745  * @offset_y:      the new Y offset
746  * @resize_window: whether the display window should be resized
747  *
748  * Directly sets the image scale and image offsets used by the display. If
749  * @resize_window is %TRUE then the display window is resized to better
750  * accommodate the image, see gimp_display_shell_shrink_wrap().
751  **/
752 void
gimp_display_shell_scale_by_values(GimpDisplayShell * shell,gdouble scale,gint offset_x,gint offset_y,gboolean resize_window)753 gimp_display_shell_scale_by_values (GimpDisplayShell *shell,
754                                     gdouble           scale,
755                                     gint              offset_x,
756                                     gint              offset_y,
757                                     gboolean          resize_window)
758 {
759   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
760 
761   /*  Abort early if the values are all setup already. We don't
762    *  want to inadvertently resize the window (bug #164281).
763    */
764   if (SCALE_EQUALS (gimp_zoom_model_get_factor (shell->zoom), scale) &&
765       shell->offset_x == offset_x &&
766       shell->offset_y == offset_y)
767     return;
768 
769   gimp_display_shell_scale_save_revert_values (shell);
770 
771   /* freeze the active tool */
772   gimp_display_shell_pause (shell);
773 
774   gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO, scale);
775 
776   shell->offset_x = offset_x;
777   shell->offset_y = offset_y;
778 
779   gimp_display_shell_rotate_update_transform (shell);
780 
781   gimp_display_shell_scale_resize (shell, resize_window, FALSE);
782 
783   /* re-enable the active tool */
784   gimp_display_shell_resume (shell);
785 }
786 
787 void
gimp_display_shell_scale_drag(GimpDisplayShell * shell,gdouble start_x,gdouble start_y,gdouble delta_x,gdouble delta_y)788 gimp_display_shell_scale_drag (GimpDisplayShell *shell,
789                                gdouble           start_x,
790                                gdouble           start_y,
791                                gdouble           delta_x,
792                                gdouble           delta_y)
793 {
794   gdouble scale;
795 
796   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
797 
798   scale = gimp_zoom_model_get_factor (shell->zoom);
799 
800   gimp_display_shell_push_zoom_focus_pointer_pos (shell, start_x, start_y);
801 
802   if (delta_y > 0)
803     {
804       gimp_display_shell_scale (shell,
805                                 GIMP_ZOOM_TO,
806                                 scale * 1.1,
807                                 GIMP_ZOOM_FOCUS_POINTER);
808     }
809   else if (delta_y < 0)
810     {
811       gimp_display_shell_scale (shell,
812                                 GIMP_ZOOM_TO,
813                                 scale * 0.9,
814                                 GIMP_ZOOM_FOCUS_POINTER);
815     }
816 }
817 
818 /**
819  * gimp_display_shell_scale_shrink_wrap:
820  * @shell: the #GimpDisplayShell
821  *
822  * Convenience function with the same functionality as
823  * gimp_display_shell_scale_resize(@shell, TRUE, grow_only).
824  **/
825 void
gimp_display_shell_scale_shrink_wrap(GimpDisplayShell * shell,gboolean grow_only)826 gimp_display_shell_scale_shrink_wrap (GimpDisplayShell *shell,
827                                       gboolean          grow_only)
828 {
829   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
830 
831   gimp_display_shell_scale_resize (shell, TRUE, grow_only);
832 }
833 
834 /**
835  * gimp_display_shell_scale_resize:
836  * @shell:          the #GimpDisplayShell
837  * @resize_window:  whether the display window should be resized
838  * @grow_only:      whether shrinking of the window is allowed or not
839  *
840  * Function commonly called after a change in display scale to make the changes
841  * visible to the user. If @resize_window is %TRUE then the display window is
842  * resized to accommodate the display image as per
843  * gimp_display_shell_shrink_wrap().
844  **/
845 void
gimp_display_shell_scale_resize(GimpDisplayShell * shell,gboolean resize_window,gboolean grow_only)846 gimp_display_shell_scale_resize (GimpDisplayShell *shell,
847                                  gboolean          resize_window,
848                                  gboolean          grow_only)
849 {
850   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
851 
852   /* freeze the active tool */
853   gimp_display_shell_pause (shell);
854 
855   if (resize_window)
856     {
857       GimpImageWindow *window = gimp_display_shell_get_window (shell);
858 
859       if (window && gimp_image_window_get_active_shell (window) == shell)
860         {
861           gimp_image_window_shrink_wrap (window, grow_only);
862         }
863     }
864 
865   gimp_display_shell_scroll_clamp_and_update (shell);
866   gimp_display_shell_scaled (shell);
867 
868   gimp_display_shell_expose_full (shell);
869 
870   /* re-enable the active tool */
871   gimp_display_shell_resume (shell);
872 }
873 
874 void
gimp_display_shell_set_initial_scale(GimpDisplayShell * shell,gdouble scale,gint * display_width,gint * display_height)875 gimp_display_shell_set_initial_scale (GimpDisplayShell *shell,
876                                       gdouble           scale,
877                                       gint             *display_width,
878                                       gint             *display_height)
879 {
880   GimpImage *image;
881   GdkScreen *screen;
882   gint       image_width;
883   gint       image_height;
884   gint       shell_width;
885   gint       shell_height;
886   gint       screen_width;
887   gint       screen_height;
888 
889   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
890 
891   image = gimp_display_get_image (shell->display);
892 
893   screen = gtk_widget_get_screen (GTK_WIDGET (shell));
894 
895   image_width  = gimp_image_get_width  (image);
896   image_height = gimp_image_get_height (image);
897 
898   screen_width  = gdk_screen_get_width (screen)  * 0.75;
899   screen_height = gdk_screen_get_height (screen) * 0.75;
900 
901   /* We need to zoom before we use SCALE[XY] */
902   gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO, scale);
903 
904   shell_width  = SCALEX (shell, image_width);
905   shell_height = SCALEY (shell, image_height);
906 
907   if (shell->display->config->initial_zoom_to_fit)
908     {
909       /*  Limit to the size of the screen...  */
910       if (shell_width > screen_width || shell_height > screen_height)
911         {
912           gdouble new_scale;
913           gdouble current = gimp_zoom_model_get_factor (shell->zoom);
914 
915           new_scale = current * MIN (((gdouble) screen_height) / shell_height,
916                                      ((gdouble) screen_width)  / shell_width);
917 
918           new_scale = gimp_zoom_model_zoom_step (GIMP_ZOOM_OUT, new_scale);
919 
920           /*  Since zooming out might skip a zoom step we zoom in
921            *  again and test if we are small enough.
922            */
923           gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO,
924                                 gimp_zoom_model_zoom_step (GIMP_ZOOM_IN,
925                                                            new_scale));
926 
927           if (SCALEX (shell, image_width) > screen_width ||
928               SCALEY (shell, image_height) > screen_height)
929             gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO, new_scale);
930 
931           shell_width  = SCALEX (shell, image_width);
932           shell_height = SCALEY (shell, image_height);
933         }
934     }
935   else
936     {
937       /*  Set up size like above, but do not zoom to fit. Useful when
938        *  working on large images.
939        */
940       if (shell_width > screen_width)
941         shell_width = screen_width;
942 
943       if (shell_height > screen_height)
944         shell_height = screen_height;
945     }
946 
947   if (display_width)
948     *display_width = shell_width;
949 
950   if (display_height)
951     *display_height = shell_height;
952 }
953 
954 /**
955  * gimp_display_shell_get_rotated_scale:
956  * @shell:   the #GimpDisplayShell
957  * @scale_x: horizontal scale output
958  * @scale_y: vertical scale output
959  *
960  * Returns the screen space horizontal and vertical scaling
961  * factors, taking rotation into account.
962  **/
963 void
gimp_display_shell_get_rotated_scale(GimpDisplayShell * shell,gdouble * scale_x,gdouble * scale_y)964 gimp_display_shell_get_rotated_scale (GimpDisplayShell *shell,
965                                       gdouble          *scale_x,
966                                       gdouble          *scale_y)
967 {
968   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
969 
970   if (shell->rotate_angle == 0.0 || shell->scale_x == shell->scale_y)
971     {
972       if (scale_x) *scale_x = shell->scale_x;
973       if (scale_y) *scale_y = shell->scale_y;
974     }
975   else
976     {
977       gdouble a     = G_PI * shell->rotate_angle / 180.0;
978       gdouble cos_a = cos (a);
979       gdouble sin_a = sin (a);
980 
981       if (scale_x) *scale_x = 1.0 / sqrt (SQR (cos_a / shell->scale_x) +
982                                           SQR (sin_a / shell->scale_y));
983 
984       if (scale_y) *scale_y = 1.0 / sqrt (SQR (cos_a / shell->scale_y) +
985                                           SQR (sin_a / shell->scale_x));
986     }
987 }
988 
989 /**
990  * gimp_display_shell_push_zoom_focus_pointer_pos:
991  * @shell:
992  * @x:
993  * @y:
994  *
995  * When the zoom focus mechanism asks for the pointer the next time,
996  * use @x and @y.
997  **/
998 void
gimp_display_shell_push_zoom_focus_pointer_pos(GimpDisplayShell * shell,gint x,gint y)999 gimp_display_shell_push_zoom_focus_pointer_pos (GimpDisplayShell *shell,
1000                                                 gint              x,
1001                                                 gint              y)
1002 {
1003   GdkPoint *point = g_slice_new (GdkPoint);
1004   point->x = x;
1005   point->y = y;
1006 
1007   g_queue_push_head (shell->zoom_focus_pointer_queue,
1008                      point);
1009 }
1010 
1011 
1012 /*  private functions   */
1013 
1014 static void
gimp_display_shell_scale_get_screen_resolution(GimpDisplayShell * shell,gdouble * xres,gdouble * yres)1015 gimp_display_shell_scale_get_screen_resolution (GimpDisplayShell *shell,
1016                                                 gdouble          *xres,
1017                                                 gdouble          *yres)
1018 {
1019   gdouble x, y;
1020 
1021   if (shell->dot_for_dot)
1022     {
1023       gimp_image_get_resolution (gimp_display_get_image (shell->display),
1024                                  &x, &y);
1025     }
1026   else
1027     {
1028       x = shell->monitor_xres;
1029       y = shell->monitor_yres;
1030     }
1031 
1032   if (xres) *xres = x;
1033   if (yres) *yres = y;
1034 }
1035 
1036 /**
1037  * gimp_display_shell_scale_get_image_size_for_scale:
1038  * @shell:
1039  * @scale:
1040  * @w:
1041  * @h:
1042  *
1043  **/
1044 static void
gimp_display_shell_scale_get_image_size_for_scale(GimpDisplayShell * shell,gdouble scale,gint * w,gint * h)1045 gimp_display_shell_scale_get_image_size_for_scale (GimpDisplayShell *shell,
1046                                                    gdouble           scale,
1047                                                    gint             *w,
1048                                                    gint             *h)
1049 {
1050   GimpImage *image = gimp_display_get_image (shell->display);
1051   gdouble    scale_x;
1052   gdouble    scale_y;
1053 
1054   gimp_display_shell_calculate_scale_x_and_y (shell, scale, &scale_x, &scale_y);
1055 
1056   if (w) *w = scale_x * gimp_image_get_width  (image);
1057   if (h) *h = scale_y * gimp_image_get_height (image);
1058 }
1059 
1060 /**
1061  * gimp_display_shell_calculate_scale_x_and_y:
1062  * @shell:
1063  * @scale:
1064  * @scale_x:
1065  * @scale_y:
1066  *
1067  **/
1068 static void
gimp_display_shell_calculate_scale_x_and_y(GimpDisplayShell * shell,gdouble scale,gdouble * scale_x,gdouble * scale_y)1069 gimp_display_shell_calculate_scale_x_and_y (GimpDisplayShell *shell,
1070                                             gdouble           scale,
1071                                             gdouble          *scale_x,
1072                                             gdouble          *scale_y)
1073 {
1074   GimpImage *image = gimp_display_get_image (shell->display);
1075   gdouble    xres;
1076   gdouble    yres;
1077   gdouble    screen_xres;
1078   gdouble    screen_yres;
1079 
1080   gimp_image_get_resolution (image, &xres, &yres);
1081   gimp_display_shell_scale_get_screen_resolution (shell,
1082                                                   &screen_xres, &screen_yres);
1083 
1084   if (scale_x) *scale_x = scale * screen_xres / xres;
1085   if (scale_y) *scale_y = scale * screen_yres / yres;
1086 }
1087 
1088 /**
1089  * gimp_display_shell_scale_to:
1090  * @shell:
1091  * @scale:
1092  * @viewport_x:
1093  * @viewport_y:
1094  *
1095  * Zooms. The display offsets are adjusted so that the point specified
1096  * by @x and @y doesn't change it's position on screen.
1097  **/
1098 static void
gimp_display_shell_scale_to(GimpDisplayShell * shell,gdouble scale,gdouble viewport_x,gdouble viewport_y)1099 gimp_display_shell_scale_to (GimpDisplayShell *shell,
1100                              gdouble           scale,
1101                              gdouble           viewport_x,
1102                              gdouble           viewport_y)
1103 {
1104   gdouble image_x, image_y;
1105   gdouble new_viewport_x, new_viewport_y;
1106 
1107   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
1108 
1109   if (! shell->display)
1110     return;
1111 
1112   /* freeze the active tool */
1113   gimp_display_shell_pause (shell);
1114 
1115   gimp_display_shell_untransform_xy_f (shell,
1116                                        viewport_x,
1117                                        viewport_y,
1118                                        &image_x,
1119                                        &image_y);
1120 
1121   /* Note that we never come here if we need to resize_windows_on_zoom
1122    */
1123   gimp_display_shell_scale_by_values (shell,
1124                                       scale,
1125                                       shell->offset_x,
1126                                       shell->offset_y,
1127                                       FALSE);
1128 
1129   gimp_display_shell_transform_xy_f (shell,
1130                                      image_x,
1131                                      image_y,
1132                                      &new_viewport_x,
1133                                      &new_viewport_y);
1134 
1135   gimp_display_shell_scroll (shell,
1136                              new_viewport_x - viewport_x,
1137                              new_viewport_y - viewport_y);
1138 
1139   /* re-enable the active tool */
1140   gimp_display_shell_resume (shell);
1141 }
1142 
1143 /**
1144  * gimp_display_shell_scale_fit_or_fill:
1145  * @shell: the #GimpDisplayShell
1146  * @fill:  whether to scale the image to fill the viewport,
1147  *         or fit inside the viewport
1148  *
1149  * A common implementation for gimp_display_shell_scale_{fit_in,fill}().
1150  **/
1151 static void
gimp_display_shell_scale_fit_or_fill(GimpDisplayShell * shell,gboolean fill)1152 gimp_display_shell_scale_fit_or_fill (GimpDisplayShell *shell,
1153                                       gboolean          fill)
1154 {
1155   GeglRectangle bounding_box;
1156   gdouble       image_x;
1157   gdouble       image_y;
1158   gdouble       image_width;
1159   gdouble       image_height;
1160   gdouble       current_scale;
1161   gdouble       zoom_factor;
1162 
1163   if (! gimp_display_shell_get_infinite_canvas (shell))
1164     {
1165       GimpImage *image = gimp_display_get_image (shell->display);
1166 
1167       bounding_box.x      = 0;
1168       bounding_box.y      = 0;
1169       bounding_box.width  = gimp_image_get_width  (image);
1170       bounding_box.height = gimp_image_get_height (image);
1171     }
1172   else
1173     {
1174       bounding_box = gimp_display_shell_get_bounding_box (shell);
1175     }
1176 
1177   gimp_display_shell_transform_bounds (shell,
1178                                        bounding_box.x,
1179                                        bounding_box.y,
1180                                        bounding_box.x + bounding_box.width,
1181                                        bounding_box.y + bounding_box.height,
1182                                        &image_x,
1183                                        &image_y,
1184                                        &image_width,
1185                                        &image_height);
1186 
1187   image_width  -= image_x;
1188   image_height -= image_y;
1189 
1190   current_scale = gimp_zoom_model_get_factor (shell->zoom);
1191 
1192   if (fill)
1193     {
1194       zoom_factor = MAX (shell->disp_width  / image_width,
1195                          shell->disp_height / image_height);
1196     }
1197   else
1198     {
1199       zoom_factor = MIN (shell->disp_width  / image_width,
1200                          shell->disp_height / image_height);
1201     }
1202 
1203   gimp_display_shell_scale (shell,
1204                             GIMP_ZOOM_TO,
1205                             zoom_factor * current_scale,
1206                             GIMP_ZOOM_FOCUS_BEST_GUESS);
1207 
1208   gimp_display_shell_scroll_center_content (shell, TRUE, TRUE);
1209 }
1210 
1211 static gboolean
gimp_display_shell_scale_image_starts_to_fit(GimpDisplayShell * shell,gdouble new_scale,gdouble current_scale,gboolean * vertically,gboolean * horizontally)1212 gimp_display_shell_scale_image_starts_to_fit (GimpDisplayShell *shell,
1213                                               gdouble           new_scale,
1214                                               gdouble           current_scale,
1215                                               gboolean         *vertically,
1216                                               gboolean         *horizontally)
1217 {
1218   gboolean vertically_dummy;
1219   gboolean horizontally_dummy;
1220 
1221   if (! vertically)   vertically   = &vertically_dummy;
1222   if (! horizontally) horizontally = &horizontally_dummy;
1223 
1224   /* The image can only start to fit if we zoom out */
1225   if (new_scale > current_scale ||
1226       gimp_display_shell_get_infinite_canvas (shell))
1227     {
1228       *vertically   = FALSE;
1229       *horizontally = FALSE;
1230     }
1231   else
1232     {
1233       gint current_scale_width;
1234       gint current_scale_height;
1235       gint new_scale_width;
1236       gint new_scale_height;
1237 
1238       gimp_display_shell_scale_get_image_size_for_scale (shell,
1239                                                          current_scale,
1240                                                          &current_scale_width,
1241                                                          &current_scale_height);
1242 
1243       gimp_display_shell_scale_get_image_size_for_scale (shell,
1244                                                          new_scale,
1245                                                          &new_scale_width,
1246                                                          &new_scale_height);
1247 
1248       *vertically   = (current_scale_width  >  shell->disp_width &&
1249                        new_scale_width      <= shell->disp_width);
1250       *horizontally = (current_scale_height >  shell->disp_height &&
1251                        new_scale_height     <= shell->disp_height);
1252     }
1253 
1254   return *vertically && *horizontally;
1255 }
1256 
1257 static gboolean
gimp_display_shell_scale_image_stops_to_fit(GimpDisplayShell * shell,gdouble new_scale,gdouble current_scale,gboolean * vertically,gboolean * horizontally)1258 gimp_display_shell_scale_image_stops_to_fit (GimpDisplayShell *shell,
1259                                              gdouble           new_scale,
1260                                              gdouble           current_scale,
1261                                              gboolean         *vertically,
1262                                              gboolean         *horizontally)
1263 {
1264   return gimp_display_shell_scale_image_starts_to_fit (shell,
1265                                                        current_scale,
1266                                                        new_scale,
1267                                                        vertically,
1268                                                        horizontally);
1269 }
1270 
1271 /**
1272  * gimp_display_shell_scale_viewport_coord_almost_centered:
1273  * @shell:
1274  * @x:
1275  * @y:
1276  * @horizontally:
1277  * @vertically:
1278  *
1279  **/
1280 static gboolean
gimp_display_shell_scale_viewport_coord_almost_centered(GimpDisplayShell * shell,gint x,gint y,gboolean * horizontally,gboolean * vertically)1281 gimp_display_shell_scale_viewport_coord_almost_centered (GimpDisplayShell *shell,
1282                                                          gint              x,
1283                                                          gint              y,
1284                                                          gboolean         *horizontally,
1285                                                          gboolean         *vertically)
1286 {
1287   gboolean local_horizontally = FALSE;
1288   gboolean local_vertically   = FALSE;
1289   gint     center_x           = shell->disp_width  / 2;
1290   gint     center_y           = shell->disp_height / 2;
1291 
1292   if (! gimp_display_shell_get_infinite_canvas (shell))
1293     {
1294       local_horizontally = (x > center_x - ALMOST_CENTERED_THRESHOLD &&
1295                             x < center_x + ALMOST_CENTERED_THRESHOLD);
1296 
1297       local_vertically   = (y > center_y - ALMOST_CENTERED_THRESHOLD &&
1298                             y < center_y + ALMOST_CENTERED_THRESHOLD);
1299     }
1300 
1301   if (horizontally) *horizontally = local_horizontally;
1302   if (vertically)   *vertically   = local_vertically;
1303 
1304   return local_horizontally && local_vertically;
1305 }
1306 
1307 static void
gimp_display_shell_scale_get_image_center_viewport(GimpDisplayShell * shell,gint * image_center_x,gint * image_center_y)1308 gimp_display_shell_scale_get_image_center_viewport (GimpDisplayShell *shell,
1309                                                     gint             *image_center_x,
1310                                                     gint             *image_center_y)
1311 {
1312   gint sw, sh;
1313 
1314   gimp_display_shell_scale_get_image_size (shell, &sw, &sh);
1315 
1316   if (image_center_x) *image_center_x = -shell->offset_x + sw / 2;
1317   if (image_center_y) *image_center_y = -shell->offset_y + sh / 2;
1318 }
1319 
1320 /**
1321  * gimp_display_shell_scale_get_zoom_focus:
1322  * @shell:
1323  * @new_scale:
1324  * @x:
1325  * @y:
1326  *
1327  * Calculates the viewport coordinate to focus on when zooming
1328  * independently for each axis.
1329  **/
1330 static void
gimp_display_shell_scale_get_zoom_focus(GimpDisplayShell * shell,gdouble new_scale,gdouble current_scale,gdouble * x,gdouble * y,GimpZoomFocus zoom_focus)1331 gimp_display_shell_scale_get_zoom_focus (GimpDisplayShell *shell,
1332                                          gdouble           new_scale,
1333                                          gdouble           current_scale,
1334                                          gdouble          *x,
1335                                          gdouble          *y,
1336                                          GimpZoomFocus     zoom_focus)
1337 {
1338   GtkWidget *window = GTK_WIDGET (gimp_display_shell_get_window (shell));
1339   GdkEvent  *event;
1340   gint       image_center_x;
1341   gint       image_center_y;
1342   gint       other_x;
1343   gint       other_y;
1344 
1345   /* Calculate stops-to-fit focus point */
1346   gimp_display_shell_scale_get_image_center_viewport (shell,
1347                                                       &image_center_x,
1348                                                       &image_center_y);
1349 
1350   /* Calculate other focus point, default is the canvas center */
1351   other_x = shell->disp_width  / 2;
1352   other_y = shell->disp_height / 2;
1353 
1354   /*  Center on the mouse position instead of the display center if
1355    *  one of the following conditions are fulfilled and pointer is
1356    *  within the canvas:
1357    *
1358    *   (1) there's no current event (the action was triggered by an
1359    *       input controller)
1360    *   (2) the event originates from the canvas (a scroll event)
1361    *   (3) the event originates from the window (a key press event)
1362    *
1363    *  Basically the only situation where we don't want to center on
1364    *  mouse position is if the action is being called from a menu.
1365    */
1366   event = gtk_get_current_event ();
1367 
1368   if (! event ||
1369       gtk_get_event_widget (event) == shell->canvas ||
1370       gtk_get_event_widget (event) == window)
1371     {
1372       GdkPoint *point = g_queue_pop_head (shell->zoom_focus_pointer_queue);
1373       gint      canvas_pointer_x;
1374       gint      canvas_pointer_y;
1375 
1376       if (point)
1377         {
1378           canvas_pointer_x = point->x;
1379           canvas_pointer_y = point->y;
1380 
1381           g_slice_free (GdkPoint, point);
1382         }
1383       else
1384         {
1385           gtk_widget_get_pointer (shell->canvas,
1386                                   &canvas_pointer_x,
1387                                   &canvas_pointer_y);
1388         }
1389 
1390       if (canvas_pointer_x >= 0 &&
1391           canvas_pointer_y >= 0 &&
1392           canvas_pointer_x <  shell->disp_width &&
1393           canvas_pointer_y <  shell->disp_height)
1394         {
1395           other_x = canvas_pointer_x;
1396           other_y = canvas_pointer_y;
1397         }
1398     }
1399 
1400   if (zoom_focus == GIMP_ZOOM_FOCUS_RETAIN_CENTERING_ELSE_BEST_GUESS)
1401     {
1402       if (gimp_display_shell_scale_viewport_coord_almost_centered (shell,
1403                                                                    image_center_x,
1404                                                                    image_center_y,
1405                                                                    NULL,
1406                                                                    NULL))
1407         {
1408           zoom_focus = GIMP_ZOOM_FOCUS_IMAGE_CENTER;
1409         }
1410       else
1411         {
1412           zoom_focus = GIMP_ZOOM_FOCUS_BEST_GUESS;
1413         }
1414     }
1415 
1416   switch (zoom_focus)
1417     {
1418     case GIMP_ZOOM_FOCUS_POINTER:
1419       *x = other_x;
1420       *y = other_y;
1421       break;
1422 
1423     case GIMP_ZOOM_FOCUS_IMAGE_CENTER:
1424       *x = image_center_x;
1425       *y = image_center_y;
1426       break;
1427 
1428     case GIMP_ZOOM_FOCUS_BEST_GUESS:
1429     default:
1430       {
1431         gboolean within_horizontally, within_vertically;
1432         gboolean stops_horizontally, stops_vertically;
1433 
1434         gimp_display_shell_scale_image_is_within_viewport (shell,
1435                                                            &within_horizontally,
1436                                                            &within_vertically);
1437 
1438         gimp_display_shell_scale_image_stops_to_fit (shell,
1439                                                      new_scale,
1440                                                      current_scale,
1441                                                      &stops_horizontally,
1442                                                      &stops_vertically);
1443 
1444         *x = within_horizontally && ! stops_horizontally ? image_center_x : other_x;
1445         *y = within_vertically   && ! stops_vertically   ? image_center_y : other_y;
1446       }
1447       break;
1448     }
1449 }
1450