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