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