summaryrefslogtreecommitdiffstats
path: root/app/display/gimpdisplayshell-callbacks.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--app/display/gimpdisplayshell-callbacks.c652
1 files changed, 652 insertions, 0 deletions
diff --git a/app/display/gimpdisplayshell-callbacks.c b/app/display/gimpdisplayshell-callbacks.c
new file mode 100644
index 0000000..4b87898
--- /dev/null
+++ b/app/display/gimpdisplayshell-callbacks.c
@@ -0,0 +1,652 @@
+/* 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 (_("<big>Unstable Development Version</big>\n\n"
+ "<small>commit <tt>%s</tt></small>\n\n"
+ "<small>Please test bugs against "
+ "latest git master branch\n"
+ "before reporting them.</small>"),
+ 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 */
+}