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 <stdlib.h>
21 #include <math.h>
22 
23 #include <gegl.h>
24 #include <gtk/gtk.h>
25 
26 #include "libgimpmath/gimpmath.h"
27 
28 #include "display-types.h"
29 
30 #include "config/gimpdisplayconfig.h"
31 
32 #include "core/gimpimage.h"
33 
34 #include "gimpcanvas.h"
35 #include "gimpdisplay.h"
36 #include "gimpdisplay-foreach.h"
37 #include "gimpdisplayshell.h"
38 #include "gimpdisplayshell-expose.h"
39 #include "gimpdisplayshell-rotate.h"
40 #include "gimpdisplayshell-rulers.h"
41 #include "gimpdisplayshell-scale.h"
42 #include "gimpdisplayshell-scroll.h"
43 #include "gimpdisplayshell-scrollbars.h"
44 #include "gimpdisplayshell-transform.h"
45 
46 
47 #define OVERPAN_FACTOR 0.5
48 
49 
50 /**
51  * gimp_display_shell_scroll:
52  * @shell:
53  * @x_offset:
54  * @y_offset:
55  *
56  * This function scrolls the image in the shell's viewport. It does
57  * actual scrolling of the pixels, so only the newly scrolled-in parts
58  * are freshly redrawn.
59  *
60  * Use it for incremental actual panning.
61  **/
62 void
gimp_display_shell_scroll(GimpDisplayShell * shell,gint x_offset,gint y_offset)63 gimp_display_shell_scroll (GimpDisplayShell *shell,
64                            gint              x_offset,
65                            gint              y_offset)
66 {
67   gint old_x;
68   gint old_y;
69 
70   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
71 
72   if (x_offset == 0 && y_offset == 0)
73     return;
74 
75   old_x = shell->offset_x;
76   old_y = shell->offset_y;
77 
78   /* freeze the active tool */
79   gimp_display_shell_pause (shell);
80 
81   shell->offset_x += x_offset;
82   shell->offset_y += y_offset;
83 
84   gimp_display_shell_scroll_clamp_and_update (shell);
85 
86   /*  the actual changes in offset  */
87   x_offset = (shell->offset_x - old_x);
88   y_offset = (shell->offset_y - old_y);
89 
90   if (x_offset || y_offset)
91     {
92       gimp_display_shell_scrolled (shell);
93 
94       gimp_overlay_box_scroll (GIMP_OVERLAY_BOX (shell->canvas),
95                                -x_offset, -y_offset);
96 
97     }
98 
99   /* re-enable the active tool */
100   gimp_display_shell_resume (shell);
101 }
102 
103 /**
104  * gimp_display_shell_scroll_set_offsets:
105  * @shell:
106  * @offset_x:
107  * @offset_y:
108  *
109  * This function scrolls the image in the shell's viewport. It redraws
110  * the entire canvas.
111  *
112  * Use it for setting the scroll offset on freshly scaled images or
113  * when the window is resized. For panning, use
114  * gimp_display_shell_scroll().
115  **/
116 void
gimp_display_shell_scroll_set_offset(GimpDisplayShell * shell,gint offset_x,gint offset_y)117 gimp_display_shell_scroll_set_offset (GimpDisplayShell *shell,
118                                       gint              offset_x,
119                                       gint              offset_y)
120 {
121   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
122 
123   if (shell->offset_x == offset_x &&
124       shell->offset_y == offset_y)
125     return;
126 
127   gimp_display_shell_scale_save_revert_values (shell);
128 
129   /* freeze the active tool */
130   gimp_display_shell_pause (shell);
131 
132   shell->offset_x = offset_x;
133   shell->offset_y = offset_y;
134 
135   gimp_display_shell_scroll_clamp_and_update (shell);
136 
137   gimp_display_shell_scrolled (shell);
138 
139   gimp_display_shell_expose_full (shell);
140 
141   /* re-enable the active tool */
142   gimp_display_shell_resume (shell);
143 }
144 
145 /**
146  * gimp_display_shell_scroll_clamp_and_update:
147  * @shell:
148  *
149  * Helper function for calling two functions that are commonly called
150  * in pairs.
151  **/
152 void
gimp_display_shell_scroll_clamp_and_update(GimpDisplayShell * shell)153 gimp_display_shell_scroll_clamp_and_update (GimpDisplayShell *shell)
154 {
155   GimpImage *image;
156 
157   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
158 
159   image = gimp_display_get_image (shell->display);
160 
161   if (image)
162     {
163       if (! shell->show_all)
164         {
165           gint bounds_x;
166           gint bounds_y;
167           gint bounds_width;
168           gint bounds_height;
169           gint min_offset_x;
170           gint max_offset_x;
171           gint min_offset_y;
172           gint max_offset_y;
173           gint offset_x;
174           gint offset_y;
175 
176           gimp_display_shell_rotate_update_transform (shell);
177 
178           gimp_display_shell_scale_get_image_bounds (shell,
179                                                      &bounds_x,
180                                                      &bounds_y,
181                                                      &bounds_width,
182                                                      &bounds_height);
183 
184           if (shell->disp_width < bounds_width)
185             {
186               min_offset_x = bounds_x -
187                              shell->disp_width * OVERPAN_FACTOR;
188               max_offset_x = bounds_x + bounds_width -
189                              shell->disp_width * (1.0 - OVERPAN_FACTOR);
190             }
191           else
192             {
193               gint overpan_amount;
194 
195               overpan_amount = shell->disp_width -
196                                bounds_width * (1.0 - OVERPAN_FACTOR);
197 
198               min_offset_x = bounds_x -
199                              overpan_amount;
200               max_offset_x = bounds_x + bounds_width - shell->disp_width +
201                              overpan_amount;
202             }
203 
204           if (shell->disp_height < bounds_height)
205             {
206               min_offset_y = bounds_y -
207                              shell->disp_height * OVERPAN_FACTOR;
208               max_offset_y = bounds_y + bounds_height -
209                              shell->disp_height * (1.0 - OVERPAN_FACTOR);
210             }
211           else
212             {
213               gint overpan_amount;
214 
215               overpan_amount = shell->disp_height -
216                                bounds_height * (1.0 - OVERPAN_FACTOR);
217 
218               min_offset_y = bounds_y -
219                              overpan_amount;
220               max_offset_y = bounds_y + bounds_height +
221                              overpan_amount - shell->disp_height;
222             }
223 
224           /* Clamp */
225 
226           offset_x = CLAMP (shell->offset_x, min_offset_x, max_offset_x);
227           offset_y = CLAMP (shell->offset_y, min_offset_y, max_offset_y);
228 
229           if (offset_x != shell->offset_x || offset_y != shell->offset_y)
230             {
231               shell->offset_x = offset_x;
232               shell->offset_y = offset_y;
233 
234               gimp_display_shell_rotate_update_transform (shell);
235             }
236 
237           /* Set scrollbar stepper sensitiity */
238 
239           gimp_display_shell_scrollbars_update_steppers (shell,
240                                                          min_offset_x,
241                                                          max_offset_x,
242                                                          min_offset_y,
243                                                          max_offset_y);
244         }
245       else
246         {
247           /* Set scrollbar stepper sensitiity */
248 
249           gimp_display_shell_scrollbars_update_steppers (shell,
250                                                          G_MININT,
251                                                          G_MAXINT,
252                                                          G_MININT,
253                                                          G_MAXINT);
254         }
255     }
256   else
257     {
258       shell->offset_x = 0;
259       shell->offset_y = 0;
260     }
261 
262   gimp_display_shell_scrollbars_update (shell);
263   gimp_display_shell_rulers_update (shell);
264 }
265 
266 /**
267  * gimp_display_shell_scroll_unoverscrollify:
268  * @shell:
269  * @in_offset_x:
270  * @in_offset_y:
271  * @out_offset_x:
272  * @out_offset_y:
273  *
274  * Takes a scroll offset and returns the offset that will not result
275  * in a scroll beyond the image border. If the image is already
276  * overscrolled, the return value is 0 for that given axis.
277  **/
278 void
gimp_display_shell_scroll_unoverscrollify(GimpDisplayShell * shell,gint in_offset_x,gint in_offset_y,gint * out_offset_x,gint * out_offset_y)279 gimp_display_shell_scroll_unoverscrollify (GimpDisplayShell *shell,
280                                            gint              in_offset_x,
281                                            gint              in_offset_y,
282                                            gint             *out_offset_x,
283                                            gint             *out_offset_y)
284 {
285   gint sw, sh;
286   gint out_offset_x_dummy, out_offset_y_dummy;
287 
288   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
289 
290   if (! out_offset_x) out_offset_x = &out_offset_x_dummy;
291   if (! out_offset_y) out_offset_y = &out_offset_y_dummy;
292 
293   *out_offset_x = in_offset_x;
294   *out_offset_y = in_offset_y;
295 
296   if (! shell->show_all)
297     {
298       gimp_display_shell_scale_get_image_size (shell, &sw, &sh);
299 
300       if (in_offset_x < 0)
301         {
302           *out_offset_x = MAX (in_offset_x,
303                                MIN (0, 0 - shell->offset_x));
304         }
305       else if (in_offset_x > 0)
306         {
307           gint min_offset = sw - shell->disp_width;
308 
309           *out_offset_x = MIN (in_offset_x,
310                                MAX (0, min_offset - shell->offset_x));
311         }
312 
313       if (in_offset_y < 0)
314         {
315           *out_offset_y = MAX (in_offset_y,
316                                MIN (0, 0 - shell->offset_y));
317         }
318       else if (in_offset_y > 0)
319         {
320           gint min_offset = sh - shell->disp_height;
321 
322           *out_offset_y = MIN (in_offset_y,
323                                MAX (0, min_offset - shell->offset_y));
324         }
325     }
326 }
327 
328 /**
329  * gimp_display_shell_scroll_center_image_xy:
330  * @shell:
331  * @image_x:
332  * @image_y:
333  *
334  * Center the viewport around the passed image coordinate
335  **/
336 void
gimp_display_shell_scroll_center_image_xy(GimpDisplayShell * shell,gdouble image_x,gdouble image_y)337 gimp_display_shell_scroll_center_image_xy (GimpDisplayShell *shell,
338                                            gdouble           image_x,
339                                            gdouble           image_y)
340 {
341   gint viewport_x;
342   gint viewport_y;
343 
344   gimp_display_shell_transform_xy (shell,
345                                    image_x, image_y,
346                                    &viewport_x, &viewport_y);
347 
348   gimp_display_shell_scroll (shell,
349                              viewport_x - shell->disp_width  / 2,
350                              viewport_y - shell->disp_height / 2);
351 }
352 
353 /**
354  * gimp_display_shell_scroll_center_image:
355  * @shell:
356  * @horizontally:
357  * @vertically:
358  *
359  * Centers the image in the display shell on the desired axes.
360  **/
361 void
gimp_display_shell_scroll_center_image(GimpDisplayShell * shell,gboolean horizontally,gboolean vertically)362 gimp_display_shell_scroll_center_image (GimpDisplayShell *shell,
363                                         gboolean          horizontally,
364                                         gboolean          vertically)
365 {
366   gint image_x;
367   gint image_y;
368   gint image_width;
369   gint image_height;
370   gint center_x;
371   gint center_y;
372   gint offset_x = 0;
373   gint offset_y = 0;
374 
375   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
376 
377   if (! shell->display                          ||
378       ! gimp_display_get_image (shell->display) ||
379       (! vertically && ! horizontally))
380     return;
381 
382   gimp_display_shell_scale_get_image_bounds (shell,
383                                              &image_x, &image_y,
384                                              &image_width, &image_height);
385 
386   if (shell->disp_width > image_width)
387     {
388       image_x     -= (shell->disp_width - image_width) / 2;
389       image_width  = shell->disp_width;
390     }
391 
392   if (shell->disp_height > image_height)
393     {
394       image_y      -= (shell->disp_height - image_height) / 2;
395       image_height  = shell->disp_height;
396     }
397 
398   center_x = image_x + image_width  / 2;
399   center_y = image_y + image_height / 2;
400 
401   if (horizontally)
402     offset_x = center_x - shell->disp_width / 2 - shell->offset_x;
403 
404   if (vertically)
405     offset_y = center_y - shell->disp_height / 2 - shell->offset_y;
406 
407   gimp_display_shell_scroll (shell, offset_x, offset_y);
408 }
409 
410 /**
411  * gimp_display_shell_scroll_center_image:
412  * @shell:
413  * @horizontally:
414  * @vertically:
415  *
416  * Centers the image content in the display shell on the desired axes.
417  **/
418 void
gimp_display_shell_scroll_center_content(GimpDisplayShell * shell,gboolean horizontally,gboolean vertically)419 gimp_display_shell_scroll_center_content (GimpDisplayShell *shell,
420                                           gboolean          horizontally,
421                                           gboolean          vertically)
422 {
423   gint content_x;
424   gint content_y;
425   gint content_width;
426   gint content_height;
427   gint center_x;
428   gint center_y;
429   gint offset_x = 0;
430   gint offset_y = 0;
431 
432   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
433 
434   if (! shell->display                          ||
435       ! gimp_display_get_image (shell->display) ||
436       (! vertically && ! horizontally))
437     return;
438 
439   if (! gimp_display_shell_get_infinite_canvas (shell))
440     {
441       gimp_display_shell_scale_get_image_bounds (shell,
442                                                  &content_x,
443                                                  &content_y,
444                                                  &content_width,
445                                                  &content_height);
446     }
447   else
448     {
449       gimp_display_shell_scale_get_image_bounding_box (shell,
450                                                        &content_x,
451                                                        &content_y,
452                                                        &content_width,
453                                                        &content_height);
454     }
455 
456   if (shell->disp_width > content_width)
457     {
458       content_x     -= (shell->disp_width - content_width) / 2;
459       content_width  = shell->disp_width;
460     }
461 
462   if (shell->disp_height > content_height)
463     {
464       content_y      -= (shell->disp_height - content_height) / 2;
465       content_height  = shell->disp_height;
466     }
467 
468   center_x = content_x + content_width  / 2;
469   center_y = content_y + content_height / 2;
470 
471   if (horizontally)
472     offset_x = center_x - shell->disp_width / 2 - shell->offset_x;
473 
474   if (vertically)
475     offset_y = center_y - shell->disp_height / 2 - shell->offset_y;
476 
477   gimp_display_shell_scroll (shell, offset_x, offset_y);
478 }
479 
480 /**
481  * gimp_display_shell_scroll_get_scaled_viewport:
482  * @shell:
483  * @x:
484  * @y:
485  * @w:
486  * @h:
487  *
488  * Gets the viewport in screen coordinates, with origin at (0, 0) in
489  * the image.
490  **/
491 void
gimp_display_shell_scroll_get_scaled_viewport(GimpDisplayShell * shell,gint * x,gint * y,gint * w,gint * h)492 gimp_display_shell_scroll_get_scaled_viewport (GimpDisplayShell *shell,
493                                                gint             *x,
494                                                gint             *y,
495                                                gint             *w,
496                                                gint             *h)
497 {
498   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
499 
500   *x = shell->offset_x;
501   *y = shell->offset_y;
502   *w = shell->disp_width;
503   *h = shell->disp_height;
504 }
505 
506 /**
507  * gimp_display_shell_scroll_get_viewport:
508  * @shell:
509  * @x:
510  * @y:
511  * @w:
512  * @h:
513  *
514  * Gets the viewport in image coordinates.
515  **/
516 void
gimp_display_shell_scroll_get_viewport(GimpDisplayShell * shell,gdouble * x,gdouble * y,gdouble * w,gdouble * h)517 gimp_display_shell_scroll_get_viewport (GimpDisplayShell *shell,
518                                         gdouble          *x,
519                                         gdouble          *y,
520                                         gdouble          *w,
521                                         gdouble          *h)
522 {
523   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
524 
525   *x = shell->offset_x    / shell->scale_x;
526   *y = shell->offset_y    / shell->scale_y;
527   *w = shell->disp_width  / shell->scale_x;
528   *h = shell->disp_height / shell->scale_y;
529 }
530