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 <gegl.h>
21 #include <gtk/gtk.h>
22
23 #include "libgimpmath/gimpmath.h"
24 #include "libgimpwidgets/gimpwidgets.h"
25
26 #include "display-types.h"
27
28 #include "core/gimp.h"
29 #include "core/gimpimage.h"
30 #include "core/gimpimage-quick-mask.h"
31
32 #include "widgets/gimpcairo-wilber.h"
33 #include "widgets/gimpuimanager.h"
34
35 #include "gimpcanvasitem.h"
36 #include "gimpdisplay.h"
37 #include "gimpdisplayshell.h"
38 #include "gimpdisplayshell-appearance.h"
39 #include "gimpdisplayshell-callbacks.h"
40 #include "gimpdisplayshell-draw.h"
41 #include "gimpdisplayshell-scale.h"
42 #include "gimpdisplayshell-scroll.h"
43 #include "gimpdisplayshell-scrollbars.h"
44 #include "gimpdisplayshell-selection.h"
45 #include "gimpdisplayshell-title.h"
46 #include "gimpdisplayshell-transform.h"
47 #include "gimpdisplayxfer.h"
48 #include "gimpimagewindow.h"
49 #include "gimpnavigationeditor.h"
50
51 #include "git-version.h"
52
53 #include "gimp-intl.h"
54
55
56 /* local function prototypes */
57
58 static void gimp_display_shell_vadjustment_changed (GtkAdjustment *adjustment,
59 GimpDisplayShell *shell);
60 static void gimp_display_shell_hadjustment_changed (GtkAdjustment *adjustment,
61 GimpDisplayShell *shell);
62 static gboolean gimp_display_shell_vscrollbar_change_value (GtkRange *range,
63 GtkScrollType scroll,
64 gdouble value,
65 GimpDisplayShell *shell);
66
67 static gboolean gimp_display_shell_hscrollbar_change_value (GtkRange *range,
68 GtkScrollType scroll,
69 gdouble value,
70 GimpDisplayShell *shell);
71
72 static void gimp_display_shell_canvas_draw_image (GimpDisplayShell *shell,
73 cairo_t *cr);
74 static void gimp_display_shell_canvas_draw_drop_zone (GimpDisplayShell *shell,
75 cairo_t *cr);
76
77
78 /* public functions */
79
80 void
gimp_display_shell_canvas_realize(GtkWidget * canvas,GimpDisplayShell * shell)81 gimp_display_shell_canvas_realize (GtkWidget *canvas,
82 GimpDisplayShell *shell)
83 {
84 GimpCanvasPaddingMode padding_mode;
85 GimpRGB padding_color;
86 GtkAllocation allocation;
87
88 gtk_widget_grab_focus (canvas);
89
90 gimp_display_shell_get_padding (shell, &padding_mode, &padding_color);
91 gimp_display_shell_set_padding (shell, padding_mode, &padding_color);
92
93 gtk_widget_get_allocation (canvas, &allocation);
94
95 gimp_display_shell_title_update (shell);
96
97 shell->disp_width = allocation.width;
98 shell->disp_height = allocation.height;
99
100 /* set up the scrollbar observers */
101 g_signal_connect (shell->hsbdata, "value-changed",
102 G_CALLBACK (gimp_display_shell_hadjustment_changed),
103 shell);
104 g_signal_connect (shell->vsbdata, "value-changed",
105 G_CALLBACK (gimp_display_shell_vadjustment_changed),
106 shell);
107
108 g_signal_connect (shell->hsb, "change-value",
109 G_CALLBACK (gimp_display_shell_hscrollbar_change_value),
110 shell);
111
112 g_signal_connect (shell->vsb, "change-value",
113 G_CALLBACK (gimp_display_shell_vscrollbar_change_value),
114 shell);
115
116 /* allow shrinking */
117 gtk_widget_set_size_request (GTK_WIDGET (shell), 0, 0);
118
119 shell->xfer = gimp_display_xfer_realize (GTK_WIDGET(shell));
120
121 /* HACK: remove with GTK+ 3.x: this unconditionally maps the
122 * rulers, if configured to be hidden they are never visible to the
123 * user because they will be hidden again right away.
124 *
125 * For some obscure reason, having the rulers mapped once prevents
126 * crashes with tablets and on-canvas dialogs. See bug #784480 and
127 * all its duplicates.
128 */
129 gtk_widget_show (shell->hrule);
130 gtk_widget_show (shell->vrule);
131 }
132
133 void
gimp_display_shell_canvas_realize_after(GtkWidget * canvas,GimpDisplayShell * shell)134 gimp_display_shell_canvas_realize_after (GtkWidget *canvas,
135 GimpDisplayShell *shell)
136 {
137 GimpImageWindow *window = gimp_display_shell_get_window (shell);
138
139 /* HACK: see above: must go with GTK+ 3.x too. Restore the rulers'
140 * intended visibility again.
141 */
142 gimp_image_window_suspend_keep_pos (window);
143 gimp_display_shell_appearance_update (shell);
144 gimp_image_window_resume_keep_pos (window);
145 }
146
147 void
gimp_display_shell_canvas_size_allocate(GtkWidget * widget,GtkAllocation * allocation,GimpDisplayShell * shell)148 gimp_display_shell_canvas_size_allocate (GtkWidget *widget,
149 GtkAllocation *allocation,
150 GimpDisplayShell *shell)
151 {
152 /* are we in destruction? */
153 if (! shell->display || ! gimp_display_get_shell (shell->display))
154 return;
155
156 if ((shell->disp_width != allocation->width) ||
157 (shell->disp_height != allocation->height))
158 {
159 if (shell->zoom_on_resize &&
160 shell->disp_width > 64 &&
161 shell->disp_height > 64 &&
162 allocation->width > 64 &&
163 allocation->height > 64)
164 {
165 gdouble scale = gimp_zoom_model_get_factor (shell->zoom);
166 gint offset_x;
167 gint offset_y;
168
169 /* FIXME: The code is a bit of a mess */
170
171 /* multiply the zoom_factor with the ratio of the new and
172 * old canvas diagonals
173 */
174 scale *= (sqrt (SQR (allocation->width) +
175 SQR (allocation->height)) /
176 sqrt (SQR (shell->disp_width) +
177 SQR (shell->disp_height)));
178
179 offset_x = UNSCALEX (shell, shell->offset_x);
180 offset_y = UNSCALEX (shell, shell->offset_y);
181
182 gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO, scale);
183
184 shell->offset_x = SCALEX (shell, offset_x);
185 shell->offset_y = SCALEY (shell, offset_y);
186 }
187
188 shell->disp_width = allocation->width;
189 shell->disp_height = allocation->height;
190
191 /* When we size-allocate due to resize of the top level window,
192 * we want some additional logic. Don't apply it on
193 * zoom_on_resize though.
194 */
195 if (shell->size_allocate_from_configure_event &&
196 ! shell->zoom_on_resize)
197 {
198 gboolean center_horizontally;
199 gboolean center_vertically;
200 gint target_offset_x;
201 gint target_offset_y;
202 gint sw;
203 gint sh;
204
205 gimp_display_shell_scale_get_image_size (shell, &sw, &sh);
206
207 center_horizontally = sw <= shell->disp_width;
208 center_vertically = sh <= shell->disp_height;
209
210 if (! gimp_display_shell_get_infinite_canvas (shell))
211 {
212 gimp_display_shell_scroll_center_image (shell,
213 center_horizontally,
214 center_vertically);
215 }
216 else
217 {
218 gimp_display_shell_scroll_center_content (shell,
219 center_horizontally,
220 center_vertically);
221 }
222
223 /* This is basically the best we can do before we get an
224 * API for storing the image offset at the start of an
225 * image window resize using the mouse
226 */
227 target_offset_x = shell->offset_x;
228 target_offset_y = shell->offset_y;
229
230 if (! center_horizontally)
231 {
232 target_offset_x = MAX (shell->offset_x, 0);
233 }
234
235 if (! center_vertically)
236 {
237 target_offset_y = MAX (shell->offset_y, 0);
238 }
239
240 gimp_display_shell_scroll_set_offset (shell,
241 target_offset_x,
242 target_offset_y);
243 }
244
245 gimp_display_shell_scroll_clamp_and_update (shell);
246 gimp_display_shell_scaled (shell);
247
248 shell->size_allocate_from_configure_event = FALSE;
249 }
250
251 if (shell->size_allocate_center_image)
252 {
253 gimp_display_shell_scroll_center_image (shell, TRUE, TRUE);
254
255 shell->size_allocate_center_image = FALSE;
256 }
257 }
258
259 gboolean
gimp_display_shell_canvas_expose(GtkWidget * widget,GdkEventExpose * eevent,GimpDisplayShell * shell)260 gimp_display_shell_canvas_expose (GtkWidget *widget,
261 GdkEventExpose *eevent,
262 GimpDisplayShell *shell)
263 {
264 /* are we in destruction? */
265 if (! shell->display || ! gimp_display_get_shell (shell->display))
266 return TRUE;
267
268 /* we will scroll around in the next tick anyway, so we just can as
269 * well skip the drawing of this frame and wait for the next
270 */
271 if (shell->size_allocate_center_image)
272 return TRUE;
273
274 /* ignore events on overlays */
275 if (eevent->window == gtk_widget_get_window (widget))
276 {
277 cairo_t *cr;
278
279 cr = gdk_cairo_create (gtk_widget_get_window (shell->canvas));
280 gdk_cairo_region (cr, eevent->region);
281 cairo_clip (cr);
282
283 if (gimp_display_get_image (shell->display))
284 {
285 gimp_display_shell_canvas_draw_image (shell, cr);
286 }
287 else
288 {
289 gimp_display_shell_canvas_draw_drop_zone (shell, cr);
290 }
291
292 cairo_destroy (cr);
293 }
294
295 return FALSE;
296 }
297
298 gboolean
gimp_display_shell_origin_button_press(GtkWidget * widget,GdkEventButton * event,GimpDisplayShell * shell)299 gimp_display_shell_origin_button_press (GtkWidget *widget,
300 GdkEventButton *event,
301 GimpDisplayShell *shell)
302 {
303 if (! shell->display->gimp->busy)
304 {
305 if (event->type == GDK_BUTTON_PRESS && event->button == 1)
306 {
307 gboolean unused;
308
309 g_signal_emit_by_name (shell, "popup-menu", &unused);
310 }
311 }
312
313 /* Return TRUE to stop signal emission so the button doesn't grab the
314 * pointer away from us.
315 */
316 return TRUE;
317 }
318
319 gboolean
gimp_display_shell_quick_mask_button_press(GtkWidget * widget,GdkEventButton * bevent,GimpDisplayShell * shell)320 gimp_display_shell_quick_mask_button_press (GtkWidget *widget,
321 GdkEventButton *bevent,
322 GimpDisplayShell *shell)
323 {
324 if (! gimp_display_get_image (shell->display))
325 return TRUE;
326
327 if (gdk_event_triggers_context_menu ((GdkEvent *) bevent))
328 {
329 GimpImageWindow *window = gimp_display_shell_get_window (shell);
330
331 if (window)
332 {
333 GimpUIManager *manager = gimp_image_window_get_ui_manager (window);
334
335 gimp_ui_manager_ui_popup (manager,
336 "/quick-mask-popup",
337 GTK_WIDGET (shell),
338 NULL, NULL, NULL, NULL);
339 }
340
341 return TRUE;
342 }
343
344 return FALSE;
345 }
346
347 void
gimp_display_shell_quick_mask_toggled(GtkWidget * widget,GimpDisplayShell * shell)348 gimp_display_shell_quick_mask_toggled (GtkWidget *widget,
349 GimpDisplayShell *shell)
350 {
351 GimpImage *image = gimp_display_get_image (shell->display);
352 gboolean active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
353
354 if (active != gimp_image_get_quick_mask_state (image))
355 {
356 GimpImageWindow *window = gimp_display_shell_get_window (shell);
357
358 if (window)
359 {
360 GimpUIManager *manager = gimp_image_window_get_ui_manager (window);
361
362 gimp_ui_manager_toggle_action (manager,
363 "quick-mask", "quick-mask-toggle",
364 active);
365 }
366 }
367 }
368
369 gboolean
gimp_display_shell_navigation_button_press(GtkWidget * widget,GdkEventButton * bevent,GimpDisplayShell * shell)370 gimp_display_shell_navigation_button_press (GtkWidget *widget,
371 GdkEventButton *bevent,
372 GimpDisplayShell *shell)
373 {
374 if (! gimp_display_get_image (shell->display))
375 return TRUE;
376
377 if (bevent->type == GDK_BUTTON_PRESS && bevent->button == 1)
378 {
379 gimp_navigation_editor_popup (shell, widget, bevent->x, bevent->y);
380 }
381
382 return TRUE;
383 }
384
385
386 /* private functions */
387
388 static void
gimp_display_shell_vadjustment_changed(GtkAdjustment * adjustment,GimpDisplayShell * shell)389 gimp_display_shell_vadjustment_changed (GtkAdjustment *adjustment,
390 GimpDisplayShell *shell)
391 {
392 /* If we are panning with mouse, scrollbars are to be ignored or
393 * they will cause jitter in motion
394 */
395 if (! shell->scrolling)
396 gimp_display_shell_scroll (shell,
397 0,
398 gtk_adjustment_get_value (adjustment) -
399 shell->offset_y);
400 }
401
402 static void
gimp_display_shell_hadjustment_changed(GtkAdjustment * adjustment,GimpDisplayShell * shell)403 gimp_display_shell_hadjustment_changed (GtkAdjustment *adjustment,
404 GimpDisplayShell *shell)
405 {
406 /* If we are panning with mouse, scrollbars are to be ignored or
407 * they will cause jitter in motion
408 */
409 if (! shell->scrolling)
410 gimp_display_shell_scroll (shell,
411 gtk_adjustment_get_value (adjustment) -
412 shell->offset_x,
413 0);
414 }
415
416 static gboolean
gimp_display_shell_hscrollbar_change_value(GtkRange * range,GtkScrollType scroll,gdouble value,GimpDisplayShell * shell)417 gimp_display_shell_hscrollbar_change_value (GtkRange *range,
418 GtkScrollType scroll,
419 gdouble value,
420 GimpDisplayShell *shell)
421 {
422 if (! shell->display)
423 return TRUE;
424
425 if ((scroll == GTK_SCROLL_JUMP) ||
426 (scroll == GTK_SCROLL_PAGE_BACKWARD) ||
427 (scroll == GTK_SCROLL_PAGE_FORWARD))
428 return FALSE;
429
430 g_object_freeze_notify (G_OBJECT (shell->hsbdata));
431
432 gimp_display_shell_scrollbars_setup_horizontal (shell, value);
433
434 g_object_thaw_notify (G_OBJECT (shell->hsbdata)); /* emits "changed" */
435
436 return FALSE;
437 }
438
439 static gboolean
gimp_display_shell_vscrollbar_change_value(GtkRange * range,GtkScrollType scroll,gdouble value,GimpDisplayShell * shell)440 gimp_display_shell_vscrollbar_change_value (GtkRange *range,
441 GtkScrollType scroll,
442 gdouble value,
443 GimpDisplayShell *shell)
444 {
445 if (! shell->display)
446 return TRUE;
447
448 if ((scroll == GTK_SCROLL_JUMP) ||
449 (scroll == GTK_SCROLL_PAGE_BACKWARD) ||
450 (scroll == GTK_SCROLL_PAGE_FORWARD))
451 return FALSE;
452
453 g_object_freeze_notify (G_OBJECT (shell->vsbdata));
454
455 gimp_display_shell_scrollbars_setup_vertical (shell, value);
456
457 g_object_thaw_notify (G_OBJECT (shell->vsbdata)); /* emits "changed" */
458
459 return FALSE;
460 }
461
462 static void
gimp_display_shell_canvas_draw_image(GimpDisplayShell * shell,cairo_t * cr)463 gimp_display_shell_canvas_draw_image (GimpDisplayShell *shell,
464 cairo_t *cr)
465 {
466 cairo_rectangle_list_t *clip_rectangles;
467 GeglRectangle image_rect;
468 GeglRectangle rotated_image_rect;
469 GeglRectangle canvas_rect;
470 cairo_matrix_t matrix;
471 gdouble x1, y1;
472 gdouble x2, y2;
473
474 gimp_display_shell_scale_get_image_unrotated_bounding_box (
475 shell,
476 &image_rect.x,
477 &image_rect.y,
478 &image_rect.width,
479 &image_rect.height);
480
481 gimp_display_shell_scale_get_image_unrotated_bounds (
482 shell,
483 &canvas_rect.x,
484 &canvas_rect.y,
485 &canvas_rect.width,
486 &canvas_rect.height);
487
488 /* the background has already been cleared by GdkWindow
489 */
490
491
492 /* on top, draw the exposed part of the region that is inside the
493 * image
494 */
495
496 cairo_save (cr);
497 clip_rectangles = cairo_copy_clip_rectangle_list (cr);
498 cairo_get_matrix (cr, &matrix);
499
500 if (shell->rotate_transform)
501 cairo_transform (cr, shell->rotate_transform);
502
503 if (shell->show_all)
504 {
505 cairo_save (cr);
506
507 if (gimp_display_shell_get_padding_in_show_all (shell))
508 {
509 cairo_rectangle (cr,
510 canvas_rect.x,
511 canvas_rect.y,
512 canvas_rect.width,
513 canvas_rect.height);
514 cairo_clip (cr);
515 }
516
517 gimp_display_shell_draw_checkerboard (shell, cr);
518
519 cairo_restore (cr);
520 }
521
522 cairo_rectangle (cr,
523 image_rect.x,
524 image_rect.y,
525 image_rect.width,
526 image_rect.height);
527 cairo_clip (cr);
528
529 gimp_display_shell_rotate_bounds (shell,
530 image_rect.x,
531 image_rect.y,
532 image_rect.x + image_rect.width,
533 image_rect.y + image_rect.height,
534 &x1, &y1, &x2, &y2);
535
536 rotated_image_rect.x = floor (x1);
537 rotated_image_rect.y = floor (y1);
538 rotated_image_rect.width = ceil (x2) - rotated_image_rect.x;
539 rotated_image_rect.height = ceil (y2) - rotated_image_rect.y;
540
541 if (gdk_cairo_get_clip_rectangle (cr, NULL))
542 {
543 gint i;
544
545 if (! shell->show_all)
546 {
547 cairo_save (cr);
548 gimp_display_shell_draw_checkerboard (shell, cr);
549 cairo_restore (cr);
550 }
551
552 if (shell->show_image)
553 {
554 cairo_set_matrix (cr, &matrix);
555
556 for (i = 0; i < clip_rectangles->num_rectangles; i++)
557 {
558 cairo_rectangle_t clip_rect = clip_rectangles->rectangles[i];
559 GeglRectangle rect;
560
561 rect.x = floor (clip_rect.x);
562 rect.y = floor (clip_rect.y);
563 rect.width = ceil (clip_rect.x + clip_rect.width) - rect.x;
564 rect.height = ceil (clip_rect.y + clip_rect.height) - rect.y;
565
566 if (gegl_rectangle_intersect (&rect, &rect, &rotated_image_rect))
567 {
568 gimp_display_shell_draw_image (shell, cr,
569 rect.x, rect.y,
570 rect.width, rect.height);
571 }
572 }
573 }
574 }
575
576 cairo_rectangle_list_destroy (clip_rectangles);
577 cairo_restore (cr);
578
579
580 /* finally, draw all the remaining image window stuff on top
581 */
582
583 /* draw canvas items */
584 cairo_save (cr);
585
586 if (shell->rotate_transform)
587 cairo_transform (cr, shell->rotate_transform);
588
589 gimp_canvas_item_draw (shell->canvas_item, cr);
590
591 cairo_restore (cr);
592
593 gimp_canvas_item_draw (shell->unrotated_item, cr);
594
595 /* restart (and recalculate) the selection boundaries */
596 gimp_display_shell_selection_draw (shell, cr);
597 gimp_display_shell_selection_restart (shell);
598 }
599
600 static void
gimp_display_shell_canvas_draw_drop_zone(GimpDisplayShell * shell,cairo_t * cr)601 gimp_display_shell_canvas_draw_drop_zone (GimpDisplayShell *shell,
602 cairo_t *cr)
603 {
604 cairo_save (cr);
605
606 gimp_cairo_draw_drop_wilber (shell->canvas, cr, shell->blink);
607
608 cairo_restore (cr);
609
610 #ifdef GIMP_UNSTABLE
611 {
612 PangoLayout *layout;
613 gchar *msg;
614 GtkAllocation allocation;
615 gint width;
616 gint height;
617 gdouble scale;
618
619 layout = gtk_widget_create_pango_layout (shell->canvas, NULL);
620
621 msg = g_strdup_printf (_("<big>Unstable Development Version</big>\n\n"
622 "<small>commit <tt>%s</tt></small>\n\n"
623 "<small>Please test bugs against "
624 "latest git master branch\n"
625 "before reporting them.</small>"),
626 GIMP_GIT_VERSION_ABBREV);
627 pango_layout_set_markup (layout, msg, -1);
628 g_free (msg);
629 pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
630
631 pango_layout_get_pixel_size (layout, &width, &height);
632 gtk_widget_get_allocation (shell->canvas, &allocation);
633
634 scale = MIN (((gdouble) allocation.width / 2.0) / (gdouble) width,
635 ((gdouble) allocation.height / 2.0) / (gdouble) height);
636
637 cairo_move_to (cr,
638 (allocation.width - (width * scale)) / 2,
639 (allocation.height - (height * scale)) / 2);
640
641 cairo_scale (cr, scale, scale);
642
643 pango_cairo_show_layout (cr, layout);
644
645 g_object_unref (layout);
646 }
647 #endif /* GIMP_UNSTABLE */
648 }
649