/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include "libgimpmath/gimpmath.h" #include "libgimpwidgets/gimpwidgets.h" #include "display-types.h" #include "core/gimp.h" #include "core/gimpimage.h" #include "core/gimpimage-quick-mask.h" #include "widgets/gimpcairo-wilber.h" #include "widgets/gimpuimanager.h" #include "gimpcanvasitem.h" #include "gimpdisplay.h" #include "gimpdisplayshell.h" #include "gimpdisplayshell-appearance.h" #include "gimpdisplayshell-callbacks.h" #include "gimpdisplayshell-draw.h" #include "gimpdisplayshell-scale.h" #include "gimpdisplayshell-scroll.h" #include "gimpdisplayshell-scrollbars.h" #include "gimpdisplayshell-selection.h" #include "gimpdisplayshell-title.h" #include "gimpdisplayshell-transform.h" #include "gimpdisplayxfer.h" #include "gimpimagewindow.h" #include "gimpnavigationeditor.h" #include "git-version.h" #include "gimp-intl.h" /* local function prototypes */ static void gimp_display_shell_vadjustment_changed (GtkAdjustment *adjustment, GimpDisplayShell *shell); static void gimp_display_shell_hadjustment_changed (GtkAdjustment *adjustment, GimpDisplayShell *shell); static gboolean gimp_display_shell_vscrollbar_change_value (GtkRange *range, GtkScrollType scroll, gdouble value, GimpDisplayShell *shell); static gboolean gimp_display_shell_hscrollbar_change_value (GtkRange *range, GtkScrollType scroll, gdouble value, GimpDisplayShell *shell); static void gimp_display_shell_canvas_draw_image (GimpDisplayShell *shell, cairo_t *cr); static void gimp_display_shell_canvas_draw_drop_zone (GimpDisplayShell *shell, cairo_t *cr); /* public functions */ void gimp_display_shell_canvas_realize (GtkWidget *canvas, GimpDisplayShell *shell) { GimpCanvasPaddingMode padding_mode; GimpRGB padding_color; GtkAllocation allocation; gtk_widget_grab_focus (canvas); gimp_display_shell_get_padding (shell, &padding_mode, &padding_color); gimp_display_shell_set_padding (shell, padding_mode, &padding_color); gtk_widget_get_allocation (canvas, &allocation); gimp_display_shell_title_update (shell); shell->disp_width = allocation.width; shell->disp_height = allocation.height; /* set up the scrollbar observers */ g_signal_connect (shell->hsbdata, "value-changed", G_CALLBACK (gimp_display_shell_hadjustment_changed), shell); g_signal_connect (shell->vsbdata, "value-changed", G_CALLBACK (gimp_display_shell_vadjustment_changed), shell); g_signal_connect (shell->hsb, "change-value", G_CALLBACK (gimp_display_shell_hscrollbar_change_value), shell); g_signal_connect (shell->vsb, "change-value", G_CALLBACK (gimp_display_shell_vscrollbar_change_value), shell); /* allow shrinking */ gtk_widget_set_size_request (GTK_WIDGET (shell), 0, 0); shell->xfer = gimp_display_xfer_realize (GTK_WIDGET(shell)); /* HACK: remove with GTK+ 3.x: this unconditionally maps the * rulers, if configured to be hidden they are never visible to the * user because they will be hidden again right away. * * For some obscure reason, having the rulers mapped once prevents * crashes with tablets and on-canvas dialogs. See bug #784480 and * all its duplicates. */ gtk_widget_show (shell->hrule); gtk_widget_show (shell->vrule); } void gimp_display_shell_canvas_realize_after (GtkWidget *canvas, GimpDisplayShell *shell) { GimpImageWindow *window = gimp_display_shell_get_window (shell); /* HACK: see above: must go with GTK+ 3.x too. Restore the rulers' * intended visibility again. */ gimp_image_window_suspend_keep_pos (window); gimp_display_shell_appearance_update (shell); gimp_image_window_resume_keep_pos (window); } void gimp_display_shell_canvas_size_allocate (GtkWidget *widget, GtkAllocation *allocation, GimpDisplayShell *shell) { /* are we in destruction? */ if (! shell->display || ! gimp_display_get_shell (shell->display)) return; if ((shell->disp_width != allocation->width) || (shell->disp_height != allocation->height)) { if (shell->zoom_on_resize && shell->disp_width > 64 && shell->disp_height > 64 && allocation->width > 64 && allocation->height > 64) { gdouble scale = gimp_zoom_model_get_factor (shell->zoom); gint offset_x; gint offset_y; /* FIXME: The code is a bit of a mess */ /* multiply the zoom_factor with the ratio of the new and * old canvas diagonals */ scale *= (sqrt (SQR (allocation->width) + SQR (allocation->height)) / sqrt (SQR (shell->disp_width) + SQR (shell->disp_height))); offset_x = UNSCALEX (shell, shell->offset_x); offset_y = UNSCALEX (shell, shell->offset_y); gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO, scale); shell->offset_x = SCALEX (shell, offset_x); shell->offset_y = SCALEY (shell, offset_y); } shell->disp_width = allocation->width; shell->disp_height = allocation->height; /* When we size-allocate due to resize of the top level window, * we want some additional logic. Don't apply it on * zoom_on_resize though. */ if (shell->size_allocate_from_configure_event && ! shell->zoom_on_resize) { gboolean center_horizontally; gboolean center_vertically; gint target_offset_x; gint target_offset_y; gint sw; gint sh; gimp_display_shell_scale_get_image_size (shell, &sw, &sh); center_horizontally = sw <= shell->disp_width; center_vertically = sh <= shell->disp_height; if (! gimp_display_shell_get_infinite_canvas (shell)) { gimp_display_shell_scroll_center_image (shell, center_horizontally, center_vertically); } else { gimp_display_shell_scroll_center_content (shell, center_horizontally, center_vertically); } /* This is basically the best we can do before we get an * API for storing the image offset at the start of an * image window resize using the mouse */ target_offset_x = shell->offset_x; target_offset_y = shell->offset_y; if (! center_horizontally) { target_offset_x = MAX (shell->offset_x, 0); } if (! center_vertically) { target_offset_y = MAX (shell->offset_y, 0); } gimp_display_shell_scroll_set_offset (shell, target_offset_x, target_offset_y); } gimp_display_shell_scroll_clamp_and_update (shell); gimp_display_shell_scaled (shell); shell->size_allocate_from_configure_event = FALSE; } if (shell->size_allocate_center_image) { gimp_display_shell_scroll_center_image (shell, TRUE, TRUE); shell->size_allocate_center_image = FALSE; } } gboolean gimp_display_shell_canvas_expose (GtkWidget *widget, GdkEventExpose *eevent, GimpDisplayShell *shell) { /* are we in destruction? */ if (! shell->display || ! gimp_display_get_shell (shell->display)) return TRUE; /* we will scroll around in the next tick anyway, so we just can as * well skip the drawing of this frame and wait for the next */ if (shell->size_allocate_center_image) return TRUE; /* ignore events on overlays */ if (eevent->window == gtk_widget_get_window (widget)) { GimpImage *image = gimp_display_get_image (shell->display); cairo_t *cr; cr = gdk_cairo_create (gtk_widget_get_window (shell->canvas)); gdk_cairo_region (cr, eevent->region); cairo_clip (cr); /* If we are currently converting the image, it might be in inconsistent * state and should not be redrawn. */ if (image != NULL && ! gimp_image_get_converting (image)) { gimp_display_shell_canvas_draw_image (shell, cr); } else if (image == NULL) { gimp_display_shell_canvas_draw_drop_zone (shell, cr); } cairo_destroy (cr); } return FALSE; } gboolean gimp_display_shell_origin_button_press (GtkWidget *widget, GdkEventButton *event, GimpDisplayShell *shell) { if (! shell->display->gimp->busy) { if (event->type == GDK_BUTTON_PRESS && event->button == 1) { gboolean unused; g_signal_emit_by_name (shell, "popup-menu", &unused); } } /* Return TRUE to stop signal emission so the button doesn't grab the * pointer away from us. */ return TRUE; } gboolean gimp_display_shell_quick_mask_button_press (GtkWidget *widget, GdkEventButton *bevent, GimpDisplayShell *shell) { if (! gimp_display_get_image (shell->display)) return TRUE; if (gdk_event_triggers_context_menu ((GdkEvent *) bevent)) { GimpImageWindow *window = gimp_display_shell_get_window (shell); if (window) { GimpUIManager *manager = gimp_image_window_get_ui_manager (window); gimp_ui_manager_ui_popup (manager, "/quick-mask-popup", GTK_WIDGET (shell), NULL, NULL, NULL, NULL); } return TRUE; } return FALSE; } void gimp_display_shell_quick_mask_toggled (GtkWidget *widget, GimpDisplayShell *shell) { GimpImage *image = gimp_display_get_image (shell->display); gboolean active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); if (active != gimp_image_get_quick_mask_state (image)) { GimpImageWindow *window = gimp_display_shell_get_window (shell); if (window) { GimpUIManager *manager = gimp_image_window_get_ui_manager (window); gimp_ui_manager_toggle_action (manager, "quick-mask", "quick-mask-toggle", active); } } } gboolean gimp_display_shell_navigation_button_press (GtkWidget *widget, GdkEventButton *bevent, GimpDisplayShell *shell) { if (! gimp_display_get_image (shell->display)) return TRUE; if (bevent->type == GDK_BUTTON_PRESS && bevent->button == 1) { gimp_navigation_editor_popup (shell, widget, bevent->x, bevent->y); } return TRUE; } /* private functions */ static void gimp_display_shell_vadjustment_changed (GtkAdjustment *adjustment, GimpDisplayShell *shell) { /* If we are panning with mouse, scrollbars are to be ignored or * they will cause jitter in motion */ if (! shell->scrolling) gimp_display_shell_scroll (shell, 0, gtk_adjustment_get_value (adjustment) - shell->offset_y); } static void gimp_display_shell_hadjustment_changed (GtkAdjustment *adjustment, GimpDisplayShell *shell) { /* If we are panning with mouse, scrollbars are to be ignored or * they will cause jitter in motion */ if (! shell->scrolling) gimp_display_shell_scroll (shell, gtk_adjustment_get_value (adjustment) - shell->offset_x, 0); } static gboolean gimp_display_shell_hscrollbar_change_value (GtkRange *range, GtkScrollType scroll, gdouble value, GimpDisplayShell *shell) { if (! shell->display) return TRUE; if ((scroll == GTK_SCROLL_JUMP) || (scroll == GTK_SCROLL_PAGE_BACKWARD) || (scroll == GTK_SCROLL_PAGE_FORWARD)) return FALSE; g_object_freeze_notify (G_OBJECT (shell->hsbdata)); gimp_display_shell_scrollbars_setup_horizontal (shell, value); g_object_thaw_notify (G_OBJECT (shell->hsbdata)); /* emits "changed" */ return FALSE; } static gboolean gimp_display_shell_vscrollbar_change_value (GtkRange *range, GtkScrollType scroll, gdouble value, GimpDisplayShell *shell) { if (! shell->display) return TRUE; if ((scroll == GTK_SCROLL_JUMP) || (scroll == GTK_SCROLL_PAGE_BACKWARD) || (scroll == GTK_SCROLL_PAGE_FORWARD)) return FALSE; g_object_freeze_notify (G_OBJECT (shell->vsbdata)); gimp_display_shell_scrollbars_setup_vertical (shell, value); g_object_thaw_notify (G_OBJECT (shell->vsbdata)); /* emits "changed" */ return FALSE; } static void gimp_display_shell_canvas_draw_image (GimpDisplayShell *shell, cairo_t *cr) { cairo_rectangle_list_t *clip_rectangles; GeglRectangle image_rect; GeglRectangle rotated_image_rect; GeglRectangle canvas_rect; cairo_matrix_t matrix; gdouble x1, y1; gdouble x2, y2; gimp_display_shell_scale_get_image_unrotated_bounding_box ( shell, &image_rect.x, &image_rect.y, &image_rect.width, &image_rect.height); gimp_display_shell_scale_get_image_unrotated_bounds ( shell, &canvas_rect.x, &canvas_rect.y, &canvas_rect.width, &canvas_rect.height); /* the background has already been cleared by GdkWindow */ /* on top, draw the exposed part of the region that is inside the * image */ cairo_save (cr); clip_rectangles = cairo_copy_clip_rectangle_list (cr); cairo_get_matrix (cr, &matrix); if (shell->rotate_transform) cairo_transform (cr, shell->rotate_transform); if (shell->show_all) { cairo_save (cr); if (gimp_display_shell_get_padding_in_show_all (shell)) { cairo_rectangle (cr, canvas_rect.x, canvas_rect.y, canvas_rect.width, canvas_rect.height); cairo_clip (cr); } gimp_display_shell_draw_checkerboard (shell, cr); cairo_restore (cr); } cairo_rectangle (cr, image_rect.x, image_rect.y, image_rect.width, image_rect.height); cairo_clip (cr); gimp_display_shell_rotate_bounds (shell, image_rect.x, image_rect.y, image_rect.x + image_rect.width, image_rect.y + image_rect.height, &x1, &y1, &x2, &y2); rotated_image_rect.x = floor (x1); rotated_image_rect.y = floor (y1); rotated_image_rect.width = ceil (x2) - rotated_image_rect.x; rotated_image_rect.height = ceil (y2) - rotated_image_rect.y; if (gdk_cairo_get_clip_rectangle (cr, NULL)) { gint i; if (! shell->show_all) { cairo_save (cr); gimp_display_shell_draw_checkerboard (shell, cr); cairo_restore (cr); } if (shell->show_image) { cairo_set_matrix (cr, &matrix); for (i = 0; i < clip_rectangles->num_rectangles; i++) { cairo_rectangle_t clip_rect = clip_rectangles->rectangles[i]; GeglRectangle rect; rect.x = floor (clip_rect.x); rect.y = floor (clip_rect.y); rect.width = ceil (clip_rect.x + clip_rect.width) - rect.x; rect.height = ceil (clip_rect.y + clip_rect.height) - rect.y; if (gegl_rectangle_intersect (&rect, &rect, &rotated_image_rect)) { gimp_display_shell_draw_image (shell, cr, rect.x, rect.y, rect.width, rect.height); } } } } cairo_rectangle_list_destroy (clip_rectangles); cairo_restore (cr); /* finally, draw all the remaining image window stuff on top */ /* draw canvas items */ cairo_save (cr); if (shell->rotate_transform) cairo_transform (cr, shell->rotate_transform); gimp_canvas_item_draw (shell->canvas_item, cr); cairo_restore (cr); gimp_canvas_item_draw (shell->unrotated_item, cr); /* restart (and recalculate) the selection boundaries */ gimp_display_shell_selection_draw (shell, cr); gimp_display_shell_selection_restart (shell); } static void gimp_display_shell_canvas_draw_drop_zone (GimpDisplayShell *shell, cairo_t *cr) { cairo_save (cr); gimp_cairo_draw_drop_wilber (shell->canvas, cr, shell->blink); cairo_restore (cr); #ifdef GIMP_UNSTABLE { PangoLayout *layout; gchar *msg; GtkAllocation allocation; gint width; gint height; gdouble scale; layout = gtk_widget_create_pango_layout (shell->canvas, NULL); msg = g_strdup_printf (_("Unstable Development Version\n\n" "commit %s\n\n" "Please test bugs against " "latest git master branch\n" "before reporting them."), GIMP_GIT_VERSION_ABBREV); pango_layout_set_markup (layout, msg, -1); g_free (msg); pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER); pango_layout_get_pixel_size (layout, &width, &height); gtk_widget_get_allocation (shell->canvas, &allocation); scale = MIN (((gdouble) allocation.width / 2.0) / (gdouble) width, ((gdouble) allocation.height / 2.0) / (gdouble) height); cairo_move_to (cr, (allocation.width - (width * scale)) / 2, (allocation.height - (height * scale)) / 2); cairo_scale (cr, scale, scale); pango_cairo_show_layout (cr, layout); g_object_unref (layout); } #endif /* GIMP_UNSTABLE */ }